Skip to content

Commit f39235b

Browse files
committed
Add ParseAddr, ParsePrefix to netutilsv2
1 parent a8abe6f commit f39235b

File tree

4 files changed

+153
-2
lines changed

4 files changed

+153
-2
lines changed

net/v2/ipfamily_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func TestIsDualStack(t *testing.T) {
9191
addrs := make([]netip.Addr, len(tc.ips))
9292
for i := range tc.ips {
9393
netips[i], _ = ParseIP(tc.ips[i])
94-
addrs[i], _ = netip.ParseAddr(tc.ips[i])
94+
addrs[i], _ = ParseAddr(tc.ips[i])
9595
}
9696

9797
dualStack := IsDualStack(tc.ips)
@@ -180,7 +180,7 @@ func TestIsDualStackCIDRs(t *testing.T) {
180180
prefixes := make([]netip.Prefix, len(tc.cidrs))
181181
for i := range tc.cidrs {
182182
ipnets[i], _ = ParseIPNet(tc.cidrs[i])
183-
prefixes[i], _ = netip.ParsePrefix(tc.cidrs[i])
183+
prefixes[i], _ = ParsePrefix(tc.cidrs[i])
184184
}
185185

186186
dualStack := IsDualStackCIDRs(tc.cidrs)

net/v2/ips_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type testIP struct {
5050
//
5151
// Parsing tests (unless `skipParse: true`):
5252
// - Each element of .strings should parse to a value equal to .ips[0].
53+
// - Each element of .strings should parse to a value equal to .addrs[0].
5354
//
5455
// Conversion tests (unless `skipConvert: true`):
5556
// - Each element of .ips should convert to a value equal to .addrs[0].
@@ -184,6 +185,8 @@ var goodTestIPs = []testIP{
184185
desc: "IPv4-mapped IPv6",
185186
// net.IP can represent an IPv4 address internally as either a 4-byte
186187
// value or a 16-byte value, but it treats the two forms as equivalent.
188+
// Because IPv4-mapped IPv6 is annoying, we make our ParseAddr() behave
189+
// this way too, even though that's *not* how netip.ParseAddr() behaves.
187190
//
188191
// This test case confirms that:
189192
// - The 4-byte and 16-byte forms of a given net.IP compare as .Equal().
@@ -377,6 +380,7 @@ type testCIDR struct {
377380
//
378381
// Parsing tests (unless `skipParse: true`):
379382
// - Each element of .strings should parse to a value "equal" to .ipnets[0].
383+
// - Each element of .strings should parse to a value equal to .prefixes[0].
380384
//
381385
// Conversion tests (unless `skipConvert: true`):
382386
// - Each element of .ipnets should convert to a value equal to .prefixes[0].

net/v2/parse.go

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

2324
forkednet "k8s.io/utils/internal/third_party/forked/golang/net"
2425
)
@@ -28,6 +29,8 @@ import (
2829
// non-validated input strings. It should be used instead of net.ParseIP (which rejects
2930
// some strings we need to accept for backward compatibility) and the old
3031
// netutilsv1.ParseIPSloppy.
32+
//
33+
// Compare ParseAddr, which returns a netip.Addr but is otherwise identical.
3134
func ParseIP(ipStr string) (net.IP, error) {
3235
// Note: if we want to get rid of forkednet, we should be able to use some
3336
// invocation of regexp.ReplaceAllString to get rid of leading 0s in ipStr.
@@ -47,6 +50,25 @@ func ParseIP(ipStr string) (net.IP, error) {
4750
return nil, fmt.Errorf("not a valid IP address")
4851
}
4952

53+
// ParseAddr parses an IPv4 or IPv6 address to a netip.Addr. This accepts both fully-valid
54+
// IP addresses and irregular/ambiguous forms, making it usable for both validated and
55+
// non-validated input strings. As compared to netip.ParseAddr:
56+
//
57+
// - It accepts IPv4 IPs with extra leading "0"s, for backward compatibility.
58+
// - It rejects IPs with attached zone identifiers (e.g. `"fe80::1234%eth0"`).
59+
// - It converts "IPv4-mapped IPv6" addresses (e.g. `"::ffff:1.2.3.4"`) to the
60+
// corresponding "plain" IPv4 values (e.g. `"1.2.3.4"`). That is, it never returns an
61+
// address for which `Is4In6()` would return `true`.
62+
//
63+
// Compare ParseIP, which returns a net.IP but is otherwise identical.
64+
func ParseAddr(ipStr string) (netip.Addr, error) {
65+
// To ensure identical parsing, we use ParseIP and then convert. (If ParseIP
66+
// returns a nil ip, AddrFromIP will convert that to the zero/invalid netip.Addr,
67+
// which is what we want.)
68+
ip, err := ParseIP(ipStr)
69+
return AddrFromIP(ip), err
70+
}
71+
5072
// ParseIPNet parses an IPv4 or IPv6 CIDR string representing a subnet or mask, to a
5173
// *net.IPNet. This accepts both fully-valid CIDR values and irregular/ambiguous forms,
5274
// making it usable for both validated and non-validated input strings. It should be used
@@ -56,6 +78,8 @@ func ParseIP(ipStr string) (net.IP, error) {
5678
// The return value is equivalent to the second return value from net.ParseCIDR. Note that
5779
// this means that if the CIDR string has bits set beyond the prefix length (e.g., the "5"
5880
// in "192.168.1.5/24"), those bits are simply discarded.
81+
//
82+
// Compare ParsePrefix, which returns a netip.Prefix but is otherwise identical.
5983
func ParseIPNet(cidrStr string) (*net.IPNet, error) {
6084
// Note: if we want to get rid of forkednet, we should be able to use some
6185
// invocation of regexp.ReplaceAllString to get rid of leading 0s in cidrStr.
@@ -73,3 +97,25 @@ func ParseIPNet(cidrStr string) (*net.IPNet, error) {
7397
}
7498
return nil, fmt.Errorf("not a valid CIDR value")
7599
}
100+
101+
// ParsePrefix parses an IPv4 or IPv6 CIDR string representing a subnet or mask, to a
102+
// netip.Prefix. This accepts both fully-valid CIDR values and irregular/ambiguous forms,
103+
// making it usable for both validated and non-validated input strings. As compared to
104+
// netip.ParsePrefix:
105+
//
106+
// - It accepts IPv4 IPs with extra leading "0"s, for backward compatibility.
107+
// - It converts "IPv4-mapped IPv6" addresses (e.g. `"::ffff:1.2.3.0/120"`) to the
108+
// corresponding "plain" IPv4 values (e.g. `"1.2.3.0/24"`). That is, it never returns
109+
// a prefix for which `.Addr().Is4In6()` would return `true`.
110+
// - When given a CIDR string with bits set beyond the prefix length, like
111+
// `"192.168.1.5/24"`, it discards those extra bits (the equivalent of calling
112+
// .Masked() on the return value of netip.ParsePrefix).
113+
//
114+
// Compare ParseIPNet, which returns a *net.IPNet but is otherwise identical.
115+
func ParsePrefix(cidrStr string) (netip.Prefix, error) {
116+
// To ensure identical parsing, we use ParseIPNet and then convert. (If ParseIPNet
117+
// returns nil, PrefixFromIPNet will convert that to the zero/invalid
118+
// netip.Prefix, which is what we want.)
119+
ipnet, err := ParseIPNet(cidrStr)
120+
return PrefixFromIPNet(ipnet), err
121+
}

net/v2/parse_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,56 @@ func TestParseIP(t *testing.T) {
7171
}
7272
}
7373

74+
func TestParseAddr(t *testing.T) {
75+
// See test cases in ips_test.go
76+
for _, tc := range goodTestIPs {
77+
if tc.skipParse {
78+
continue
79+
}
80+
t.Run(tc.desc, func(t *testing.T) {
81+
for i, str := range tc.strings {
82+
addr, err := ParseAddr(str)
83+
if err != nil {
84+
t.Errorf("expected %q to parse, but got error %v", str, err)
85+
}
86+
if addr != tc.addrs[0] {
87+
t.Errorf("expected string %d %q to parse equal to Addr %#v %q but got %#v (%q)", i+1, str, tc.addrs[0], tc.addrs[0].String(), addr, addr.String())
88+
}
89+
}
90+
})
91+
}
92+
93+
for _, tc := range badTestIPs {
94+
if tc.skipParse {
95+
continue
96+
}
97+
t.Run(tc.desc, func(t *testing.T) {
98+
for i, ip := range tc.ips {
99+
errStr := ip.String()
100+
parsedAddr, err := ParseAddr(errStr)
101+
if err == nil {
102+
t.Errorf("expected IP %d %#v (%q) to not re-parse but got %#v (%q)", i+1, ip, errStr, parsedAddr, parsedAddr.String())
103+
}
104+
}
105+
106+
for i, addr := range tc.addrs {
107+
errStr := addr.String()
108+
parsedAddr, err := ParseAddr(errStr)
109+
if err == nil {
110+
t.Errorf("expected Addr %d %#v (%q) to not re-parse but got %#v (%q)", i+1, addr, errStr, parsedAddr, parsedAddr.String())
111+
}
112+
}
113+
114+
for i, str := range tc.strings {
115+
addr, err := ParseAddr(str)
116+
if err == nil {
117+
t.Errorf("expected string %d %q to not parse but got %#v (%q)", i+1, str, addr, addr.String())
118+
}
119+
}
120+
})
121+
}
122+
}
123+
74124
func TestParseIPNet(t *testing.T) {
75125
// See test cases in ips_test.go
76126
for _, tc := range goodTestCIDRs {
@@ -121,3 +171,54 @@ func TestParseIPNet(t *testing.T) {
121171
})
122172
}
123173
}
174+
175+
func TestParsePrefix(t *testing.T) {
176+
// See test cases in ips_test.go
177+
for _, tc := range goodTestCIDRs {
178+
if tc.skipParse {
179+
continue
180+
}
181+
t.Run(tc.desc, func(t *testing.T) {
182+
for i, str := range tc.strings {
183+
prefix, err := ParsePrefix(str)
184+
if err != nil {
185+
t.Errorf("expected %q to parse, but got error %v", str, err)
186+
}
187+
if prefix != tc.prefixes[0] {
188+
t.Errorf("expected string %d %q to parse equal to Prefix %#v %q but got %#v (%q)", i+1, str, tc.prefixes[0], tc.prefixes[0].String(), prefix, prefix.String())
189+
}
190+
}
191+
})
192+
}
193+
194+
// See test cases in ips_test.go
195+
for _, tc := range badTestCIDRs {
196+
if tc.skipParse {
197+
continue
198+
}
199+
t.Run(tc.desc, func(t *testing.T) {
200+
for i, ipnet := range tc.ipnets {
201+
errStr := ipnet.String()
202+
parsedPrefix, err := ParsePrefix(errStr)
203+
if err == nil {
204+
t.Errorf("expected IPNet %d %q to not parse but got %#v (%q)", i+1, errStr, parsedPrefix, parsedPrefix.String())
205+
}
206+
}
207+
208+
for i, prefix := range tc.prefixes {
209+
errStr := prefix.String()
210+
parsedPrefix, err := ParsePrefix(errStr)
211+
if err == nil {
212+
t.Errorf("expected Prefix %d %q to not parse but got %#v (%q)", i+1, errStr, parsedPrefix, parsedPrefix.String())
213+
}
214+
}
215+
216+
for i, str := range tc.strings {
217+
prefix, err := ParsePrefix(str)
218+
if err == nil {
219+
t.Errorf("expected string %d %q to not parse but got %#v (%q)", i+1, str, prefix, prefix.String())
220+
}
221+
}
222+
})
223+
}
224+
}

0 commit comments

Comments
 (0)