diff --git a/go.mod b/go.mod index a7f87f0..07e0fec 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/minio/sha256-simd v0.1.1 github.com/mr-tron/base58 v1.1.3 github.com/multiformats/go-multiaddr v0.2.0 + github.com/multiformats/go-multiaddr-net v0.1.1 github.com/multiformats/go-multihash v0.0.10 github.com/multiformats/go-varint v0.0.1 github.com/smola/gocompat v0.2.0 diff --git a/go.sum b/go.sum index 57e096a..0b29900 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ipfs/go-cid v0.0.3 h1:UIAh32wymBpStoe83YCzwVQQ5Oy/H0FdxvUS6DJDzms= -github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.4 h1:UlfXKrZx1DjZoBhQHmNHLC1fK1dUJDN20Y28A7s+gJ8= github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= @@ -67,10 +65,11 @@ github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= -github.com/multiformats/go-multiaddr v0.1.2 h1:HWYHNSyyllbQopmVIF5K7JKJugiah+L9/kuZKHbmNdQ= -github.com/multiformats/go-multiaddr v0.1.2/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= +github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.2.0 h1:lR52sFwcTCuQb6bTfnXF6zA2XfyYvyd+5a9qECv/J90= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= +github.com/multiformats/go-multiaddr-net v0.1.1 h1:jFFKUuXTXv+3ARyHZi3XUqQO+YWMKgBdhEvuGRfnL6s= +github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= diff --git a/host/helpers.go b/host/helpers.go index 276eeae..aa7ae3a 100644 --- a/host/helpers.go +++ b/host/helpers.go @@ -3,7 +3,10 @@ package host import ( "errors" "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/peerstore" "github.com/libp2p/go-libp2p-core/routing" + ma "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr-net" ) // InfoFromHost returns a peer.AddrInfo struct with the Host's ID and all of its Addrs. @@ -16,11 +19,51 @@ func InfoFromHost(h Host) *peer.AddrInfo { // SignedRoutingStateFromHost returns a SignedRoutingState record containing // the Host's listen addresses, signed with the Host's private key. -func SignedRoutingStateFromHost(h Host) (*routing.SignedRoutingState, error) { +// +// By default, only publicly routable addresses will be included. +// To include loopback and LAN addresses, pass in the IncludeLocalAddrs option: +// +// state := SignedRoutingStateFromHost(h, IncludeLocalAddrs) +func SignedRoutingStateFromHost(h minimalHost, opts ...Option) (*routing.SignedRoutingState, error) { + cfg := config{} + for _, opt := range opts { + opt(&cfg) + } + privKey := h.Peerstore().PrivKey(h.ID()) if privKey == nil { return nil, errors.New("unable to find host's private key in peerstore") } - return routing.MakeSignedRoutingState(privKey, h.Addrs()) + var addrs []ma.Multiaddr + if cfg.includeLocalAddrs { + addrs = h.Addrs() + } else { + for _, a := range h.Addrs() { + if manet.IsPublicAddr(a) { + addrs = append(addrs, a) + } + } + } + + return routing.MakeSignedRoutingState(privKey, addrs) +} + +// IncludeLocalAddrs can be passed into SignedRoutingStateFromHost to +// produce a routing record with LAN and loopback addresses included. +func IncludeLocalAddrs(cfg *config) { + cfg.includeLocalAddrs = true +} + +// minimalHost is the subset of the Host interface that's required by +// SignedRoutingStateFromHost. +type minimalHost interface { + ID() peer.ID + Peerstore() peerstore.Peerstore + Addrs() []ma.Multiaddr +} + +type Option func(cfg *config) +type config struct { + includeLocalAddrs bool } diff --git a/host/helpers_test.go b/host/helpers_test.go new file mode 100644 index 0000000..52cf261 --- /dev/null +++ b/host/helpers_test.go @@ -0,0 +1,131 @@ +package host + +import ( + "context" + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/peerstore" + "github.com/libp2p/go-libp2p-core/routing" + "github.com/libp2p/go-libp2p-core/test" + ma "github.com/multiformats/go-multiaddr" + "testing" + "time" +) + +type mockHost struct { + fixedPrivKey crypto.PrivKey + addrs []ma.Multiaddr +} + +func (h *mockHost) Addrs() []ma.Multiaddr { + return h.addrs +} + +func (h *mockHost) Peerstore() peerstore.Peerstore { + return mockPeerstore{fixedPrivKey: h.fixedPrivKey} +} + +func (*mockHost) ID() peer.ID { return "" } + +type mockPeerstore struct { + fixedPrivKey crypto.PrivKey +} + +// the one method I care about... +func (m mockPeerstore) PrivKey(peer.ID) crypto.PrivKey { + return m.fixedPrivKey +} + +// so many other things in the Peerstore interface... +func (m mockPeerstore) Close() error { return nil } +func (m mockPeerstore) AddAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {} +func (m mockPeerstore) AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {} +func (m mockPeerstore) AddCertifiedAddrs(s *routing.SignedRoutingState, ttl time.Duration) error { + return nil +} +func (m mockPeerstore) SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {} +func (m mockPeerstore) SetAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {} +func (m mockPeerstore) UpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL time.Duration) {} +func (m mockPeerstore) Addrs(p peer.ID) []ma.Multiaddr { return nil } +func (m mockPeerstore) CertifiedAddrs(p peer.ID) []ma.Multiaddr { return nil } +func (m mockPeerstore) AddrStream(context.Context, peer.ID) <-chan ma.Multiaddr { return nil } +func (m mockPeerstore) ClearAddrs(p peer.ID) {} +func (m mockPeerstore) PeersWithAddrs() peer.IDSlice { return nil } +func (m mockPeerstore) SignedRoutingState(p peer.ID) *routing.SignedRoutingState { return nil } +func (m mockPeerstore) Get(p peer.ID, key string) (interface{}, error) { return nil, nil } +func (m mockPeerstore) Put(p peer.ID, key string, val interface{}) error { return nil } +func (m mockPeerstore) RecordLatency(peer.ID, time.Duration) {} +func (m mockPeerstore) LatencyEWMA(peer.ID) time.Duration { return 0 } +func (m mockPeerstore) GetProtocols(peer.ID) ([]string, error) { return nil, nil } +func (m mockPeerstore) AddProtocols(peer.ID, ...string) error { return nil } +func (m mockPeerstore) SetProtocols(peer.ID, ...string) error { return nil } +func (m mockPeerstore) RemoveProtocols(peer.ID, ...string) error { return nil } +func (m mockPeerstore) SupportsProtocols(peer.ID, ...string) ([]string, error) { return nil, nil } +func (m mockPeerstore) PeerInfo(peer.ID) peer.AddrInfo { return peer.AddrInfo{} } +func (m mockPeerstore) Peers() peer.IDSlice { return nil } +func (m mockPeerstore) PubKey(peer.ID) crypto.PubKey { return nil } +func (m mockPeerstore) AddPubKey(peer.ID, crypto.PubKey) error { return nil } +func (m mockPeerstore) AddPrivKey(peer.ID, crypto.PrivKey) error { return nil } +func (m mockPeerstore) PeersWithKeys() peer.IDSlice { return nil } + +func TestSignedRoutingStateFromHost_FailsIfPrivKeyIsNil(t *testing.T) { + _, err := SignedRoutingStateFromHost(&mockHost{}) + test.ExpectError(t, err, "expected generating signed routing state to fail when host private key is nil") +} + +func TestSignedRoutingStateFromHost_AddrFiltering(t *testing.T) { + localAddrs := parseAddrs(t, + // loopback + "/ip4/127.0.0.1/tcp/42", + "/ip6/::1/tcp/9999", + + // ip4 LAN reserved + "/ip4/10.0.0.1/tcp/1234", + "/ip4/100.64.0.123/udp/10101", + "/ip4/172.16.0.254/tcp/2345", + "/ip4/192.168.1.4/udp/1600", + + // link local + "/ip4/169.254.0.1/udp/1234", + "/ip6/fe80::c001:37ff:fe6c:0/tcp/42", + ) + + wanAddrs := parseAddrs(t, + "/ip4/1.2.3.4/tcp/42", + "/ip4/8.8.8.8/udp/1234", + "/ip6/2607:f8b0:4002:c02::8a/udp/1234", + "/ip6/2a03:2880:f111:83:face:b00c:0:25de/udp/2345/quic", + ) + + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) + if err != nil { + t.Fatal(err) + } + + host := &mockHost{ + fixedPrivKey: priv, + addrs: append(localAddrs, wanAddrs...), + } + + // test with local addrs + state, err := SignedRoutingStateFromHost(host, IncludeLocalAddrs) + if err != nil { + t.Fatalf("error generating routing state: %v", err) + } + test.AssertAddressesEqual(t, host.addrs, state.Addrs) + + // test filtering out local addrs + state, err = SignedRoutingStateFromHost(host) + if err != nil { + t.Fatalf("error generating routing state: %v", err) + } + test.AssertAddressesEqual(t, wanAddrs, state.Addrs) +} + +func parseAddrs(t *testing.T, addrStrings ...string) (out []ma.Multiaddr) { + t.Helper() + for _, s := range addrStrings { + out = append(out, ma.StringCast(s)) + } + return out +}