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-01-24 21:54:38 +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-01-24 21:54:38 +00:00
// MListener is returned by MListen and simply wraps a net.Listener.
type MListener struct {
2019-01-13 02:01:16 +00:00
net . Listener
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-01-24 21:54:38 +00:00
// MListen returns an MListener which will be initialized when the start event
2019-02-05 20:18:17 +00:00
// 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-01-11 22:47:30 +00:00
//
// network defaults to "tcp" if empty. defaultAddr defaults to ":0" if empty,
// and will be configurable via mcfg.
2019-02-05 20:18:17 +00:00
func MListen ( ctx context . Context , network , defaultAddr string ) ( context . Context , * MListener ) {
2019-01-11 22:47:30 +00:00
if network == "" {
network = "tcp"
}
if defaultAddr == "" {
defaultAddr = ":0"
}
2019-01-24 21:54:38 +00:00
l := new ( MListener )
2019-01-13 02:01:16 +00:00
2019-02-05 20:18:17 +00:00
// TODO the equivalent functionality as here will be added with annotations
//l.log = mlog.From(ctx)
//l.log.SetKV(l)
ctx , addr := mcfg . String ( ctx , "listen-addr" , defaultAddr , strings . ToUpper ( network ) + " address to listen on in format [host]:port. If port is 0 then a random one will be chosen" )
ctx = mrun . OnStart ( ctx , func ( context . Context ) error {
2019-01-11 22:47:30 +00:00
var err error
if l . Listener , err = net . Listen ( network , * addr ) ; err != nil {
return err
}
2019-02-05 20:18:17 +00:00
mlog . Info ( l . ctx , "listening" )
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-05 20:18:17 +00:00
ctx = mrun . OnStop ( ctx , func ( context . Context ) error {
2019-01-25 00:19:57 +00:00
if l . NoCloseOnStop {
return nil
}
2019-02-05 20:18:17 +00:00
mlog . Info ( l . ctx , "stopping listener" )
2019-01-13 01:11:22 +00:00
return l . Close ( )
} )
2019-02-05 20:18:17 +00:00
l . ctx = ctx
return 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.
func ( l * MListener ) Accept ( ) ( net . Conn , error ) {
conn , err := l . Listener . Accept ( )
if err != nil {
return conn , err
}
2019-02-05 20:18:17 +00:00
mlog . Debug ( l . ctx , "connection accepted" , mlog . KV { "remoteAddr" : conn . RemoteAddr ( ) } )
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.
func ( l * MListener ) Close ( ) error {
2019-02-05 20:18:17 +00:00
mlog . Debug ( l . ctx , "listener closing" )
2019-01-24 21:54:38 +00:00
err := l . Listener . Close ( )
2019-02-05 20:18:17 +00:00
mlog . Debug ( l . ctx , "listener closed" , merr . KV ( err ) )
2019-01-24 21:54:38 +00:00
return err
}
// KV implements the mlog.KVer interface.
func ( l * MListener ) KV ( ) map [ string ] interface { } {
2019-01-15 03:32:26 +00:00
return map [ string ] interface { } { "addr" : l . Addr ( ) . String ( ) }
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 )
}