Skip to content

Commit 8cf5475

Browse files
committed
Allow binding on address with arbitrary zone
Signed-off-by: Xu Liu <[email protected]>
1 parent 6da6235 commit 8cf5475

File tree

3 files changed

+62
-25
lines changed

3 files changed

+62
-25
lines changed

addr.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"net"
66
"net/netip"
7+
"strconv"
78
)
89

910
// An Addr is an IPv6 unicast address.
@@ -18,14 +19,16 @@ const (
1819
)
1920

2021
// chooseAddr selects an Addr from the interface based on the specified Addr type.
21-
func chooseAddr(addrs []net.Addr, zone string, addr Addr) (netip.Addr, error) {
22+
func chooseAddr(addrs []net.Addr, zone string, zoneIndex int, addr Addr) (netip.Addr, error) {
2223
// Does the caller want an unspecified address?
2324
if addr == Unspecified {
2425
return netip.IPv6Unspecified().WithZone(zone), nil
2526
}
2627

2728
// Select an IPv6 address from the interface's addresses.
2829
var match func(ip netip.Addr) bool
30+
var preferred netip.Addr
31+
var err error
2932
switch addr {
3033
case LinkLocal:
3134
match = (netip.Addr).IsLinkLocalUnicast
@@ -38,27 +41,30 @@ func chooseAddr(addrs []net.Addr, zone string, addr Addr) (netip.Addr, error) {
3841
}
3942
default:
4043
// Special case: try to match Addr as a literal IPv6 address.
41-
ip, err := netip.ParseAddr(string(addr))
44+
preferred, err = netip.ParseAddr(string(addr))
4245
if err != nil {
4346
return netip.Addr{}, fmt.Errorf("ndp: invalid IPv6 address: %q", addr)
4447
}
45-
46-
if err := checkIPv6(ip); err != nil {
48+
if err := checkIPv6(preferred); err != nil {
4749
return netip.Addr{}, err
4850
}
49-
5051
match = func(check netip.Addr) bool {
51-
return ip == check
52+
return preferred == check ||
53+
preferred == check.WithZone(zone) ||
54+
preferred == check.WithZone(strconv.Itoa(zoneIndex))
5255
}
5356
}
5457

55-
return findAddr(addrs, addr, zone, match)
58+
found := findAddr(addrs, preferred, zone, match)
59+
if !found.IsValid() {
60+
return netip.Addr{}, fmt.Errorf("ndp: no valid IPv6 address found for %q", addr)
61+
}
62+
return found, nil
5663
}
5764

5865
// findAddr searches for a valid IPv6 address in the slice of net.Addr that
59-
// matches the input function. If none is found, the IPv6 unspecified address
60-
// "::" is returned.
61-
func findAddr(addrs []net.Addr, addr Addr, zone string, match func(ip netip.Addr) bool) (netip.Addr, error) {
66+
// matches the input function. If none is found, it returns an invalid netip.Addr.
67+
func findAddr(addrs []net.Addr, preferred netip.Addr, zone string, match func(ip netip.Addr) bool) netip.Addr {
6268
for _, a := range addrs {
6369
ipn, ok := a.(*net.IPNet)
6470
if !ok {
@@ -75,11 +81,18 @@ func findAddr(addrs []net.Addr, addr Addr, zone string, match func(ip netip.Addr
7581

7682
// From here on, we can assume that only IPv6 addresses are
7783
// being checked.
78-
if match(ip) {
79-
return ip.WithZone(zone), nil
84+
if !match(ip) {
85+
continue
86+
}
87+
// If a preferred address is set, use it directly.
88+
if preferred.IsValid() {
89+
if preferred.Zone() == "" {
90+
return preferred.WithZone(zone)
91+
}
92+
return preferred
8093
}
94+
return ip.WithZone(zone)
8195
}
8296

83-
// No matching address on this interface.
84-
return netip.Addr{}, fmt.Errorf("ndp: address %q not found on interface %q", addr, zone)
97+
return netip.Addr{}
8598
}

addr_test.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ package ndp
33
import (
44
"net"
55
"net/netip"
6+
"strconv"
67
"testing"
78

89
"github.com/google/go-cmp/cmp"
910
)
1011

1112
func Test_chooseAddr(t *testing.T) {
1213
// Assumed zone for all tests.
13-
const zone = "eth0"
14+
zone := "eth0"
15+
zoneId := 1
1416

1517
var (
1618
ip4 = net.IPv4(192, 168, 1, 1).To4()
@@ -60,43 +62,67 @@ func Test_chooseAddr(t *testing.T) {
6062
},
6163
{
6264
name: "ok, unspecified",
63-
ip: netip.IPv6Unspecified(),
65+
ip: netip.IPv6Unspecified().WithZone(zone),
6466
addr: Unspecified,
6567
ok: true,
6668
},
6769
{
6870
name: "ok, GUA",
6971
addrs: addrs,
70-
ip: netip.MustParseAddr("2001:db8::1"),
72+
ip: netip.MustParseAddr("2001:db8::1").WithZone(zone),
7173
addr: Global,
7274
ok: true,
7375
},
7476
{
7577
name: "ok, ULA",
7678
addrs: addrs,
77-
ip: netip.MustParseAddr("fc00::1"),
79+
ip: netip.MustParseAddr("fc00::1").WithZone(zone),
7880
addr: UniqueLocal,
7981
ok: true,
8082
},
8183
{
8284
name: "ok, LLA",
8385
addrs: addrs,
84-
ip: netip.MustParseAddr("fe80::1"),
86+
ip: netip.MustParseAddr("fe80::1").WithZone(zone),
8587
addr: LinkLocal,
8688
ok: true,
8789
},
8890
{
8991
name: "ok, arbitrary",
9092
addrs: addrs,
91-
ip: netip.MustParseAddr("2001:db8::1000"),
93+
ip: netip.MustParseAddr("2001:db8::1000").WithZone(zone),
9294
addr: Addr(ip6.String()),
9395
ok: true,
9496
},
97+
{
98+
name: "ok, arbitrary with zone",
99+
addrs: addrs,
100+
ip: netip.MustParseAddr("2001:db8::1000").WithZone(zone),
101+
addr: Addr("2001:db8::1000%eth0"),
102+
ok: true,
103+
},
104+
{
105+
name: "arbitrary with mismatched zone",
106+
addrs: addrs,
107+
addr: Addr("2001:db8::1000%eth1"),
108+
},
109+
{
110+
name: "ok, arbitrary with zone id",
111+
addrs: addrs,
112+
ip: netip.MustParseAddr("2001:db8::1000").WithZone(strconv.Itoa(zoneId)),
113+
addr: Addr("2001:db8::1000%1"),
114+
ok: true,
115+
},
116+
{
117+
name: "arbitrary with mismatched zone id",
118+
addrs: addrs,
119+
addr: Addr("2001:db8::1000%2"),
120+
},
95121
}
96122

97123
for _, tt := range tests {
98124
t.Run(tt.name, func(t *testing.T) {
99-
ipa, err := chooseAddr(tt.addrs, zone, tt.addr)
125+
ipa, err := chooseAddr(tt.addrs, zone, zoneId, tt.addr)
100126

101127
if err != nil && tt.ok {
102128
t.Fatalf("unexpected error: %v", err)
@@ -108,9 +134,7 @@ func Test_chooseAddr(t *testing.T) {
108134
t.Logf("OK error: %v", err)
109135
return
110136
}
111-
112-
ttipa := tt.ip.WithZone(zone)
113-
if diff := cmp.Diff(ttipa, ipa, cmp.Comparer(addrEqual)); diff != "" {
137+
if diff := cmp.Diff(tt.ip, ipa, cmp.Comparer(addrEqual)); diff != "" {
114138
t.Fatalf("unexpected IPv6 address (-want +got):\n%s", diff)
115139
}
116140
})

conn.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func Listen(ifi *net.Interface, addr Addr) (*Conn, netip.Addr, error) {
4141
return nil, netip.Addr{}, err
4242
}
4343

44-
ip, err := chooseAddr(addrs, ifi.Name, addr)
44+
ip, err := chooseAddr(addrs, ifi.Name, ifi.Index, addr)
4545
if err != nil {
4646
return nil, netip.Addr{}, err
4747
}

0 commit comments

Comments
 (0)