Skip to content

Commit a8abe6f

Browse files
committed
Add replacements for Parse{IP,CIDR}Sloppy to netutils v2
Replace ParseIPSloppy and ParseCIDRSloppy with ParseIP and ParseIPNet, where both have a single return value plus an error.
1 parent c3dc69e commit a8abe6f

File tree

9 files changed

+111
-62
lines changed

9 files changed

+111
-62
lines changed

net/v2/convert.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ func IPFromInterfaceAddr(ifaddr net.Addr) net.IP {
137137
addrStr = addrStr[:end]
138138
}
139139
// What's left is either an IP address, or something we can't parse.
140-
return ParseIPSloppy(addrStr)
140+
ip, _ := ParseIP(addrStr)
141+
return ip
141142
}
142143

143144
// AddrFromInterfaceAddr can be used to extract the underlying IP address value (as a

net/v2/ipfamily.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ func IPFamilyOf[T ipOrString](val T) IPFamily {
6868
return IPv6
6969
}
7070
case string:
71-
return IPFamilyOf(ParseIPSloppy(typedVal))
71+
if ip, _ := ParseIP(typedVal); ip != nil {
72+
return IPFamilyOf(ip)
73+
}
7274
}
7375

7476
return IPFamilyUnknown
@@ -134,8 +136,9 @@ func IPFamilyOfCIDR[T cidrOrString](val T) IPFamily {
134136
}
135137
return IPFamilyOf(typedVal.Addr())
136138
case string:
137-
parsedIP, _, _ := ParseCIDRSloppy(typedVal)
138-
return IPFamilyOf(parsedIP)
139+
if ipnet, _ := ParseIPNet(typedVal); ipnet != nil {
140+
return IPFamilyOf(ipnet.IP)
141+
}
139142
}
140143

141144
return IPFamilyUnknown

net/v2/ipfamily_test.go

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

@@ -179,7 +179,7 @@ func TestIsDualStackCIDRs(t *testing.T) {
179179
ipnets := make([]*net.IPNet, len(tc.cidrs))
180180
prefixes := make([]netip.Prefix, len(tc.cidrs))
181181
for i := range tc.cidrs {
182-
_, ipnets[i], _ = ParseCIDRSloppy(tc.cidrs[i])
182+
ipnets[i], _ = ParseIPNet(tc.cidrs[i])
183183
prefixes[i], _ = netip.ParsePrefix(tc.cidrs[i])
184184
}
185185

net/v2/ips_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,7 @@ var goodTestCIDRs = []testCIDR{
439439
{
440440
desc: "IPv4 ifaddr (masked)",
441441
// This tests that if you try to parse an "ifaddr-style" CIDR string with
442-
// ParseCIDRSloppy, the *net.IPNet return value has the bits beyond the
443-
// prefix length masked out.
442+
// ParseIPNet, it has the bits beyond the prefix length masked out.
444443
family: IPv4,
445444
strings: []string{
446445
"1.2.3.0/24",
@@ -531,8 +530,7 @@ var goodTestCIDRs = []testCIDR{
531530
{
532531
desc: "IPv6 ifaddr (masked)",
533532
// This tests that if you try to parse an "ifaddr-style" CIDR string with
534-
// ParseCIDRSloppy, the *net.IPNet return value has the bits beyond the
535-
// prefix length masked out.
533+
// ParseIPNet, it value has the bits beyond the prefix length masked out.
536534
family: IPv6,
537535
strings: []string{
538536
"2001:db8::/64",

net/v2/multi_listen_test.go

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,13 @@ import (
2626
"sync/atomic"
2727
"testing"
2828
"time"
29+
30+
forkednet "k8s.io/utils/internal/third_party/forked/golang/net"
2931
)
3032

33+
// Temporary
34+
var parseIPSloppy = forkednet.ParseIP
35+
3136
type fakeCon struct {
3237
remoteAddr net.Addr
3338
}
@@ -113,7 +118,7 @@ func listenFuncFactory(listeners []*fakeListener) func(_ context.Context, networ
113118
}
114119
listener := listeners[index]
115120
addr := &net.TCPAddr{
116-
IP: ParseIPSloppy(host),
121+
IP: parseIPSloppy(host),
117122
Port: port,
118123
}
119124
if err != nil {
@@ -265,14 +270,14 @@ func TestMultiListen_Close(t *testing.T) {
265270
},
266271
fakeListeners: []*fakeListener{{
267272
connErrPairs: []connErrPair{{
268-
conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("10.10.10.10"), Port: 50001}},
273+
conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("10.10.10.10"), Port: 50001}},
269274
}}}, {
270275
connErrPairs: []connErrPair{{
271-
conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 50002}},
276+
conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("192.168.1.10"), Port: 50002}},
272277
},
273278
}}, {
274279
connErrPairs: []connErrPair{{
275-
conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 50003}},
280+
conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("127.0.0.1"), Port: 50003}},
276281
}},
277282
}},
278283
},
@@ -294,13 +299,13 @@ func TestMultiListen_Close(t *testing.T) {
294299
},
295300
fakeListeners: []*fakeListener{{
296301
connErrPairs: []connErrPair{
297-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("10.10.10.10"), Port: 50001}}},
302+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("10.10.10.10"), Port: 50001}}},
298303
}}, {
299304
connErrPairs: []connErrPair{
300-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 50002}}},
305+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("192.168.1.10"), Port: 50002}}},
301306
}}, {
302307
connErrPairs: []connErrPair{
303-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 50003}}},
308+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("127.0.0.1"), Port: 50003}}},
304309
},
305310
}},
306311
acceptCalls: 3,
@@ -381,13 +386,13 @@ func TestMultiListen_Accept(t *testing.T) {
381386
},
382387
fakeListeners: []*fakeListener{{
383388
connErrPairs: []connErrPair{
384-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("10.10.10.10"), Port: 50001}}},
389+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("10.10.10.10"), Port: 50001}}},
385390
}}, {
386391
connErrPairs: []connErrPair{
387-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 50002}}},
392+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("192.168.1.10"), Port: 50002}}},
388393
}}, {
389394
connErrPairs: []connErrPair{
390-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 50003}}},
395+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("127.0.0.1"), Port: 50003}}},
391396
},
392397
}},
393398
acceptCalls: 3,
@@ -412,24 +417,24 @@ func TestMultiListen_Accept(t *testing.T) {
412417
},
413418
fakeListeners: []*fakeListener{{
414419
connErrPairs: []connErrPair{
415-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("10.10.10.10"), Port: 30001}}},
420+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("10.10.10.10"), Port: 30001}}},
416421
}}, {
417422
connErrPairs: []connErrPair{
418-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 40001}}},
419-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 40002}}},
423+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("192.168.1.10"), Port: 40001}}},
424+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("192.168.1.10"), Port: 40002}}},
420425
}}, {
421426
connErrPairs: []connErrPair{
422-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("172.16.20.10"), Port: 50001}}},
423-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("172.16.20.10"), Port: 50002}}},
424-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("172.16.20.10"), Port: 50003}}},
425-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("172.16.20.10"), Port: 50004}}},
427+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("172.16.20.10"), Port: 50001}}},
428+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("172.16.20.10"), Port: 50002}}},
429+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("172.16.20.10"), Port: 50003}}},
430+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("172.16.20.10"), Port: 50004}}},
426431
}}, {
427432
connErrPairs: []connErrPair{
428-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 60001}}},
429-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 60002}}},
430-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 60003}}},
431-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 60004}}},
432-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 60005}}},
433+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("127.0.0.1"), Port: 60001}}},
434+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("127.0.0.1"), Port: 60002}}},
435+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("127.0.0.1"), Port: 60003}}},
436+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("127.0.0.1"), Port: 60004}}},
437+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("127.0.0.1"), Port: 60005}}},
433438
},
434439
}},
435440
acceptCalls: 3,
@@ -452,13 +457,13 @@ func TestMultiListen_Accept(t *testing.T) {
452457
},
453458
fakeListeners: []*fakeListener{{
454459
connErrPairs: []connErrPair{
455-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("10.10.10.10"), Port: 50001}}},
460+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("10.10.10.10"), Port: 50001}}},
456461
}}, {
457462
connErrPairs: []connErrPair{
458-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("192.168.1.10"), Port: 50002}}},
463+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("192.168.1.10"), Port: 50002}}},
459464
}}, {
460465
connErrPairs: []connErrPair{
461-
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: ParseIPSloppy("127.0.0.1"), Port: 50003}}},
466+
{conn: &fakeCon{remoteAddr: &net.TCPAddr{IP: parseIPSloppy("127.0.0.1"), Port: 50003}}},
462467
},
463468
}},
464469
acceptCalls: 1,

net/v2/net.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
func ParseCIDRs(cidrsString []string) ([]*net.IPNet, error) {
3131
cidrs := make([]*net.IPNet, 0, len(cidrsString))
3232
for i, cidrString := range cidrsString {
33-
_, cidr, err := ParseCIDRSloppy(cidrString)
33+
cidr, err := ParseIPNet(cidrString)
3434
if err != nil {
3535
return nil, fmt.Errorf("invalid CIDR[%d]: %v (%v)", i, cidr, err)
3636
}

net/v2/net_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func TestRangeSize(t *testing.T) {
162162
}
163163

164164
for _, tc := range testCases {
165-
_, cidr, err := ParseCIDRSloppy(tc.cidr)
165+
cidr, err := ParseIPNet(tc.cidr)
166166
if err != nil {
167167
t.Errorf("failed to parse cidr for test %s, unexpected error: '%s'", tc.name, err)
168168
}
@@ -228,7 +228,7 @@ func TestGetIndexedIP(t *testing.T) {
228228
}
229229

230230
for _, tc := range testCases {
231-
_, subnet, err := ParseCIDRSloppy(tc.cidr)
231+
subnet, err := ParseIPNet(tc.cidr)
232232
if err != nil {
233233
t.Errorf("failed to parse cidr %s, unexpected error: '%s'", tc.cidr, err)
234234
}

net/v2/parse.go

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2021 The Kubernetes Authors.
2+
Copyright 2024 The Kubernetes Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -17,17 +17,59 @@ limitations under the License.
1717
package net
1818

1919
import (
20+
"fmt"
21+
"net"
22+
2023
forkednet "k8s.io/utils/internal/third_party/forked/golang/net"
2124
)
2225

23-
// ParseIPSloppy is identical to Go's standard net.ParseIP, except that it allows
24-
// leading '0' characters on numbers. Go used to allow this and then changed
25-
// the behavior in 1.17. We're choosing to keep it for compat with potential
26-
// stored values.
27-
var ParseIPSloppy = forkednet.ParseIP
28-
29-
// ParseCIDRSloppy is identical to Go's standard net.ParseCIDR, except that it allows
30-
// leading '0' characters on numbers. Go used to allow this and then changed
31-
// the behavior in 1.17. We're choosing to keep it for compat with potential
32-
// stored values.
33-
var ParseCIDRSloppy = forkednet.ParseCIDR
26+
// ParseIP parses an IPv4 or IPv6 address to a net.IP. This accepts both fully-valid IP
27+
// addresses and irregular/ambiguous forms, making it usable for both validated and
28+
// non-validated input strings. It should be used instead of net.ParseIP (which rejects
29+
// some strings we need to accept for backward compatibility) and the old
30+
// netutilsv1.ParseIPSloppy.
31+
func ParseIP(ipStr string) (net.IP, error) {
32+
// Note: if we want to get rid of forkednet, we should be able to use some
33+
// invocation of regexp.ReplaceAllString to get rid of leading 0s in ipStr.
34+
ip := forkednet.ParseIP(ipStr)
35+
if ip != nil {
36+
return ip, nil
37+
}
38+
39+
if ipStr == "" {
40+
return nil, fmt.Errorf("expected an IP address")
41+
}
42+
// NB: we use forkednet.ParseCIDR directly, not ParseIPNet, to avoid recursing
43+
// between ParseIP and ParseIPNet.
44+
if _, _, err := forkednet.ParseCIDR(ipStr); err == nil {
45+
return nil, fmt.Errorf("expected an IP address, got a CIDR value")
46+
}
47+
return nil, fmt.Errorf("not a valid IP address")
48+
}
49+
50+
// ParseIPNet parses an IPv4 or IPv6 CIDR string representing a subnet or mask, to a
51+
// *net.IPNet. This accepts both fully-valid CIDR values and irregular/ambiguous forms,
52+
// making it usable for both validated and non-validated input strings. It should be used
53+
// instead of net.ParseCIDR (which rejects some strings that we need to accept for
54+
// backward-compatibility) and the old netutilsv1.ParseCIDRSloppy.
55+
//
56+
// The return value is equivalent to the second return value from net.ParseCIDR. Note that
57+
// this means that if the CIDR string has bits set beyond the prefix length (e.g., the "5"
58+
// in "192.168.1.5/24"), those bits are simply discarded.
59+
func ParseIPNet(cidrStr string) (*net.IPNet, error) {
60+
// Note: if we want to get rid of forkednet, we should be able to use some
61+
// invocation of regexp.ReplaceAllString to get rid of leading 0s in cidrStr.
62+
if _, ipnet, err := forkednet.ParseCIDR(cidrStr); err == nil {
63+
return ipnet, nil
64+
}
65+
66+
if cidrStr == "" {
67+
return nil, fmt.Errorf("expected a CIDR value")
68+
}
69+
// NB: we use forkednet.ParseIP directly, not our own ParseIP, to avoid recursing
70+
// between ParseIPNet and ParseIP.
71+
if forkednet.ParseIP(cidrStr) != nil {
72+
return nil, fmt.Errorf("expected a CIDR value, but got IP address")
73+
}
74+
return nil, fmt.Errorf("not a valid CIDR value")
75+
}

net/v2/parse_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ import (
2020
"testing"
2121
)
2222

23-
func TestParseIPSloppy(t *testing.T) {
23+
func TestParseIP(t *testing.T) {
2424
// See test cases in ips_test.go
2525
for _, tc := range goodTestIPs {
2626
if tc.skipParse {
2727
continue
2828
}
2929
t.Run(tc.desc, func(t *testing.T) {
3030
for i, str := range tc.strings {
31-
ip := ParseIPSloppy(str)
32-
if ip == nil {
33-
t.Errorf("expected %q to parse, but failed", str)
31+
ip, err := ParseIP(str)
32+
if err != nil {
33+
t.Errorf("expected %q to parse, but got error %v", str, err)
3434
}
3535
if !ip.Equal(tc.ips[0]) {
3636
t.Errorf("expected string %d %q to parse equal to IP %#v %q but got %#v (%q)", i+1, str, tc.ips[0], tc.ips[0].String(), ip, ip.String())
@@ -47,22 +47,22 @@ func TestParseIPSloppy(t *testing.T) {
4747
t.Run(tc.desc, func(t *testing.T) {
4848
for i, ip := range tc.ips {
4949
errStr := ip.String()
50-
parsedIP := ParseIPSloppy(errStr)
50+
parsedIP, _ := ParseIP(errStr)
5151
if parsedIP != nil {
5252
t.Errorf("expected IP %d %#v (%q) to not re-parse but got %#v (%q)", i+1, ip, errStr, parsedIP, parsedIP.String())
5353
}
5454
}
5555

5656
for i, addr := range tc.addrs {
5757
errStr := addr.String()
58-
parsedIP := ParseIPSloppy(errStr)
58+
parsedIP, _ := ParseIP(errStr)
5959
if parsedIP != nil {
6060
t.Errorf("expected Addr %d %#v (%q) to not re-parse but got %#v (%q)", i+1, addr, errStr, parsedIP, parsedIP.String())
6161
}
6262
}
6363

6464
for i, str := range tc.strings {
65-
ip := ParseIPSloppy(str)
65+
ip, _ := ParseIP(str)
6666
if ip != nil {
6767
t.Errorf("expected string %d %q to not parse but got %#v (%q)", i+1, str, ip, ip.String())
6868
}
@@ -71,15 +71,15 @@ func TestParseIPSloppy(t *testing.T) {
7171
}
7272
}
7373

74-
func TestParseCIDRSloppy(t *testing.T) {
74+
func TestParseIPNet(t *testing.T) {
7575
// See test cases in ips_test.go
7676
for _, tc := range goodTestCIDRs {
7777
if tc.skipParse {
7878
continue
7979
}
8080
t.Run(tc.desc, func(t *testing.T) {
8181
for i, str := range tc.strings {
82-
_, ipnet, err := ParseCIDRSloppy(str)
82+
ipnet, err := ParseIPNet(str)
8383
if err != nil {
8484
t.Errorf("expected %q to parse, but got error %v", str, err)
8585
}
@@ -98,22 +98,22 @@ func TestParseCIDRSloppy(t *testing.T) {
9898
t.Run(tc.desc, func(t *testing.T) {
9999
for i, ipnet := range tc.ipnets {
100100
errStr := ipnet.String()
101-
_, parsedIPNet, err := ParseCIDRSloppy(errStr)
101+
parsedIPNet, err := ParseIPNet(errStr)
102102
if err == nil {
103103
t.Errorf("expected IPNet %d %q to not parse but got %#v (%q)", i+1, errStr, *parsedIPNet, parsedIPNet.String())
104104
}
105105
}
106106

107107
for i, prefix := range tc.prefixes {
108108
errStr := prefix.String()
109-
_, parsedIPNet, err := ParseCIDRSloppy(errStr)
109+
parsedIPNet, err := ParseIPNet(errStr)
110110
if err == nil {
111111
t.Errorf("expected Prefix %d %#v %q to not parse but got %#v (%q)", i+1, prefix, errStr, *parsedIPNet, parsedIPNet.String())
112112
}
113113
}
114114

115115
for i, str := range tc.strings {
116-
_, ipnet, err := ParseCIDRSloppy(str)
116+
ipnet, err := ParseIPNet(str)
117117
if err == nil {
118118
t.Errorf("expected string %d %q to not parse but got %#v (%q)", i+1, str, *ipnet, ipnet.String())
119119
}

0 commit comments

Comments
 (0)