Skip to content

Commit c3dc69e

Browse files
committed
Add net.InterfaceAddrs() conversion functions to netutils
net.InterfaceAddrs() has a weird and useless return value that various pieces of code parse in variously correct ways. Add functions to reliably convert its return values to net.IP or netip.Addr.
1 parent 6e2efc9 commit c3dc69e

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

net/v2/convert.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package net
1919
import (
2020
"net"
2121
"net/netip"
22+
"strings"
2223
)
2324

2425
// AddrFromIP converts a net.IP to a netip.Addr. Given valid input this will always
@@ -113,6 +114,40 @@ func IPFromAddr(addr netip.Addr) net.IP {
113114
return net.IP(addr.AsSlice())
114115
}
115116

117+
// IPFromInterfaceAddr can be used to extract the underlying IP address value (as a
118+
// net.IP) from the return values of net.InterfaceAddrs(), net.Interface.Addrs(), or
119+
// net.Interface.MulticastAddrs(). (net.Addr is also used in some other APIs, but this
120+
// function should not be used on net.Addrs that are not "interface addresses".)
121+
func IPFromInterfaceAddr(ifaddr net.Addr) net.IP {
122+
// On both Linux and Windows, the values returned from the "interface address"
123+
// methods are currently *net.IPNet for unicast addresses or *net.IPAddr for
124+
// multicast addresses.
125+
if ipnet, ok := ifaddr.(*net.IPNet); ok {
126+
return ipnet.IP
127+
} else if ipaddr, ok := ifaddr.(*net.IPAddr); ok {
128+
return ipaddr.IP
129+
}
130+
131+
// Try to deal with other similar types... in particular, this is needed for
132+
// some existing unit tests...
133+
addrStr := ifaddr.String()
134+
// If it has a subnet length (like net.IPNet) or optional zone identifier (like
135+
// net.IPAddr), trim that away.
136+
if end := strings.IndexAny(addrStr, "/%"); end != -1 {
137+
addrStr = addrStr[:end]
138+
}
139+
// What's left is either an IP address, or something we can't parse.
140+
return ParseIPSloppy(addrStr)
141+
}
142+
143+
// AddrFromInterfaceAddr can be used to extract the underlying IP address value (as a
144+
// netip.Addr) from the return values of net.InterfaceAddrs(), net.Interface.Addrs(), or
145+
// net.Interface.MulticastAddrs(). (net.Addr is also used in some other APIs, but this
146+
// function should not be used on net.Addrs that are not "interface addresses".)
147+
func AddrFromInterfaceAddr(ifaddr net.Addr) netip.Addr {
148+
return AddrFromIP(IPFromInterfaceAddr(ifaddr))
149+
}
150+
116151
// PrefixFromIPNet converts a *net.IPNet to a netip.Prefix. Given valid input this will
117152
// always succeed; it will return the invalid netip.Prefix on nil or garbage input.
118153
func PrefixFromIPNet(ipnet *net.IPNet) netip.Prefix {

net/v2/convert_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,91 @@ func TestIPFromAddr(t *testing.T) {
126126
}
127127
}
128128

129+
type dummyNetAddr string
130+
131+
func (d dummyNetAddr) Network() string {
132+
return "dummy"
133+
}
134+
func (d dummyNetAddr) String() string {
135+
return string(d)
136+
}
137+
138+
func TestIPFromInterfaceAddr_AddrFromInterfaceAddr(t *testing.T) {
139+
testCases := []struct {
140+
desc string
141+
ifaddr net.Addr
142+
out string
143+
}{
144+
{
145+
desc: "net.IPNet",
146+
ifaddr: &net.IPNet{IP: net.IP{192, 168, 1, 1}, Mask: net.CIDRMask(24, 32)},
147+
out: "192.168.1.1",
148+
},
149+
{
150+
desc: "net.IPAddr",
151+
ifaddr: &net.IPAddr{IP: net.IP{192, 168, 1, 2}},
152+
out: "192.168.1.2",
153+
},
154+
{
155+
desc: "net.IPAddr with zone",
156+
ifaddr: &net.IPAddr{IP: net.IP{192, 168, 1, 3}, Zone: "eth0"},
157+
out: "192.168.1.3",
158+
},
159+
{
160+
desc: "net.TCPAddr",
161+
ifaddr: &net.TCPAddr{IP: net.IP{192, 168, 1, 4}, Port: 80},
162+
out: "",
163+
},
164+
{
165+
desc: "unknown plain IP",
166+
ifaddr: dummyNetAddr("192.168.1.5"),
167+
out: "192.168.1.5",
168+
},
169+
{
170+
desc: "unknown CIDR",
171+
ifaddr: dummyNetAddr("192.168.1.6/24"),
172+
out: "192.168.1.6",
173+
},
174+
{
175+
desc: "unknown IP with zone",
176+
ifaddr: dummyNetAddr("192.168.1.7%eth0"),
177+
out: "192.168.1.7",
178+
},
179+
{
180+
desc: "unknown sockaddr",
181+
ifaddr: dummyNetAddr("192.168.1.8:80"),
182+
out: "",
183+
},
184+
{
185+
desc: "unknown junk",
186+
ifaddr: dummyNetAddr("junk"),
187+
out: "",
188+
},
189+
}
190+
191+
for _, tc := range testCases {
192+
t.Run(tc.desc, func(t *testing.T) {
193+
ip := IPFromInterfaceAddr(tc.ifaddr)
194+
addr := AddrFromInterfaceAddr(tc.ifaddr)
195+
if tc.out == "" {
196+
if ip != nil {
197+
t.Errorf("expected IPFromInterfaceAddr to return nil but got %q", ip.String())
198+
}
199+
if addr.IsValid() {
200+
t.Errorf("expected AddrFromInterfaceAddr to return zero but got %q", addr.String())
201+
}
202+
} else {
203+
if ip.String() != tc.out {
204+
t.Errorf("expected IPFromInterfaceAddr to return %q but got %q", tc.out, ip.String())
205+
}
206+
if addr.String() != tc.out {
207+
t.Errorf("expected AddrFromInterfaceAddr to return %q but got %q", tc.out, addr.String())
208+
}
209+
}
210+
})
211+
}
212+
}
213+
129214
func TestPrefixFromIPNet(t *testing.T) {
130215
// See test cases in ips_test.go
131216
for _, tc := range goodTestCIDRs {

0 commit comments

Comments
 (0)