diff --git a/go.mod b/go.mod index 6c6e9d3..907c155 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/ipfs/go-cid v0.0.7 github.com/multiformats/go-multihash v0.0.14 github.com/multiformats/go-varint v0.0.6 + github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index 53b95ca..67ce308 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= @@ -19,8 +21,13 @@ github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUj github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -29,3 +36,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/multiaddr.go b/multiaddr.go index 58fe8ce..b6b39f7 100644 --- a/multiaddr.go +++ b/multiaddr.go @@ -184,3 +184,19 @@ func (m *multiaddr) ValueForProtocol(code int) (value string, err error) { }) return } + +// FilterAddrs is a filter that removes certain addresses, according to the given filters. +// If all filters return true, the address is kept. +func FilterAddrs(a []Multiaddr, filters ...func(Multiaddr) bool) []Multiaddr { + b := make([]Multiaddr, 0, len(a)) +addrloop: + for _, addr := range a { + for _, filter := range filters { + if !filter(addr) { + continue addrloop + } + } + b = append(b, addr) + } + return b +} diff --git a/multiaddr_test.go b/multiaddr_test.go index f617ff9..d130081 100644 --- a/multiaddr_test.go +++ b/multiaddr_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/ipfs/go-cid" mh "github.com/multiformats/go-multihash" ) @@ -226,7 +228,7 @@ func TestStringToBytes(t *testing.T) { t.Error("failed to decode hex", h) } - //t.Log("196", h, []byte(b1)) + // t.Log("196", h, []byte(b1)) b2, err := stringToBytes(s) if err != nil { @@ -739,3 +741,24 @@ func TestComponentJSONMarshaler(t *testing.T) { t.Error("expected equal components in circular marshaling test") } } + +func TestFilterAddrs(t *testing.T) { + bad := []Multiaddr{ + newMultiaddr(t, "/ip6/fe80::1/tcp/1234"), + newMultiaddr(t, "/ip6/fe80::100/tcp/1234"), + } + good := []Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip4/1.1.1.1/tcp/999"), + newMultiaddr(t, "/ip4/1.2.3.4/udp/1234/utp"), + } + goodAndBad := append(good, bad...) + + filter := func(addr Multiaddr) bool { + return addr.Protocols()[0].Code == P_IP4 + } + + require.Empty(t, FilterAddrs(bad, filter)) + require.ElementsMatch(t, FilterAddrs(good, filter), good) + require.ElementsMatch(t, FilterAddrs(goodAndBad, filter), good) +} diff --git a/net/ip.go b/net/ip.go index 1cf9a77..fab5ee5 100644 --- a/net/ip.go +++ b/net/ip.go @@ -95,6 +95,18 @@ func IsIPUnspecified(m ma.Multiaddr) bool { return net.IP(c.RawValue()).IsUnspecified() } +// IsIpv6LinkLocal returns whether the addr uses a non-local ip link +func IsIpv6LinkLocal(a ma.Multiaddr) bool { + split := ma.Split(a) + if len(split) < 1 { + return false + } + if IsIP6LinkLocal(split[0]) { + return false + } + return true +} + // If m matches [zone,ip6,...], return [ip6,...] // else if m matches [], [zone], or [zone,...], return nil // else return m diff --git a/net/resolve.go b/net/resolve.go new file mode 100644 index 0000000..d9d0a4d --- /dev/null +++ b/net/resolve.go @@ -0,0 +1,83 @@ +package manet + +import ( + "fmt" + + ma "github.com/multiformats/go-multiaddr" +) + +// ResolveUnspecifiedAddress expands an unspecified ip addresses (/ip4/0.0.0.0, /ip6/::) to +// use the known local interfaces. If ifaceAddr is nil, we request interface addresses +// from the network stack. (this is so you can provide a cached value if resolving many addrs) +func ResolveUnspecifiedAddress(resolve ma.Multiaddr, ifaceAddrs []ma.Multiaddr) ([]ma.Multiaddr, error) { + // split address into its components + split := ma.Split(resolve) + + // if first component (ip) is not unspecified, use it as is. + if !IsIPUnspecified(split[0]) { + return []ma.Multiaddr{resolve}, nil + } + + out := make([]ma.Multiaddr, 0, len(ifaceAddrs)) + for _, ia := range ifaceAddrs { + // must match the first protocol to be resolve. + if ia.Protocols()[0].Code != resolve.Protocols()[0].Code { + continue + } + + split[0] = ia + joined := ma.Join(split...) + out = append(out, joined) + } + if len(out) < 1 { + return nil, fmt.Errorf("failed to resolve: %s", resolve) + } + return out, nil +} + +// ResolveUnspecifiedAddresses expands unspecified ip addresses (/ip4/0.0.0.0, /ip6/::) to +// use the known local interfaces. +func ResolveUnspecifiedAddresses(unspecAddrs, ifaceAddrs []ma.Multiaddr) ([]ma.Multiaddr, error) { + // todo optimize: only fetch these if we have a "any" addr. + if len(ifaceAddrs) < 1 { + var err error + ifaceAddrs, err = interfaceAddresses() + if err != nil { + return nil, err + } + } + + var outputAddrs []ma.Multiaddr + for _, a := range unspecAddrs { + // unspecified? + resolved, err := ResolveUnspecifiedAddress(a, ifaceAddrs) + if err != nil { + continue // optimistic. if we can't resolve anything, we'll know at the bottom. + } + outputAddrs = append(outputAddrs, resolved...) + } + + if len(outputAddrs) < 1 { + return nil, fmt.Errorf("failed to specify addrs: %s", unspecAddrs) + } + return outputAddrs, nil +} + +// interfaceAddresses returns a list of addresses associated with local machine +// Note: we do not return link local addresses. IP loopback is ok, because we +// may be connecting to other nodes in the same machine. +func interfaceAddresses() ([]ma.Multiaddr, error) { + maddrs, err := InterfaceMultiaddrs() + if err != nil { + return nil, err + } + + var out []ma.Multiaddr + for _, a := range maddrs { + if !IsIpv6LinkLocal(a) { + continue + } + out = append(out, a) + } + return out, nil +} diff --git a/net/resolve_test.go b/net/resolve_test.go new file mode 100644 index 0000000..f2472f8 --- /dev/null +++ b/net/resolve_test.go @@ -0,0 +1,75 @@ +package manet + +import ( + "testing" + + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +func TestResolvingAddrs(t *testing.T) { + unspec := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/0.0.0.0/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip6/::/tcp/1234"), + newMultiaddr(t, "/ip6/::100/tcp/1234"), + } + + iface := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1"), + newMultiaddr(t, "/ip4/10.20.30.40"), + newMultiaddr(t, "/ip6/::1"), + newMultiaddr(t, "/ip6/::f"), + } + + spec := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip4/10.20.30.40/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip6/::f/tcp/1234"), + newMultiaddr(t, "/ip6/::100/tcp/1234"), + } + + actual, err := ResolveUnspecifiedAddresses(unspec, iface) + require.NoError(t, err) + require.Equal(t, actual, spec) + + ip4u := []ma.Multiaddr{newMultiaddr(t, "/ip4/0.0.0.0")} + ip4i := []ma.Multiaddr{newMultiaddr(t, "/ip4/1.2.3.4")} + + ip6u := []ma.Multiaddr{newMultiaddr(t, "/ip6/::")} + ip6i := []ma.Multiaddr{newMultiaddr(t, "/ip6/::1")} + + if _, err := ResolveUnspecifiedAddress(ip4u[0], ip6i); err == nil { + t.Fatal("should have failed") + } + if _, err := ResolveUnspecifiedAddress(ip6u[0], ip4i); err == nil { + t.Fatal("should have failed") + } + + if _, err := ResolveUnspecifiedAddresses(ip6u, ip4i); err == nil { + t.Fatal("should have failed") + } + if _, err := ResolveUnspecifiedAddresses(ip4u, ip6i); err == nil { + t.Fatal("should have failed") + } +} + +func TestAddrOverNonLocalIP(t *testing.T) { + bad := []ma.Multiaddr{ + newMultiaddr(t, "/ip6/fe80::1/tcp/1234"), // link local + newMultiaddr(t, "/ip6/fe80::100/tcp/1234"), // link local + } + good := []ma.Multiaddr{ + newMultiaddr(t, "/ip4/127.0.0.1/tcp/1234"), + newMultiaddr(t, "/ip6/::1/tcp/1234"), + newMultiaddr(t, "/ip4/1.2.3.4/udp/1234/utp"), + } + for _, addr := range bad { + require.Falsef(t, IsIpv6LinkLocal(addr), "%s is a link local addr", addr) + } + for _, addr := range good { + require.Truef(t, IsIpv6LinkLocal(addr), "%s is not a link local addr", addr) + } +}