Skip to content

Commit a1defdc

Browse files
author
mwnx
committed
Fully implement ip6zone support
- Add `FromIPAndZone` function (complementing the `FromIP` function). - Handle zones when parsing `net.Addr`s. - In `DialArgs`, return an error if an `ip6zone` is prefixed to an `ip4` or to another `ip6zone`. Note: I was not able to add a listen test (in TestListenAddrs) for `/zone/.../ip6/...` since there is no link-local address which is guaranteed to exist on all systems.
1 parent 3ce601c commit a1defdc

File tree

4 files changed

+153
-51
lines changed

4 files changed

+153
-51
lines changed

convert.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,38 +65,61 @@ func parseBasicNetMaddr(maddr ma.Multiaddr) (net.Addr, error) {
6565
return nil, fmt.Errorf("network not supported: %s", network)
6666
}
6767

68-
// FromIP converts a net.IP type to a Multiaddr.
69-
func FromIP(ip net.IP) (ma.Multiaddr, error) {
70-
var proto string
68+
func FromIPAndZone(ip net.IP, zone string) (ma.Multiaddr, error) {
7169
switch {
7270
case ip.To4() != nil:
73-
proto = "ip4"
71+
return ma.NewComponent("ip4", ip.String())
7472
case ip.To16() != nil:
75-
proto = "ip6"
73+
ip6, err := ma.NewComponent("ip6", ip.String())
74+
if err != nil {
75+
return nil, err
76+
}
77+
if zone == "" {
78+
return ip6, nil
79+
} else {
80+
zone, err := ma.NewComponent("ip6zone", zone)
81+
if err != nil {
82+
return nil, err
83+
}
84+
return zone.Encapsulate(ip6), nil
85+
}
7686
default:
7787
return nil, errIncorrectNetAddr
7888
}
79-
return ma.NewComponent(proto, ip.String())
89+
}
90+
91+
// FromIP converts a net.IP type to a Multiaddr.
92+
func FromIP(ip net.IP) (ma.Multiaddr, error) {
93+
return FromIPAndZone(ip, "")
8094
}
8195

8296
// DialArgs is a convenience function returning arguments for use in net.Dial
8397
func DialArgs(m ma.Multiaddr) (string, string, error) {
8498
var (
8599
zone, network, ip, port string
100+
err error
86101
)
87102

88103
ma.ForEach(m, func(c ma.Component) bool {
89104
switch network {
90105
case "":
91106
switch c.Protocol().Code {
92107
case ma.P_IP6ZONE:
108+
if zone != "" {
109+
err = fmt.Errorf("%s has multiple zones", m)
110+
return false
111+
}
93112
zone = c.Value()
94113
return true
95114
case ma.P_IP6:
96115
network = "ip6"
97116
ip = c.Value()
98117
return true
99118
case ma.P_IP4:
119+
if zone != "" {
120+
err = fmt.Errorf("%s has ip4 with zone", m)
121+
return false
122+
}
100123
network = "ip4"
101124
ip = c.Value()
102125
return true
@@ -125,6 +148,9 @@ func DialArgs(m ma.Multiaddr) (string, string, error) {
125148
// Done.
126149
return false
127150
})
151+
if err != nil {
152+
return "", "", err
153+
}
128154
switch network {
129155
case "ip6":
130156
if zone != "" {
@@ -152,7 +178,7 @@ func parseTCPNetAddr(a net.Addr) (ma.Multiaddr, error) {
152178
}
153179

154180
// Get IP Addr
155-
ipm, err := FromIP(ac.IP)
181+
ipm, err := FromIPAndZone(ac.IP, ac.Zone)
156182
if err != nil {
157183
return nil, errIncorrectNetAddr
158184
}
@@ -174,7 +200,7 @@ func parseUDPNetAddr(a net.Addr) (ma.Multiaddr, error) {
174200
}
175201

176202
// Get IP Addr
177-
ipm, err := FromIP(ac.IP)
203+
ipm, err := FromIPAndZone(ac.IP, ac.Zone)
178204
if err != nil {
179205
return nil, errIncorrectNetAddr
180206
}
@@ -194,7 +220,7 @@ func parseIPNetAddr(a net.Addr) (ma.Multiaddr, error) {
194220
if !ok {
195221
return nil, errIncorrectNetAddr
196222
}
197-
return FromIP(ac.IP)
223+
return FromIPAndZone(ac.IP, ac.Zone)
198224
}
199225

200226
func parseIPPlusNetAddr(a net.Addr) (ma.Multiaddr, error) {

convert_test.go

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,25 @@ func TestFromUDP(t *testing.T) {
9090

9191
func TestThinWaist(t *testing.T) {
9292
addrs := map[string]bool{
93-
"/ip4/127.0.0.1/udp/1234": true,
94-
"/ip4/127.0.0.1/tcp/1234": true,
95-
"/ip4/127.0.0.1/udp/1234/tcp/1234": true,
96-
"/ip4/127.0.0.1/tcp/12345/ip4/1.2.3.4": true,
97-
"/ip6/::1/tcp/80": true,
98-
"/ip6/::1/udp/80": true,
99-
"/ip6/::1": true,
100-
"/tcp/1234/ip4/1.2.3.4": false,
101-
"/tcp/1234": false,
102-
"/tcp/1234/udp/1234": false,
103-
"/ip4/1.2.3.4/ip4/2.3.4.5": true,
104-
"/ip6/::1/ip4/2.3.4.5": true,
93+
"/ip4/127.0.0.1/udp/1234": true,
94+
"/ip4/127.0.0.1/tcp/1234": true,
95+
"/ip4/127.0.0.1/udp/1234/tcp/1234": true,
96+
"/ip4/127.0.0.1/tcp/12345/ip4/1.2.3.4": true,
97+
"/ip6/::1/tcp/80": true,
98+
"/ip6/::1/udp/80": true,
99+
"/ip6/::1": true,
100+
"/ip6zone/hello/ip6/fe80::1/tcp/80": true,
101+
"/ip6zone/hello/ip6/fe80::1": true,
102+
"/tcp/1234/ip4/1.2.3.4": false,
103+
"/tcp/1234": false,
104+
"/tcp/1234/udp/1234": false,
105+
"/ip4/1.2.3.4/ip4/2.3.4.5": true,
106+
"/ip6/fe80::1/ip4/2.3.4.5": true,
107+
"/ip6zone/hello/ip6/fe80::1/ip4/2.3.4.5": true,
108+
109+
// Invalid ip6zone usage:
110+
"/ip6zone/hello": false,
111+
"/ip6zone/hello/ip4/1.1.1.1": false,
105112
}
106113

107114
for a, res := range addrs {
@@ -120,7 +127,7 @@ func TestDialArgs(t *testing.T) {
120127
test := func(e_maddr, e_nw, e_host string) {
121128
m, err := ma.NewMultiaddr(e_maddr)
122129
if err != nil {
123-
t.Fatal("failed to construct", "/ip4/127.0.0.1/udp/1234", e_maddr)
130+
t.Fatal("failed to construct", e_maddr)
124131
}
125132

126133
nw, host, err := DialArgs(m)
@@ -137,14 +144,28 @@ func TestDialArgs(t *testing.T) {
137144
}
138145
}
139146

147+
test_error := func(e_maddr string) {
148+
m, err := ma.NewMultiaddr(e_maddr)
149+
if err != nil {
150+
t.Fatal("failed to construct", e_maddr)
151+
}
152+
153+
_, _, err = DialArgs(m)
154+
if err == nil {
155+
t.Fatal("expected DialArgs to fail on", e_maddr)
156+
}
157+
}
158+
140159
test("/ip4/127.0.0.1/udp/1234", "udp4", "127.0.0.1:1234")
141160
test("/ip4/127.0.0.1/tcp/4321", "tcp4", "127.0.0.1:4321")
142161
test("/ip6/::1/udp/1234", "udp6", "[::1]:1234")
143162
test("/ip6/::1/tcp/4321", "tcp6", "[::1]:4321")
144-
test("/ip6/::1", "ip6", "::1") // Just an IP
145-
test("/ip4/1.2.3.4", "ip4", "1.2.3.4") // Just an IP
146-
test("/ip6zone/foo/ip6/::1/tcp/4321", "tcp6", "[::1%foo]:4321") // zone
147-
test("/ip6zone/foo/ip6/::1", "ip6", "::1%foo") // no TCP
148-
test("/ip6zone/foo/ip6/::1/ip6zone/bar", "ip6", "::1%foo") // IP over IP
149-
test("/ip6zone/foo/ip4/127.0.0.1/ip6zone/bar", "ip4", "127.0.0.1") // Skip zones in IP
163+
test("/ip6/::1", "ip6", "::1") // Just an IP
164+
test("/ip4/1.2.3.4", "ip4", "1.2.3.4") // Just an IP
165+
test("/ip6zone/foo/ip6/::1/tcp/4321", "tcp6", "[::1%foo]:4321") // zone
166+
test("/ip6zone/foo/ip6/::1/udp/4321", "udp6", "[::1%foo]:4321") // zone
167+
test("/ip6zone/foo/ip6/::1", "ip6", "::1%foo") // no TCP
168+
test_error("/ip6zone/foo/ip4/127.0.0.1") // IP4 doesn't take zone
169+
test("/ip6zone/foo/ip6/::1/ip6zone/bar", "ip6", "::1%foo") // IP over IP
170+
test_error("/ip6zone/foo/ip6zone/bar/ip6/::1") // Only one zone per IP6
150171
}

ip.go

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ var (
2727
// IsThinWaist returns whether a Multiaddr starts with "Thin Waist" Protocols.
2828
// This means: /{IP4, IP6}[/{TCP, UDP}]
2929
func IsThinWaist(m ma.Multiaddr) bool {
30+
m = zoneless(m)
31+
if m == nil {
32+
return false
33+
}
3034
p := m.Protocols()
3135

3236
// nothing? not even a waist.
@@ -52,9 +56,14 @@ func IsThinWaist(m ma.Multiaddr) bool {
5256
}
5357

5458
// IsIPLoopback returns whether a Multiaddr is a "Loopback" IP address
55-
// This means either /ip4/127.*.*.*, /ip6/::1, or /ip6/::ffff:127.*.*.*.*
59+
// This means either /ip4/127.*.*.*, /ip6/::1, or /ip6/::ffff:127.*.*.*.*,
60+
// or /ip6zone/<any value>/ip6/<one of the preceding ip6 values>
5661
func IsIPLoopback(m ma.Multiaddr) bool {
62+
m = zoneless(m)
5763
c, rest := ma.SplitFirst(m)
64+
if c == nil {
65+
return false
66+
}
5867
if rest != nil {
5968
// Not *just* an IPv4 addr
6069
return false
@@ -66,33 +75,47 @@ func IsIPLoopback(m ma.Multiaddr) bool {
6675
return false
6776
}
6877

69-
// IsIP6LinkLocal returns if a an IPv6 link-local multiaddress (with zero or
70-
// more leading zones). These addresses are non routable.
78+
// IsIP6LinkLocal returns whether a Multiaddr starts with an IPv6 link-local
79+
// multiaddress (with zero or one leading zone). These addresses are non
80+
// routable.
7181
func IsIP6LinkLocal(m ma.Multiaddr) bool {
72-
matched := false
73-
ma.ForEach(m, func(c ma.Component) bool {
74-
// Too much.
75-
if matched {
76-
matched = false
77-
return false
78-
}
79-
80-
switch c.Protocol().Code {
81-
case ma.P_IP6ZONE:
82-
return true
83-
case ma.P_IP6:
84-
ip := net.IP(c.RawValue())
85-
matched = ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast()
86-
return true
87-
default:
88-
return false
89-
}
90-
})
91-
return matched
82+
m = zoneless(m)
83+
c, _ := ma.SplitFirst(m)
84+
if c == nil || c.Protocol().Code != ma.P_IP6 {
85+
return false
86+
}
87+
ip := net.IP(c.RawValue())
88+
return ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast()
9289
}
9390

9491
// IsIPUnspecified returns whether a Multiaddr is am Unspecified IP address
9592
// This means either /ip4/0.0.0.0 or /ip6/::
9693
func IsIPUnspecified(m ma.Multiaddr) bool {
94+
m = zoneless(m)
95+
if m == nil {
96+
return false
97+
}
9798
return IP4Unspecified.Equal(m) || IP6Unspecified.Equal(m)
9899
}
100+
101+
// If m matches [zone,ip6,...], return [ip6,...]
102+
// else if m matches [], [zone], or [zone,...], return nil
103+
// else return m
104+
func zoneless(m ma.Multiaddr) ma.Multiaddr {
105+
head, tail := ma.SplitFirst(m)
106+
if head == nil {
107+
return nil
108+
}
109+
if head.Protocol().Code == ma.P_IP6ZONE {
110+
if tail == nil {
111+
return nil
112+
}
113+
tailhead, _ := ma.SplitFirst(tail)
114+
if tailhead.Protocol().Code != ma.P_IP6 {
115+
return nil
116+
}
117+
return tail
118+
} else {
119+
return m
120+
}
121+
}

net_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,20 @@ func TestListenAddrs(t *testing.T) {
175175
test("/ip4/0.0.0.0/tcp/4324", "", true)
176176
test("/ip4/0.0.0.0/udp/4325", "", false)
177177
test("/ip4/0.0.0.0/udp/4326/udt", "", false)
178+
178179
test("/ip6/::1/tcp/4324", "", true)
179180
test("/ip6/::1/udp/4325", "", false)
180181
test("/ip6/::1/udp/4326/udt", "", false)
181182
test("/ip6/::/tcp/4324", "", true)
182183
test("/ip6/::/udp/4325", "", false)
183184
test("/ip6/::/udp/4326/udt", "", false)
185+
186+
/* "An implementation should also support the concept of a "default"
187+
* zone for each scope. And, when supported, the index value zero
188+
* at each scope SHOULD be reserved to mean "use the default zone"."
189+
* -- rfc4007. So, this _should_ work everywhere(?). */
190+
test("/ip6zone/0/ip6/::1/tcp/4324", "/ip6/::1/tcp/4324", true)
191+
test("/ip6zone/0/ip6/::1/udp/4324", "", false)
184192
}
185193

186194
func TestListenAndDial(t *testing.T) {
@@ -345,6 +353,22 @@ func TestIPLoopback(t *testing.T) {
345353
if IsIPLoopback(newMultiaddr(t, "/ip6/::fffa:127.99.3.2")) {
346354
t.Error("IsIPLoopback false positive (/ip6/::fffa:127.99.3.2)")
347355
}
356+
357+
if !IsIPLoopback(newMultiaddr(t, "/ip6zone/0/ip6/::1")) {
358+
t.Error("IsIPLoopback failed (/ip6zone/0/ip6/::1)")
359+
}
360+
361+
if !IsIPLoopback(newMultiaddr(t, "/ip6zone/xxx/ip6/::1")) {
362+
t.Error("IsIPLoopback failed (/ip6zone/xxx/ip6/::1)")
363+
}
364+
365+
if IsIPLoopback(newMultiaddr(t, "/ip6zone/0/ip6/::1/tcp/3333")) {
366+
t.Error("IsIPLoopback failed (/ip6zone/0/ip6/::1/tcp/3333)")
367+
}
368+
369+
if IsIPLoopback(newMultiaddr(t, "/ip6zone/0/ip6/1::1")) {
370+
t.Errorf("IsIPLoopback false positive (/ip6zone/0/ip6/1::1)")
371+
}
348372
}
349373

350374
func TestIPUnspecified(t *testing.T) {
@@ -363,6 +387,10 @@ func TestIPUnspecified(t *testing.T) {
363387
if !IsIPUnspecified(IP6Unspecified) {
364388
t.Error("IsIPUnspecified failed (IP6Unspecified)")
365389
}
390+
391+
if !IsIPUnspecified(newMultiaddr(t, "/ip6zone/xxx/ip6/::")) {
392+
t.Error("IsIPUnspecified failed (/ip6zone/xxx/ip6/::)")
393+
}
366394
}
367395

368396
func TestIP6LinkLocal(t *testing.T) {
@@ -373,6 +401,10 @@ func TestIP6LinkLocal(t *testing.T) {
373401
t.Errorf("IsIP6LinkLocal failed (%s != %v)", m, isLinkLocal)
374402
}
375403
}
404+
405+
if !IsIP6LinkLocal(newMultiaddr(t, "/ip6zone/hello/ip6/fe80::9999")) {
406+
t.Error("IsIP6LinkLocal failed (/ip6/fe80::9999)")
407+
}
376408
}
377409

378410
func TestConvertNetAddr(t *testing.T) {

0 commit comments

Comments
 (0)