From 3a1d20b748b63ef30369c52d230bdf41c32794ec Mon Sep 17 00:00:00 2001 From: Aarsh Shah Date: Fri, 29 May 2020 19:46:58 +0530 Subject: [PATCH] Filter Interface Addresses (#936) * Filter Host Addresses --- go.mod | 4 +- p2p/host/basic/basic_host.go | 83 +++++++++++++++++++++++++++---- p2p/host/basic/basic_host_test.go | 53 ++++++++++++++++++++ 3 files changed, 128 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index bf09aee29c..8a45a07229 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/ipfs/go-log v1.0.4 github.com/jbenet/go-cienv v0.1.0 github.com/jbenet/goprocess v0.1.4 + github.com/libp2p/go-addr-util v0.0.2 github.com/libp2p/go-conn-security-multistream v0.2.0 github.com/libp2p/go-eventbus v0.1.0 github.com/libp2p/go-libp2p-autonat v0.2.3 @@ -29,7 +30,8 @@ require ( github.com/libp2p/go-libp2p-tls v0.1.3 github.com/libp2p/go-libp2p-transport-upgrader v0.3.0 github.com/libp2p/go-libp2p-yamux v0.2.7 - github.com/libp2p/go-sockaddr v0.1.0 // indirect + github.com/libp2p/go-netroute v0.1.2 + github.com/libp2p/go-sockaddr v0.1.0 github.com/libp2p/go-stream-muxer-multistream v0.3.0 github.com/libp2p/go-tcp-transport v0.2.0 github.com/libp2p/go-ws-transport v0.3.1 diff --git a/p2p/host/basic/basic_host.go b/p2p/host/basic/basic_host.go index 530b998ac0..7b2f308a28 100644 --- a/p2p/host/basic/basic_host.go +++ b/p2p/host/basic/basic_host.go @@ -19,10 +19,12 @@ import ( "github.com/libp2p/go-libp2p-core/protocol" "github.com/libp2p/go-libp2p-core/record" + addrutil "github.com/libp2p/go-addr-util" "github.com/libp2p/go-eventbus" inat "github.com/libp2p/go-libp2p-nat" "github.com/libp2p/go-libp2p/p2p/protocol/identify" "github.com/libp2p/go-libp2p/p2p/protocol/ping" + "github.com/libp2p/go-netroute" logging "github.com/ipfs/go-log" @@ -101,6 +103,10 @@ type BasicHost struct { addrChangeChan chan struct{} + lipMu sync.RWMutex + localIPv4Addr ma.Multiaddr + localIPv6Addr ma.Multiaddr + disableSignedPeerRecord bool signKey crypto.PrivKey caBook peerstore.CertifiedAddrBook @@ -145,11 +151,11 @@ type HostOpts struct { } // NewHost constructs a new *BasicHost and activates it by attaching its stream and connection handlers to the given inet.Network. -func NewHost(ctx context.Context, net network.Network, opts *HostOpts) (*BasicHost, error) { +func NewHost(ctx context.Context, n network.Network, opts *HostOpts) (*BasicHost, error) { hostCtx, cancel := context.WithCancel(ctx) h := &BasicHost{ - network: net, + network: n, mux: msmux.NewMultistreamMuxer(), negtimeout: DefaultNegotiationTimeout, AddrsFactory: DefaultAddrsFactory, @@ -161,6 +167,8 @@ func NewHost(ctx context.Context, net network.Network, opts *HostOpts) (*BasicHo disableSignedPeerRecord: opts.DisableSignedPeerRecord, } + h.updateLocalIpAddr() + var err error if h.emitters.evtLocalProtocolsUpdated, err = h.eventbus.Emitter(&event.EvtLocalProtocolsUpdated{}); err != nil { return nil, err @@ -170,7 +178,7 @@ func NewHost(ctx context.Context, net network.Network, opts *HostOpts) (*BasicHo } if !h.disableSignedPeerRecord { - cab, ok := peerstore.GetCertifiedAddrBook(net.Peerstore()) + cab, ok := peerstore.GetCertifiedAddrBook(n.Peerstore()) if !ok { return nil, errors.New("peerstore should also be a certified address book") } @@ -212,7 +220,7 @@ func NewHost(ctx context.Context, net network.Network, opts *HostOpts) (*BasicHo } if opts.NATManager != nil { - h.natmgr = opts.NATManager(net) + h.natmgr = opts.NATManager(n) } if opts.MultiaddrResolver != nil { @@ -223,14 +231,14 @@ func NewHost(ctx context.Context, net network.Network, opts *HostOpts) (*BasicHo h.cmgr = &connmgr.NullConnMgr{} } else { h.cmgr = opts.ConnManager - net.Notify(h.cmgr.Notifee()) + n.Notify(h.cmgr.Notifee()) } if opts.EnablePing { h.pings = ping.NewPingService(h) } - net.SetStreamHandler(h.newStreamHandler) + n.SetStreamHandler(h.newStreamHandler) // register to be notified when the network's listen addrs change, // so we can update our address set and push events if needed @@ -245,6 +253,33 @@ func NewHost(ctx context.Context, net network.Network, opts *HostOpts) (*BasicHo return h, nil } +func (h *BasicHost) updateLocalIpAddr() { + h.lipMu.Lock() + defer h.lipMu.Unlock() + + if r, err := netroute.New(); err != nil { + log.Debugw("failed to build Router for kernel's routing table", "err", err) + } else { + if _, _, localIPv4, err := r.Route(net.IPv4zero); err != nil { + log.Debugw("failed to fetch local IPv4 address", "err", err) + } else { + maddr, err := manet.FromIP(localIPv4) + if err == nil { + h.localIPv4Addr = maddr + } + } + + if _, _, localIpv6, err := r.Route(net.IPv6unspecified); err != nil { + log.Debugw("failed to fetch local IPv6 address", "err", err) + } else { + maddr, err := manet.FromIP(localIpv6) + if err == nil { + h.localIPv6Addr = maddr + } + } + } +} + // New constructs and sets up a new *BasicHost with given Network and options. // The following options can be passed: // * NATPortMap @@ -433,6 +468,7 @@ func (h *BasicHost) background() { defer ticker.Stop() for { + h.updateLocalIpAddr() curr := h.Addrs() emitAddrChange(curr, lastAddrs) lastAddrs = curr @@ -711,10 +747,37 @@ func dedupAddrs(addrs []ma.Multiaddr) (uniqueAddrs []ma.Multiaddr) { // AllAddrs returns all the addresses of BasicHost at this moment in time. // It's ok to not include addresses if they're not available to be used now. func (h *BasicHost) AllAddrs() []ma.Multiaddr { - listenAddrs, err := h.Network().InterfaceListenAddresses() - if err != nil { - log.Debug("error retrieving network interface addrs") + h.lipMu.RLock() + localIPv4Addr := h.localIPv4Addr + localIPv6Addr := h.localIPv6Addr + h.lipMu.RUnlock() + + var finalAddrs []ma.Multiaddr + listenAddrs := h.Network().ListenAddresses() + for _, addr := range listenAddrs { + if !manet.IsIPUnspecified(addr) { + finalAddrs = append(finalAddrs, addr) + continue + } + + ifaceAddrs := []ma.Multiaddr{manet.IP4Loopback, manet.IP6Loopback} + if localIPv4Addr != nil { + ifaceAddrs = append(ifaceAddrs, localIPv4Addr) + } + if localIPv6Addr != nil { + ifaceAddrs = append(ifaceAddrs, localIPv6Addr) + } + + resolved, err := addrutil.ResolveUnspecifiedAddress(addr, ifaceAddrs) + if err == nil { + for _, r := range resolved { + finalAddrs = append(finalAddrs, r) + } + } } + + finalAddrs = dedupAddrs(finalAddrs) + var natMappings []inat.Mapping // natmgr is nil if we do not use nat option; @@ -723,9 +786,7 @@ func (h *BasicHost) AllAddrs() []ma.Multiaddr { natMappings = h.natmgr.NAT().Mappings() } - finalAddrs := listenAddrs if len(natMappings) > 0 { - // We have successfully mapped ports on our NAT. Use those // instead of observed addresses (mostly). diff --git a/p2p/host/basic/basic_host_test.go b/p2p/host/basic/basic_host_test.go index 4a8a89a0e7..31d732d132 100644 --- a/p2p/host/basic/basic_host_test.go +++ b/p2p/host/basic/basic_host_test.go @@ -25,6 +25,7 @@ import ( ma "github.com/multiformats/go-multiaddr" madns "github.com/multiformats/go-multiaddr-dns" + manet "github.com/multiformats/go-multiaddr-net" "github.com/stretchr/testify/require" ) @@ -201,6 +202,58 @@ func TestHostAddrsFactory(t *testing.T) { } } +func TestLocalIPChangesWhenListenAddrChanges(t *testing.T) { + ctx := context.Background() + + // no listen addrs + h := New(swarmt.GenSwarm(t, ctx, swarmt.OptDialOnly)) + defer h.Close() + + h.lipMu.Lock() + h.localIPv4Addr = nil + h.lipMu.Unlock() + + // change listen addrs and veify local IP addr is not nil again + require.NoError(t, h.Network().Listen(ma.StringCast("/ip4/0.0.0.0/tcp/0"))) + h.SignalAddressChange() + time.Sleep(1 * time.Second) + + h.lipMu.RLock() + h.lipMu.RUnlock() + require.NotNil(t, h.localIPv4Addr) +} + +func TestAllAddrs(t *testing.T) { + ctx := context.Background() + + // no listen addrs + h := New(swarmt.GenSwarm(t, ctx, swarmt.OptDialOnly)) + defer h.Close() + require.Nil(t, h.AllAddrs()) + + h.lipMu.RLock() + localIPv4Addr := h.localIPv4Addr + h.lipMu.RUnlock() + + // listen on private IP address and see it's available on the address + laddr := localIPv4Addr.Encapsulate(ma.StringCast("/tcp/0")) + require.NoError(t, h.Network().Listen(laddr)) + require.Len(t, h.AllAddrs(), 1) + addr := ma.Split(h.AllAddrs()[0]) + require.Equal(t, localIPv4Addr.String(), addr[0].String()) + + // listen on IPv4 0.0.0.0 + require.NoError(t, h.Network().Listen(ma.StringCast("/ip4/0.0.0.0/tcp/0"))) + // should contain localhost and private local addr along with previous listen address + require.Len(t, h.AllAddrs(), 3) + ipmap := make(map[string]struct{}) + for _, a := range h.AllAddrs() { + ipmap[ma.Split(a)[0].String()] = struct{}{} + } + require.Contains(t, ipmap, localIPv4Addr.String()) + require.Contains(t, ipmap, manet.IP4Loopback.String()) +} + func getHostPair(ctx context.Context, t *testing.T) (host.Host, host.Host) { t.Helper()