Merge pull request #42 from mwnx/ip6z

Add "ip6%" multiaddr support (IPv6 with zone ID)
This commit is contained in:
vyzo 2018-10-21 17:56:03 +03:00 committed by GitHub
commit 86e5d145c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 153 additions and 51 deletions

View File

@ -65,24 +65,39 @@ func parseBasicNetMaddr(maddr ma.Multiaddr) (net.Addr, error) {
return nil, fmt.Errorf("network not supported: %s", network) return nil, fmt.Errorf("network not supported: %s", network)
} }
// FromIP converts a net.IP type to a Multiaddr. func FromIPAndZone(ip net.IP, zone string) (ma.Multiaddr, error) {
func FromIP(ip net.IP) (ma.Multiaddr, error) {
var proto string
switch { switch {
case ip.To4() != nil: case ip.To4() != nil:
proto = "ip4" return ma.NewComponent("ip4", ip.String())
case ip.To16() != nil: case ip.To16() != nil:
proto = "ip6" ip6, err := ma.NewComponent("ip6", ip.String())
if err != nil {
return nil, err
}
if zone == "" {
return ip6, nil
} else {
zone, err := ma.NewComponent("ip6zone", zone)
if err != nil {
return nil, err
}
return zone.Encapsulate(ip6), nil
}
default: default:
return nil, errIncorrectNetAddr return nil, errIncorrectNetAddr
} }
return ma.NewComponent(proto, ip.String()) }
// FromIP converts a net.IP type to a Multiaddr.
func FromIP(ip net.IP) (ma.Multiaddr, error) {
return FromIPAndZone(ip, "")
} }
// DialArgs is a convenience function returning arguments for use in net.Dial // DialArgs is a convenience function returning arguments for use in net.Dial
func DialArgs(m ma.Multiaddr) (string, string, error) { func DialArgs(m ma.Multiaddr) (string, string, error) {
var ( var (
zone, network, ip, port string zone, network, ip, port string
err error
) )
ma.ForEach(m, func(c ma.Component) bool { ma.ForEach(m, func(c ma.Component) bool {
@ -90,6 +105,10 @@ func DialArgs(m ma.Multiaddr) (string, string, error) {
case "": case "":
switch c.Protocol().Code { switch c.Protocol().Code {
case ma.P_IP6ZONE: case ma.P_IP6ZONE:
if zone != "" {
err = fmt.Errorf("%s has multiple zones", m)
return false
}
zone = c.Value() zone = c.Value()
return true return true
case ma.P_IP6: case ma.P_IP6:
@ -97,6 +116,10 @@ func DialArgs(m ma.Multiaddr) (string, string, error) {
ip = c.Value() ip = c.Value()
return true return true
case ma.P_IP4: case ma.P_IP4:
if zone != "" {
err = fmt.Errorf("%s has ip4 with zone", m)
return false
}
network = "ip4" network = "ip4"
ip = c.Value() ip = c.Value()
return true return true
@ -125,6 +148,9 @@ func DialArgs(m ma.Multiaddr) (string, string, error) {
// Done. // Done.
return false return false
}) })
if err != nil {
return "", "", err
}
switch network { switch network {
case "ip6": case "ip6":
if zone != "" { if zone != "" {
@ -152,7 +178,7 @@ func parseTCPNetAddr(a net.Addr) (ma.Multiaddr, error) {
} }
// Get IP Addr // Get IP Addr
ipm, err := FromIP(ac.IP) ipm, err := FromIPAndZone(ac.IP, ac.Zone)
if err != nil { if err != nil {
return nil, errIncorrectNetAddr return nil, errIncorrectNetAddr
} }
@ -174,7 +200,7 @@ func parseUDPNetAddr(a net.Addr) (ma.Multiaddr, error) {
} }
// Get IP Addr // Get IP Addr
ipm, err := FromIP(ac.IP) ipm, err := FromIPAndZone(ac.IP, ac.Zone)
if err != nil { if err != nil {
return nil, errIncorrectNetAddr return nil, errIncorrectNetAddr
} }
@ -194,7 +220,7 @@ func parseIPNetAddr(a net.Addr) (ma.Multiaddr, error) {
if !ok { if !ok {
return nil, errIncorrectNetAddr return nil, errIncorrectNetAddr
} }
return FromIP(ac.IP) return FromIPAndZone(ac.IP, ac.Zone)
} }
func parseIPPlusNetAddr(a net.Addr) (ma.Multiaddr, error) { func parseIPPlusNetAddr(a net.Addr) (ma.Multiaddr, error) {

View File

@ -97,11 +97,18 @@ func TestThinWaist(t *testing.T) {
"/ip6/::1/tcp/80": true, "/ip6/::1/tcp/80": true,
"/ip6/::1/udp/80": true, "/ip6/::1/udp/80": true,
"/ip6/::1": true, "/ip6/::1": true,
"/ip6zone/hello/ip6/fe80::1/tcp/80": true,
"/ip6zone/hello/ip6/fe80::1": true,
"/tcp/1234/ip4/1.2.3.4": false, "/tcp/1234/ip4/1.2.3.4": false,
"/tcp/1234": false, "/tcp/1234": false,
"/tcp/1234/udp/1234": false, "/tcp/1234/udp/1234": false,
"/ip4/1.2.3.4/ip4/2.3.4.5": true, "/ip4/1.2.3.4/ip4/2.3.4.5": true,
"/ip6/::1/ip4/2.3.4.5": true, "/ip6/fe80::1/ip4/2.3.4.5": true,
"/ip6zone/hello/ip6/fe80::1/ip4/2.3.4.5": true,
// Invalid ip6zone usage:
"/ip6zone/hello": false,
"/ip6zone/hello/ip4/1.1.1.1": false,
} }
for a, res := range addrs { for a, res := range addrs {
@ -120,7 +127,7 @@ func TestDialArgs(t *testing.T) {
test := func(e_maddr, e_nw, e_host string) { test := func(e_maddr, e_nw, e_host string) {
m, err := ma.NewMultiaddr(e_maddr) m, err := ma.NewMultiaddr(e_maddr)
if err != nil { if err != nil {
t.Fatal("failed to construct", "/ip4/127.0.0.1/udp/1234", e_maddr) t.Fatal("failed to construct", e_maddr)
} }
nw, host, err := DialArgs(m) nw, host, err := DialArgs(m)
@ -137,6 +144,18 @@ func TestDialArgs(t *testing.T) {
} }
} }
test_error := func(e_maddr string) {
m, err := ma.NewMultiaddr(e_maddr)
if err != nil {
t.Fatal("failed to construct", e_maddr)
}
_, _, err = DialArgs(m)
if err == nil {
t.Fatal("expected DialArgs to fail on", e_maddr)
}
}
test("/ip4/127.0.0.1/udp/1234", "udp4", "127.0.0.1:1234") test("/ip4/127.0.0.1/udp/1234", "udp4", "127.0.0.1:1234")
test("/ip4/127.0.0.1/tcp/4321", "tcp4", "127.0.0.1:4321") test("/ip4/127.0.0.1/tcp/4321", "tcp4", "127.0.0.1:4321")
test("/ip6/::1/udp/1234", "udp6", "[::1]:1234") test("/ip6/::1/udp/1234", "udp6", "[::1]:1234")
@ -144,7 +163,9 @@ func TestDialArgs(t *testing.T) {
test("/ip6/::1", "ip6", "::1") // Just an IP test("/ip6/::1", "ip6", "::1") // Just an IP
test("/ip4/1.2.3.4", "ip4", "1.2.3.4") // Just an IP test("/ip4/1.2.3.4", "ip4", "1.2.3.4") // Just an IP
test("/ip6zone/foo/ip6/::1/tcp/4321", "tcp6", "[::1%foo]:4321") // zone test("/ip6zone/foo/ip6/::1/tcp/4321", "tcp6", "[::1%foo]:4321") // zone
test("/ip6zone/foo/ip6/::1/udp/4321", "udp6", "[::1%foo]:4321") // zone
test("/ip6zone/foo/ip6/::1", "ip6", "::1%foo") // no TCP test("/ip6zone/foo/ip6/::1", "ip6", "::1%foo") // no TCP
test_error("/ip6zone/foo/ip4/127.0.0.1") // IP4 doesn't take zone
test("/ip6zone/foo/ip6/::1/ip6zone/bar", "ip6", "::1%foo") // IP over IP test("/ip6zone/foo/ip6/::1/ip6zone/bar", "ip6", "::1%foo") // IP over IP
test("/ip6zone/foo/ip4/127.0.0.1/ip6zone/bar", "ip4", "127.0.0.1") // Skip zones in IP test_error("/ip6zone/foo/ip6zone/bar/ip6/::1") // Only one zone per IP6
} }

63
ip.go
View File

@ -27,6 +27,10 @@ var (
// IsThinWaist returns whether a Multiaddr starts with "Thin Waist" Protocols. // IsThinWaist returns whether a Multiaddr starts with "Thin Waist" Protocols.
// This means: /{IP4, IP6}[/{TCP, UDP}] // This means: /{IP4, IP6}[/{TCP, UDP}]
func IsThinWaist(m ma.Multiaddr) bool { func IsThinWaist(m ma.Multiaddr) bool {
m = zoneless(m)
if m == nil {
return false
}
p := m.Protocols() p := m.Protocols()
// nothing? not even a waist. // nothing? not even a waist.
@ -52,9 +56,14 @@ func IsThinWaist(m ma.Multiaddr) bool {
} }
// IsIPLoopback returns whether a Multiaddr is a "Loopback" IP address // IsIPLoopback returns whether a Multiaddr is a "Loopback" IP address
// This means either /ip4/127.*.*.*, /ip6/::1, or /ip6/::ffff:127.*.*.*.* // This means either /ip4/127.*.*.*, /ip6/::1, or /ip6/::ffff:127.*.*.*.*,
// or /ip6zone/<any value>/ip6/<one of the preceding ip6 values>
func IsIPLoopback(m ma.Multiaddr) bool { func IsIPLoopback(m ma.Multiaddr) bool {
m = zoneless(m)
c, rest := ma.SplitFirst(m) c, rest := ma.SplitFirst(m)
if c == nil {
return false
}
if rest != nil { if rest != nil {
// Not *just* an IPv4 addr // Not *just* an IPv4 addr
return false return false
@ -66,33 +75,47 @@ func IsIPLoopback(m ma.Multiaddr) bool {
return false return false
} }
// IsIP6LinkLocal returns if a an IPv6 link-local multiaddress (with zero or // IsIP6LinkLocal returns whether a Multiaddr starts with an IPv6 link-local
// more leading zones). These addresses are non routable. // multiaddress (with zero or one leading zone). These addresses are non
// routable.
func IsIP6LinkLocal(m ma.Multiaddr) bool { func IsIP6LinkLocal(m ma.Multiaddr) bool {
matched := false m = zoneless(m)
ma.ForEach(m, func(c ma.Component) bool { c, _ := ma.SplitFirst(m)
// Too much. if c == nil || c.Protocol().Code != ma.P_IP6 {
if matched {
matched = false
return false return false
} }
switch c.Protocol().Code {
case ma.P_IP6ZONE:
return true
case ma.P_IP6:
ip := net.IP(c.RawValue()) ip := net.IP(c.RawValue())
matched = ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() return ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast()
return true
default:
return false
}
})
return matched
} }
// IsIPUnspecified returns whether a Multiaddr is am Unspecified IP address // IsIPUnspecified returns whether a Multiaddr is am Unspecified IP address
// This means either /ip4/0.0.0.0 or /ip6/:: // This means either /ip4/0.0.0.0 or /ip6/::
func IsIPUnspecified(m ma.Multiaddr) bool { func IsIPUnspecified(m ma.Multiaddr) bool {
m = zoneless(m)
if m == nil {
return false
}
return IP4Unspecified.Equal(m) || IP6Unspecified.Equal(m) return IP4Unspecified.Equal(m) || IP6Unspecified.Equal(m)
} }
// If m matches [zone,ip6,...], return [ip6,...]
// else if m matches [], [zone], or [zone,...], return nil
// else return m
func zoneless(m ma.Multiaddr) ma.Multiaddr {
head, tail := ma.SplitFirst(m)
if head == nil {
return nil
}
if head.Protocol().Code == ma.P_IP6ZONE {
if tail == nil {
return nil
}
tailhead, _ := ma.SplitFirst(tail)
if tailhead.Protocol().Code != ma.P_IP6 {
return nil
}
return tail
} else {
return m
}
}

View File

@ -175,12 +175,20 @@ func TestListenAddrs(t *testing.T) {
test("/ip4/0.0.0.0/tcp/4324", "", true) test("/ip4/0.0.0.0/tcp/4324", "", true)
test("/ip4/0.0.0.0/udp/4325", "", false) test("/ip4/0.0.0.0/udp/4325", "", false)
test("/ip4/0.0.0.0/udp/4326/udt", "", false) test("/ip4/0.0.0.0/udp/4326/udt", "", false)
test("/ip6/::1/tcp/4324", "", true) test("/ip6/::1/tcp/4324", "", true)
test("/ip6/::1/udp/4325", "", false) test("/ip6/::1/udp/4325", "", false)
test("/ip6/::1/udp/4326/udt", "", false) test("/ip6/::1/udp/4326/udt", "", false)
test("/ip6/::/tcp/4324", "", true) test("/ip6/::/tcp/4324", "", true)
test("/ip6/::/udp/4325", "", false) test("/ip6/::/udp/4325", "", false)
test("/ip6/::/udp/4326/udt", "", false) test("/ip6/::/udp/4326/udt", "", false)
/* "An implementation should also support the concept of a "default"
* zone for each scope. And, when supported, the index value zero
* at each scope SHOULD be reserved to mean "use the default zone"."
* -- rfc4007. So, this _should_ work everywhere(?). */
test("/ip6zone/0/ip6/::1/tcp/4324", "/ip6/::1/tcp/4324", true)
test("/ip6zone/0/ip6/::1/udp/4324", "", false)
} }
func TestListenAndDial(t *testing.T) { func TestListenAndDial(t *testing.T) {
@ -345,6 +353,22 @@ func TestIPLoopback(t *testing.T) {
if IsIPLoopback(newMultiaddr(t, "/ip6/::fffa:127.99.3.2")) { if IsIPLoopback(newMultiaddr(t, "/ip6/::fffa:127.99.3.2")) {
t.Error("IsIPLoopback false positive (/ip6/::fffa:127.99.3.2)") t.Error("IsIPLoopback false positive (/ip6/::fffa:127.99.3.2)")
} }
if !IsIPLoopback(newMultiaddr(t, "/ip6zone/0/ip6/::1")) {
t.Error("IsIPLoopback failed (/ip6zone/0/ip6/::1)")
}
if !IsIPLoopback(newMultiaddr(t, "/ip6zone/xxx/ip6/::1")) {
t.Error("IsIPLoopback failed (/ip6zone/xxx/ip6/::1)")
}
if IsIPLoopback(newMultiaddr(t, "/ip6zone/0/ip6/::1/tcp/3333")) {
t.Error("IsIPLoopback failed (/ip6zone/0/ip6/::1/tcp/3333)")
}
if IsIPLoopback(newMultiaddr(t, "/ip6zone/0/ip6/1::1")) {
t.Errorf("IsIPLoopback false positive (/ip6zone/0/ip6/1::1)")
}
} }
func TestIPUnspecified(t *testing.T) { func TestIPUnspecified(t *testing.T) {
@ -363,6 +387,10 @@ func TestIPUnspecified(t *testing.T) {
if !IsIPUnspecified(IP6Unspecified) { if !IsIPUnspecified(IP6Unspecified) {
t.Error("IsIPUnspecified failed (IP6Unspecified)") t.Error("IsIPUnspecified failed (IP6Unspecified)")
} }
if !IsIPUnspecified(newMultiaddr(t, "/ip6zone/xxx/ip6/::")) {
t.Error("IsIPUnspecified failed (/ip6zone/xxx/ip6/::)")
}
} }
func TestIP6LinkLocal(t *testing.T) { func TestIP6LinkLocal(t *testing.T) {
@ -373,6 +401,10 @@ func TestIP6LinkLocal(t *testing.T) {
t.Errorf("IsIP6LinkLocal failed (%s != %v)", m, isLinkLocal) t.Errorf("IsIP6LinkLocal failed (%s != %v)", m, isLinkLocal)
} }
} }
if !IsIP6LinkLocal(newMultiaddr(t, "/ip6zone/hello/ip6/fe80::9999")) {
t.Error("IsIP6LinkLocal failed (/ip6/fe80::9999)")
}
} }
func TestConvertNetAddr(t *testing.T) { func TestConvertNetAddr(t *testing.T) {