Unverified Commit 86e5d145 authored by vyzo's avatar vyzo Committed by GitHub

Merge pull request #42 from mwnx/ip6z

Add "ip6%" multiaddr support (IPv6 with zone ID)
parents 3ce601ca a1defdc5
......@@ -65,24 +65,39 @@ func parseBasicNetMaddr(maddr ma.Multiaddr) (net.Addr, error) {
return nil, fmt.Errorf("network not supported: %s", network)
}
// FromIP converts a net.IP type to a Multiaddr.
func FromIP(ip net.IP) (ma.Multiaddr, error) {
var proto string
func FromIPAndZone(ip net.IP, zone string) (ma.Multiaddr, error) {
switch {
case ip.To4() != nil:
proto = "ip4"
return ma.NewComponent("ip4", ip.String())
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:
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
func DialArgs(m ma.Multiaddr) (string, string, error) {
var (
zone, network, ip, port string
err error
)
ma.ForEach(m, func(c ma.Component) bool {
......@@ -90,6 +105,10 @@ func DialArgs(m ma.Multiaddr) (string, string, error) {
case "":
switch c.Protocol().Code {
case ma.P_IP6ZONE:
if zone != "" {
err = fmt.Errorf("%s has multiple zones", m)
return false
}
zone = c.Value()
return true
case ma.P_IP6:
......@@ -97,6 +116,10 @@ func DialArgs(m ma.Multiaddr) (string, string, error) {
ip = c.Value()
return true
case ma.P_IP4:
if zone != "" {
err = fmt.Errorf("%s has ip4 with zone", m)
return false
}
network = "ip4"
ip = c.Value()
return true
......@@ -125,6 +148,9 @@ func DialArgs(m ma.Multiaddr) (string, string, error) {
// Done.
return false
})
if err != nil {
return "", "", err
}
switch network {
case "ip6":
if zone != "" {
......@@ -152,7 +178,7 @@ func parseTCPNetAddr(a net.Addr) (ma.Multiaddr, error) {
}
// Get IP Addr
ipm, err := FromIP(ac.IP)
ipm, err := FromIPAndZone(ac.IP, ac.Zone)
if err != nil {
return nil, errIncorrectNetAddr
}
......@@ -174,7 +200,7 @@ func parseUDPNetAddr(a net.Addr) (ma.Multiaddr, error) {
}
// Get IP Addr
ipm, err := FromIP(ac.IP)
ipm, err := FromIPAndZone(ac.IP, ac.Zone)
if err != nil {
return nil, errIncorrectNetAddr
}
......@@ -194,7 +220,7 @@ func parseIPNetAddr(a net.Addr) (ma.Multiaddr, error) {
if !ok {
return nil, errIncorrectNetAddr
}
return FromIP(ac.IP)
return FromIPAndZone(ac.IP, ac.Zone)
}
func parseIPPlusNetAddr(a net.Addr) (ma.Multiaddr, error) {
......
......@@ -90,18 +90,25 @@ func TestFromUDP(t *testing.T) {
func TestThinWaist(t *testing.T) {
addrs := map[string]bool{
"/ip4/127.0.0.1/udp/1234": true,
"/ip4/127.0.0.1/tcp/1234": true,
"/ip4/127.0.0.1/udp/1234/tcp/1234": true,
"/ip4/127.0.0.1/tcp/12345/ip4/1.2.3.4": true,
"/ip6/::1/tcp/80": true,
"/ip6/::1/udp/80": true,
"/ip6/::1": true,
"/tcp/1234/ip4/1.2.3.4": false,
"/tcp/1234": false,
"/tcp/1234/udp/1234": false,
"/ip4/1.2.3.4/ip4/2.3.4.5": true,
"/ip6/::1/ip4/2.3.4.5": true,
"/ip4/127.0.0.1/udp/1234": true,
"/ip4/127.0.0.1/tcp/1234": true,
"/ip4/127.0.0.1/udp/1234/tcp/1234": true,
"/ip4/127.0.0.1/tcp/12345/ip4/1.2.3.4": true,
"/ip6/::1/tcp/80": true,
"/ip6/::1/udp/80": 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": false,
"/tcp/1234/udp/1234": false,
"/ip4/1.2.3.4/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 {
......@@ -120,7 +127,7 @@ func TestDialArgs(t *testing.T) {
test := func(e_maddr, e_nw, e_host string) {
m, err := ma.NewMultiaddr(e_maddr)
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)
......@@ -137,14 +144,28 @@ 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/tcp/4321", "tcp4", "127.0.0.1:4321")
test("/ip6/::1/udp/1234", "udp6", "[::1]:1234")
test("/ip6/::1/tcp/4321", "tcp6", "[::1]:4321")
test("/ip6/::1", "ip6", "::1") // 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", "ip6", "::1%foo") // no TCP
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("/ip6/::1", "ip6", "::1") // 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/udp/4321", "udp6", "[::1%foo]:4321") // zone
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_error("/ip6zone/foo/ip6zone/bar/ip6/::1") // Only one zone per IP6
}
......@@ -27,6 +27,10 @@ var (
// IsThinWaist returns whether a Multiaddr starts with "Thin Waist" Protocols.
// This means: /{IP4, IP6}[/{TCP, UDP}]
func IsThinWaist(m ma.Multiaddr) bool {
m = zoneless(m)
if m == nil {
return false
}
p := m.Protocols()
// 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
// 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 {
m = zoneless(m)
c, rest := ma.SplitFirst(m)
if c == nil {
return false
}
if rest != nil {
// Not *just* an IPv4 addr
return false
......@@ -66,33 +75,47 @@ func IsIPLoopback(m ma.Multiaddr) bool {
return false
}
// IsIP6LinkLocal returns if a an IPv6 link-local multiaddress (with zero or
// more leading zones). These addresses are non routable.
// IsIP6LinkLocal returns whether a Multiaddr starts with an IPv6 link-local
// multiaddress (with zero or one leading zone). These addresses are non
// routable.
func IsIP6LinkLocal(m ma.Multiaddr) bool {
matched := false
ma.ForEach(m, func(c ma.Component) bool {
// Too much.
if matched {
matched = false
return false
}
switch c.Protocol().Code {
case ma.P_IP6ZONE:
return true
case ma.P_IP6:
ip := net.IP(c.RawValue())
matched = ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast()
return true
default:
return false
}
})
return matched
m = zoneless(m)
c, _ := ma.SplitFirst(m)
if c == nil || c.Protocol().Code != ma.P_IP6 {
return false
}
ip := net.IP(c.RawValue())
return ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast()
}
// IsIPUnspecified returns whether a Multiaddr is am Unspecified IP address
// This means either /ip4/0.0.0.0 or /ip6/::
func IsIPUnspecified(m ma.Multiaddr) bool {
m = zoneless(m)
if m == nil {
return false
}
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
}
}
......@@ -175,12 +175,20 @@ func TestListenAddrs(t *testing.T) {
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/4326/udt", "", false)
test("/ip6/::1/tcp/4324", "", true)
test("/ip6/::1/udp/4325", "", false)
test("/ip6/::1/udp/4326/udt", "", false)
test("/ip6/::/tcp/4324", "", true)
test("/ip6/::/udp/4325", "", 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) {
......@@ -345,6 +353,22 @@ func TestIPLoopback(t *testing.T) {
if IsIPLoopback(newMultiaddr(t, "/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) {
......@@ -363,6 +387,10 @@ func TestIPUnspecified(t *testing.T) {
if !IsIPUnspecified(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) {
......@@ -373,6 +401,10 @@ func TestIP6LinkLocal(t *testing.T) {
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) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment