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-06-17 01:15:51 +00:00
|
|
|
"github.com/mediocregopher/mediocre-go-lib/mcmp"
|
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-06-23 18:55:27 +00:00
|
|
|
// Listener is returned by InstListener and simply wraps a net.Listener.
|
2019-02-09 19:08:30 +00:00
|
|
|
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-06-17 01:15:51 +00:00
|
|
|
cmp *mcmp.Component
|
2019-01-13 02:01:16 +00:00
|
|
|
}
|
|
|
|
|
2019-03-01 19:06:25 +00:00
|
|
|
type listenerOpts struct {
|
2019-06-17 01:15:51 +00:00
|
|
|
proto string
|
|
|
|
defaultAddr string
|
|
|
|
closeOnShutdown bool
|
2019-03-01 19:06:25 +00:00
|
|
|
}
|
|
|
|
|
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-06-23 18:55:27 +00:00
|
|
|
// ListenerOpt is a value which adjusts the behavior of InstListener.
|
2019-03-01 19:06:25 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-17 01:15:51 +00:00
|
|
|
// ListenerCloseOnShutdown sets the Listener's behavior when mrun's Shutdown
|
|
|
|
// event is triggered on its Component. If true the Listener will call Close on
|
|
|
|
// itself, if false it will do nothing.
|
|
|
|
//
|
|
|
|
// Defaults to true.
|
|
|
|
func ListenerCloseOnShutdown(closeOnShutdown bool) ListenerOpt {
|
|
|
|
return func(opts *listenerOpts) {
|
|
|
|
opts.closeOnShutdown = closeOnShutdown
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-01 19:06:25 +00:00
|
|
|
// 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".
|
2019-06-17 01:15:51 +00:00
|
|
|
func ListenerDefaultAddr(defaultAddr string) ListenerOpt {
|
2019-03-01 19:06:25 +00:00
|
|
|
return func(opts *listenerOpts) {
|
|
|
|
opts.defaultAddr = defaultAddr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-17 01:15:51 +00:00
|
|
|
// InstListener instantiates a Listener which will be initialized when the Init
|
|
|
|
// event is triggered on the given Component, and closed when the Shutdown event
|
|
|
|
// is triggered on the returned Component.
|
|
|
|
func InstListener(cmp *mcmp.Component, opts ...ListenerOpt) *Listener {
|
2019-03-01 19:06:25 +00:00
|
|
|
lOpts := listenerOpts{
|
2019-06-17 01:15:51 +00:00
|
|
|
proto: "tcp",
|
|
|
|
defaultAddr: ":0",
|
|
|
|
closeOnShutdown: true,
|
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-06-17 01:15:51 +00:00
|
|
|
cmp = cmp.Child("net")
|
|
|
|
l := &Listener{cmp: cmp}
|
2019-02-05 20:18:17 +00:00
|
|
|
|
2019-06-17 01:15:51 +00:00
|
|
|
addr := mcfg.String(cmp, "listen-addr",
|
|
|
|
mcfg.ParamDefault(lOpts.defaultAddr),
|
|
|
|
mcfg.ParamUsage(
|
|
|
|
strings.ToUpper(lOpts.proto)+" address to listen on in format "+
|
|
|
|
"[host]:port. If port is 0 then a random one will be chosen",
|
|
|
|
),
|
|
|
|
)
|
2019-03-01 19:06:25 +00:00
|
|
|
|
2019-07-09 23:54:52 +00:00
|
|
|
mrun.InitHook(cmp, func(ctx context.Context) error {
|
2019-06-17 01:15:51 +00:00
|
|
|
cmp.Annotate("proto", lOpts.proto, "addr", *addr)
|
2019-03-01 19:21:43 +00:00
|
|
|
|
2019-07-09 23:54:52 +00:00
|
|
|
var err error
|
2019-03-01 19:21:43 +00:00
|
|
|
if lOpts.isPacketConn() {
|
2019-07-09 23:54:52 +00:00
|
|
|
if l.PacketConn, err = net.ListenPacket(lOpts.proto, *addr); err != nil {
|
|
|
|
return merr.Wrap(err, cmp.Context(), ctx)
|
|
|
|
}
|
2019-06-17 01:15:51 +00:00
|
|
|
cmp.Annotate("addr", l.PacketConn.LocalAddr().String())
|
2019-03-01 19:21:43 +00:00
|
|
|
} else {
|
2019-07-09 23:54:52 +00:00
|
|
|
if l.Listener, err = net.Listen(lOpts.proto, *addr); err != nil {
|
|
|
|
return merr.Wrap(err, cmp.Context(), ctx)
|
|
|
|
}
|
2019-06-17 01:15:51 +00:00
|
|
|
cmp.Annotate("addr", l.Listener.Addr().String())
|
2019-03-01 19:21:43 +00:00
|
|
|
}
|
|
|
|
|
2019-06-17 01:15:51 +00:00
|
|
|
mlog.From(cmp).Info("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-06-17 01:15:51 +00:00
|
|
|
mrun.ShutdownHook(cmp, func(context.Context) error {
|
|
|
|
if !lOpts.closeOnShutdown {
|
2019-01-25 00:19:57 +00:00
|
|
|
return nil
|
|
|
|
}
|
2019-06-17 01:15:51 +00:00
|
|
|
mlog.From(cmp).Info("shutting down listener")
|
2019-01-13 01:11:22 +00:00
|
|
|
return l.Close()
|
|
|
|
})
|
|
|
|
|
2019-06-17 01:15:51 +00:00
|
|
|
return 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-06-17 01:15:51 +00:00
|
|
|
mlog.From(l.cmp).Debug("connection accepted",
|
|
|
|
mctx.Annotated("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 {
|
2019-06-17 01:15:51 +00:00
|
|
|
mlog.From(l.cmp).Info("listener closing")
|
2019-03-01 19:37:41 +00:00
|
|
|
if l.Listener != nil {
|
|
|
|
return l.Listener.Close()
|
|
|
|
}
|
2019-06-17 01:15:51 +00:00
|
|
|
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)
|
|
|
|
}
|