2018-08-09 18:34:09 +00:00
// Package mnet extends the standard package with extra functionality which is
// commonly useful
package mnet
import (
2019-02-05 20:18:17 +00:00
"context"
2018-08-09 18:34:09 +00:00
"net"
2019-02-03 00:55:42 +00:00
"strings"
2019-01-11 22:47:30 +00:00
"github.com/mediocregopher/mediocre-go-lib/mcfg"
2019-02-09 19:08:30 +00:00
"github.com/mediocregopher/mediocre-go-lib/mctx"
2019-03-01 19:21:43 +00:00
"github.com/mediocregopher/mediocre-go-lib/merr"
2019-01-11 22:47:30 +00:00
"github.com/mediocregopher/mediocre-go-lib/mlog"
"github.com/mediocregopher/mediocre-go-lib/mrun"
2018-08-09 18:34:09 +00:00
)
2019-02-09 19:08:30 +00:00
// Listener is returned by WithListen and simply wraps a net.Listener.
type Listener struct {
2019-03-01 19:21:43 +00:00
// One of these will be populated during the start hook, depending on the
// protocol configured.
2019-01-13 02:01:16 +00:00
net . Listener
2019-03-01 19:21:43 +00:00
net . PacketConn
2019-02-05 20:18:17 +00:00
ctx context . Context
2019-01-25 00:19:57 +00:00
// If set to true before mrun's stop event is run, the stop event will not
// cause the MListener to be closed.
NoCloseOnStop bool
2019-01-13 02:01:16 +00:00
}
2019-03-01 19:06:25 +00:00
type listenerOpts struct {
proto string
defaultAddr string
}
2019-03-01 19:21:43 +00:00
func ( lOpts listenerOpts ) isPacketConn ( ) bool {
proto := strings . ToLower ( lOpts . proto )
return strings . HasPrefix ( proto , "udp" ) ||
proto == "unixgram" ||
strings . HasPrefix ( proto , "ip" )
}
2019-03-01 19:06:25 +00:00
// ListenerOpt is a value which adjusts the behavior of WithListener.
type ListenerOpt func ( * listenerOpts )
// ListenerProtocol adjusts the protocol which the Listener uses. The default is
// "tcp".
func ListenerProtocol ( proto string ) ListenerOpt {
return func ( opts * listenerOpts ) {
opts . proto = proto
}
}
// ListenerDefaultAddr adjusts the defaultAddr which the Listener will use. The
// addr will still be configurable via mcfg regardless of what this is set to.
// The default is ":0".
func ListenerAddr ( defaultAddr string ) ListenerOpt {
return func ( opts * listenerOpts ) {
opts . defaultAddr = defaultAddr
}
}
2019-02-09 19:08:30 +00:00
// WithListener returns a Listener which will be initialized when the start
// event is triggered on the returned Context (see mrun.Start), and closed when
// the stop event is triggered on the returned Context (see mrun.Stop).
2019-03-01 19:06:25 +00:00
func WithListener ( ctx context . Context , opts ... ListenerOpt ) ( context . Context , * Listener ) {
lOpts := listenerOpts {
proto : "tcp" ,
defaultAddr : ":0" ,
2019-01-11 22:47:30 +00:00
}
2019-03-01 19:06:25 +00:00
for _ , opt := range opts {
opt ( & lOpts )
2019-01-11 22:47:30 +00:00
}
2019-02-09 19:08:30 +00:00
l := & Listener {
ctx : mctx . NewChild ( ctx , "net" ) ,
}
2019-02-05 20:18:17 +00:00
2019-02-09 19:08:30 +00:00
var addr * string
2019-03-01 19:06:25 +00:00
l . ctx , addr = mcfg . WithString ( l . ctx , "listen-addr" , lOpts . defaultAddr , strings . ToUpper ( lOpts . proto ) + " address to listen on in format [host]:port. If port is 0 then a random one will be chosen" )
2019-02-09 19:08:30 +00:00
l . ctx = mrun . WithStartHook ( l . ctx , func ( context . Context ) error {
2019-01-11 22:47:30 +00:00
var err error
2019-03-01 19:21:43 +00:00
2019-03-01 19:06:25 +00:00
l . ctx = mctx . Annotate ( l . ctx ,
"proto" , lOpts . proto ,
2019-03-01 19:21:43 +00:00
"addr" , * addr )
if lOpts . isPacketConn ( ) {
l . PacketConn , err = net . ListenPacket ( lOpts . proto , * addr )
2019-03-01 19:36:16 +00:00
l . ctx = mctx . Annotate ( l . ctx , "addr" , l . PacketConn . LocalAddr ( ) . String ( ) )
2019-03-01 19:21:43 +00:00
} else {
l . Listener , err = net . Listen ( lOpts . proto , * addr )
2019-03-01 19:36:16 +00:00
l . ctx = mctx . Annotate ( l . ctx , "addr" , l . Listener . Addr ( ) . String ( ) )
2019-03-01 19:21:43 +00:00
}
if err != nil {
return merr . Wrap ( err , l . ctx )
}
2019-02-09 19:08:30 +00:00
mlog . Info ( "listening" , l . ctx )
2019-01-11 22:47:30 +00:00
return nil
} )
2019-01-13 01:11:22 +00:00
// TODO track connections and wait for them to complete before shutting
// down?
2019-02-09 19:08:30 +00:00
l . ctx = mrun . WithStopHook ( l . ctx , func ( context . Context ) error {
2019-01-25 00:19:57 +00:00
if l . NoCloseOnStop {
return nil
}
2019-02-09 19:08:30 +00:00
mlog . Info ( "stopping listener" , l . ctx )
2019-01-13 01:11:22 +00:00
return l . Close ( )
} )
2019-02-09 19:08:30 +00:00
return mctx . WithChild ( ctx , l . ctx ) , l
2019-01-13 02:01:16 +00:00
}
2019-01-24 21:54:38 +00:00
// Accept wraps a call to Accept on the underlying net.Listener, providing debug
// logging.
2019-02-09 19:08:30 +00:00
func ( l * Listener ) Accept ( ) ( net . Conn , error ) {
2019-01-24 21:54:38 +00:00
conn , err := l . Listener . Accept ( )
if err != nil {
return conn , err
}
2019-02-09 19:08:30 +00:00
mlog . Debug ( "connection accepted" ,
mctx . Annotate ( l . ctx , "remoteAddr" , conn . RemoteAddr ( ) . String ( ) ) )
2019-01-24 21:54:38 +00:00
return conn , nil
}
// Close wraps a call to Close on the underlying net.Listener, providing debug
// logging.
2019-02-09 19:08:30 +00:00
func ( l * Listener ) Close ( ) error {
mlog . Info ( "listener closing" , l . ctx )
2019-03-01 19:37:41 +00:00
if l . Listener != nil {
return l . Listener . Close ( )
} else {
return l . PacketConn . Close ( )
}
2019-01-11 22:47:30 +00:00
}
////////////////////////////////////////////////////////////////////////////////
2018-08-09 18:34:09 +00:00
func mustGetCIDRNetwork ( cidr string ) * net . IPNet {
_ , n , err := net . ParseCIDR ( cidr )
if err != nil {
panic ( err )
}
return n
}
// https://en.wikipedia.org/wiki/Reserved_IP_addresses
var reservedCIDRs4 = [ ] * net . IPNet {
mustGetCIDRNetwork ( "0.0.0.0/8" ) , // current network
mustGetCIDRNetwork ( "10.0.0.0/8" ) , // private network
mustGetCIDRNetwork ( "100.64.0.0/10" ) , // private network
mustGetCIDRNetwork ( "127.0.0.0/8" ) , // localhost
mustGetCIDRNetwork ( "169.254.0.0/16" ) , // link-local
mustGetCIDRNetwork ( "172.16.0.0/12" ) , // private network
mustGetCIDRNetwork ( "192.0.0.0/24" ) , // IETF protocol assignments
mustGetCIDRNetwork ( "192.0.2.0/24" ) , // documentation and examples
mustGetCIDRNetwork ( "192.88.99.0/24" ) , // 6to4 Relay
mustGetCIDRNetwork ( "192.168.0.0/16" ) , // private network
mustGetCIDRNetwork ( "198.18.0.0/15" ) , // private network
mustGetCIDRNetwork ( "198.51.100.0/24" ) , // documentation and examples
mustGetCIDRNetwork ( "203.0.113.0/24" ) , // documentation and examples
mustGetCIDRNetwork ( "224.0.0.0/4" ) , // IP multicast
mustGetCIDRNetwork ( "240.0.0.0/4" ) , // reserved
mustGetCIDRNetwork ( "255.255.255.255/32" ) , // limited broadcast address
}
var reservedCIDRs6 = [ ] * net . IPNet {
mustGetCIDRNetwork ( "::/128" ) , // unspecified address
mustGetCIDRNetwork ( "::1/128" ) , // loopback address
mustGetCIDRNetwork ( "100::/64" ) , // discard prefix
mustGetCIDRNetwork ( "2001::/32" ) , // Teredo tunneling
mustGetCIDRNetwork ( "2001:20::/28" ) , // ORCHID v2
mustGetCIDRNetwork ( "2001:db8::/32" ) , // documentation and examples
mustGetCIDRNetwork ( "2002::/16" ) , // 6to4 addressing
mustGetCIDRNetwork ( "fc00::/7" ) , // unique local
mustGetCIDRNetwork ( "fe80::/10" ) , // link local
mustGetCIDRNetwork ( "ff00::/8" ) , // multicast
}
// IsReservedIP returns true if the given valid IP is part of a reserved IP
// range.
func IsReservedIP ( ip net . IP ) bool {
containedBy := func ( cidrs [ ] * net . IPNet ) bool {
for _ , cidr := range cidrs {
if cidr . Contains ( ip ) {
return true
}
}
return false
}
if ip . To4 ( ) != nil {
return containedBy ( reservedCIDRs4 )
}
return containedBy ( reservedCIDRs6 )
}