From 30d4caeac63f368f5164569d96cb0dd754f44222 Mon Sep 17 00:00:00 2001 From: Jim Posen Date: Tue, 29 Aug 2017 14:36:26 -0700 Subject: [PATCH] server: Simplify logic to bind listeners. The helper function parseListeners has been changed to return a slice of net.Addrs with Network() returning tcp4 or tcp6 instead of returning two slices of IPv4 and IPv6 addresses to simplify calling code. Also improves how local addresses are added to the address manager when listening on wildcard addresses. Also splits some newServer logic into new method initListeners. --- server.go | 311 +++++++++++++++++++++++++++++------------------------- 1 file changed, 165 insertions(+), 146 deletions(-) diff --git a/server.go b/server.go index a865e247..c2092c44 100644 --- a/server.go +++ b/server.go @@ -93,6 +93,28 @@ func (oa *onionAddr) Network() string { // Ensure onionAddr implements the net.Addr interface. var _ net.Addr = (*onionAddr)(nil) +// onionAddr implements the net.Addr interface with two struct fields +type simpleAddr struct { + net, addr string +} + +// String returns the address. +// +// This is part of the net.Addr interface. +func (a simpleAddr) String() string { + return a.addr +} + +// Network returns the network. +// +// This is part of the net.Addr interface. +func (a simpleAddr) Network() string { + return a.net +} + +// Ensure simpleAddr implements the net.Addr interface. +var _ net.Addr = simpleAddr{} + // broadcastMsg provides the ability to house a bitcoin message to be broadcast // to all connected peers except specified excluded peers. type broadcastMsg struct { @@ -1981,28 +2003,23 @@ func (s *server) ScheduleShutdown(duration time.Duration) { }() } -// parseListeners splits the list of listen addresses passed in addrs into -// IPv4 and IPv6 slices and returns them. This allows easy creation of the -// listeners on the correct interface "tcp4" and "tcp6". It also properly -// detects addresses which apply to "all interfaces" and adds the address to -// both slices. -func parseListeners(addrs []string) ([]string, []string, bool, error) { - ipv4ListenAddrs := make([]string, 0, len(addrs)*2) - ipv6ListenAddrs := make([]string, 0, len(addrs)*2) - haveWildcard := false - +// parseListeners determines whether each listen address is IPv4 and IPv6 and +// returns a slice of appropriate net.Addrs to listen on with TCP. It also +// properly detects addresses which apply to "all interfaces" and adds the +// address as both IPv4 and IPv6. +func parseListeners(addrs []string) ([]net.Addr, error) { + netAddrs := make([]net.Addr, 0, len(addrs)*2) for _, addr := range addrs { host, _, err := net.SplitHostPort(addr) if err != nil { // Shouldn't happen due to already being normalized. - return nil, nil, false, err + return nil, err } // Empty host or host of * on plan9 is both IPv4 and IPv6. if host == "" || (host == "*" && runtime.GOOS == "plan9") { - ipv4ListenAddrs = append(ipv4ListenAddrs, addr) - ipv6ListenAddrs = append(ipv6ListenAddrs, addr) - haveWildcard = true + netAddrs = append(netAddrs, simpleAddr{net: "tcp4", addr: addr}) + netAddrs = append(netAddrs, simpleAddr{net: "tcp6", addr: addr}) continue } @@ -2016,19 +2033,18 @@ func parseListeners(addrs []string) ([]string, []string, bool, error) { // Parse the IP. ip := net.ParseIP(host) if ip == nil { - return nil, nil, false, fmt.Errorf("'%s' is not a "+ - "valid IP address", host) + return nil, fmt.Errorf("'%s' is not a valid IP address", host) } // To4 returns nil when the IP is not an IPv4 address, so use // this determine the address type. if ip.To4() == nil { - ipv6ListenAddrs = append(ipv6ListenAddrs, addr) + netAddrs = append(netAddrs, simpleAddr{net: "tcp6", addr: addr}) } else { - ipv4ListenAddrs = append(ipv4ListenAddrs, addr) + netAddrs = append(netAddrs, simpleAddr{net: "tcp4", addr: addr}) } } - return ipv4ListenAddrs, ipv6ListenAddrs, haveWildcard, nil + return netAddrs, nil } func (s *server) upnpUpdateThread() { @@ -2116,24 +2132,14 @@ func setupRPCListeners() ([]net.Listener, error) { } } - // TODO: This code is similar to the peer listener code. It should be - // factored into something shared. - ipv4Addrs, ipv6Addrs, _, err := parseListeners(cfg.RPCListeners) + netAddrs, err := parseListeners(cfg.RPCListeners) if err != nil { return nil, err } - listeners := make([]net.Listener, 0, len(ipv4Addrs)+len(ipv4Addrs)) - for _, addr := range ipv4Addrs { - listener, err := listenFunc("tcp4", addr) - if err != nil { - rpcsLog.Warnf("Can't listen on %s: %v", addr, err) - continue - } - listeners = append(listeners, listener) - } - for _, addr := range ipv6Addrs { - listener, err := listenFunc("tcp6", addr) + listeners := make([]net.Listener, 0, len(netAddrs)) + for _, addr := range netAddrs { + listener, err := listenFunc(addr.Network(), addr.String()) if err != nil { rpcsLog.Warnf("Can't listen on %s: %v", addr, err) continue @@ -2158,122 +2164,11 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param var listeners []net.Listener var nat NAT if !cfg.DisableListen { - ipv4Addrs, ipv6Addrs, wildcard, err := - parseListeners(listenAddrs) + var err error + listeners, nat, err = initListeners(amgr, listenAddrs, services) if err != nil { return nil, err } - listeners = make([]net.Listener, 0, len(ipv4Addrs)+len(ipv6Addrs)) - discover := true - if len(cfg.ExternalIPs) != 0 { - discover = false - // if this fails we have real issues. - port, _ := strconv.ParseUint( - activeNetParams.DefaultPort, 10, 16) - - for _, sip := range cfg.ExternalIPs { - eport := uint16(port) - host, portstr, err := net.SplitHostPort(sip) - if err != nil { - // no port, use default. - host = sip - } else { - port, err := strconv.ParseUint( - portstr, 10, 16) - if err != nil { - srvrLog.Warnf("Can not parse "+ - "port from %s for "+ - "externalip: %v", sip, - err) - continue - } - eport = uint16(port) - } - na, err := amgr.HostToNetAddress(host, eport, - services) - if err != nil { - srvrLog.Warnf("Not adding %s as "+ - "externalip: %v", sip, err) - continue - } - - err = amgr.AddLocalAddress(na, addrmgr.ManualPrio) - if err != nil { - amgrLog.Warnf("Skipping specified external IP: %v", err) - } - } - } else if cfg.Upnp { - nat, err = Discover() - if err != nil { - srvrLog.Warnf("Can't discover upnp: %v", err) - } - // nil nat here is fine, just means no upnp on network. - } - - // TODO: nonstandard port... - if wildcard { - port, err := - strconv.ParseUint(activeNetParams.DefaultPort, - 10, 16) - if err != nil { - // I can't think of a cleaner way to do this... - goto nowc - } - addrs, err := net.InterfaceAddrs() - for _, a := range addrs { - ip, _, err := net.ParseCIDR(a.String()) - if err != nil { - continue - } - na := wire.NewNetAddressIPPort(ip, - uint16(port), services) - if discover { - err = amgr.AddLocalAddress(na, addrmgr.InterfacePrio) - if err != nil { - amgrLog.Debugf("Skipping local address: %v", err) - } - } - } - } - nowc: - - for _, addr := range ipv4Addrs { - listener, err := net.Listen("tcp4", addr) - if err != nil { - srvrLog.Warnf("Can't listen on %s: %v", addr, - err) - continue - } - listeners = append(listeners, listener) - - if discover { - if na, err := amgr.DeserializeNetAddress(addr); err == nil { - err = amgr.AddLocalAddress(na, addrmgr.BoundPrio) - if err != nil { - amgrLog.Warnf("Skipping bound address: %v", err) - } - } - } - } - - for _, addr := range ipv6Addrs { - listener, err := net.Listen("tcp6", addr) - if err != nil { - srvrLog.Warnf("Can't listen on %s: %v", addr, - err) - continue - } - listeners = append(listeners, listener) - if discover { - if na, err := amgr.DeserializeNetAddress(addr); err == nil { - err = amgr.AddLocalAddress(na, addrmgr.BoundPrio) - if err != nil { - amgrLog.Debugf("Skipping bound address: %v", err) - } - } - } - } - if len(listeners) == 0 { return nil, errors.New("no valid listen address") } @@ -2537,6 +2432,84 @@ func newServer(listenAddrs []string, db database.DB, chainParams *chaincfg.Param return &s, nil } +// initListeners initializes the configured net listeners and adds any bound +// addresses to the address manager. Returns the listeners and a NAT interface, +// which is non-nil if UPnP is in use. +func initListeners(amgr *addrmgr.AddrManager, listenAddrs []string, services wire.ServiceFlag) ([]net.Listener, NAT, error) { + // Listen for TCP connections at the configured addresses + netAddrs, err := parseListeners(listenAddrs) + if err != nil { + return nil, nil, err + } + + listeners := make([]net.Listener, 0, len(netAddrs)) + for _, addr := range netAddrs { + listener, err := net.Listen(addr.Network(), addr.String()) + if err != nil { + srvrLog.Warnf("Can't listen on %s: %v", addr, err) + continue + } + listeners = append(listeners, listener) + } + + var nat NAT + if len(cfg.ExternalIPs) != 0 { + defaultPort, err := strconv.ParseUint(activeNetParams.DefaultPort, 10, 16) + if err != nil { + srvrLog.Errorf("Can not parse default port %s for active chain: %v", + activeNetParams.DefaultPort, err) + return nil, nil, err + } + + for _, sip := range cfg.ExternalIPs { + eport := uint16(defaultPort) + host, portstr, err := net.SplitHostPort(sip) + if err != nil { + // no port, use default. + host = sip + } else { + port, err := strconv.ParseUint(portstr, 10, 16) + if err != nil { + srvrLog.Warnf("Can not parse port from %s for "+ + "externalip: %v", sip, err) + continue + } + eport = uint16(port) + } + na, err := amgr.HostToNetAddress(host, eport, services) + if err != nil { + srvrLog.Warnf("Not adding %s as externalip: %v", sip, err) + continue + } + + err = amgr.AddLocalAddress(na, addrmgr.ManualPrio) + if err != nil { + amgrLog.Warnf("Skipping specified external IP: %v", err) + } + } + } else { + if cfg.Upnp { + var err error + nat, err = Discover() + if err != nil { + srvrLog.Warnf("Can't discover upnp: %v", err) + } + // nil nat here is fine, just means no upnp on network. + } + + // Add bound addresses to address manager to be advertised to peers. + for _, listener := range listeners { + addr := listener.Addr().String() + err := addLocalAddress(amgr, addr, services) + if err != nil { + amgrLog.Warnf("Skipping bound address %s: %v", addr, err) + } + } + } + + return listeners, nat, nil +} + // addrStringToNetAddr takes an address in the form of 'host:port' and returns // a net.Addr which maps to the original address with any host names resolved // to IP addresses. It also handles tor addresses properly by returning a @@ -2585,6 +2558,52 @@ func addrStringToNetAddr(addr string) (net.Addr, error) { }, nil } +// addLocalAddress adds an address that this node is listening on to the +// address manager so that it may be relayed to peers. +func addLocalAddress(addrMgr *addrmgr.AddrManager, addr string, services wire.ServiceFlag) error { + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return err + } + port, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return err + } + + if ip := net.ParseIP(host); ip != nil && ip.IsUnspecified() { + // If bound to unspecified address, advertise all local interfaces + addrs, err := net.InterfaceAddrs() + if err != nil { + return err + } + + for _, addr := range addrs { + ifaceIP, _, err := net.ParseCIDR(addr.String()) + if err != nil { + continue + } + + // If bound to 0.0.0.0, do not add IPv6 interfaces and if bound to + // ::, do not add IPv4 interfaces. + if (ip.To4() == nil) != (ifaceIP.To4() == nil) { + continue + } + + netAddr := wire.NewNetAddressIPPort(ifaceIP, uint16(port), services) + addrMgr.AddLocalAddress(netAddr, addrmgr.BoundPrio) + } + } else { + netAddr, err := addrMgr.HostToNetAddress(host, uint16(port), services) + if err != nil { + return err + } + + addrMgr.AddLocalAddress(netAddr, addrmgr.BoundPrio) + } + + return nil +} + // dynamicTickDuration is a convenience function used to dynamically choose a // tick duration based on remaining time. It is primarily used during // server shutdown to make shutdown warnings more frequent as the shutdown time