Skip to content

Commit 3471700

Browse files
committed
Split out IP validation functions into their own file
(No code changes.)
1 parent 5e067b6 commit 3471700

File tree

4 files changed

+381
-336
lines changed

4 files changed

+381
-336
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package validation
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/util/validation/field"
21+
netutils "k8s.io/utils/net"
22+
)
23+
24+
// IsValidIP tests that the argument is a valid IP address.
25+
func IsValidIP(fldPath *field.Path, value string) field.ErrorList {
26+
var allErrors field.ErrorList
27+
if netutils.ParseIPSloppy(value) == nil {
28+
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IP address, (e.g. 10.9.8.7 or 2001:db8::ffff)").WithOrigin("format=ip-sloppy"))
29+
}
30+
return allErrors
31+
}
32+
33+
// IsValidIPv4Address tests that the argument is a valid IPv4 address.
34+
func IsValidIPv4Address(fldPath *field.Path, value string) field.ErrorList {
35+
var allErrors field.ErrorList
36+
ip := netutils.ParseIPSloppy(value)
37+
if ip == nil || ip.To4() == nil {
38+
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv4 address"))
39+
}
40+
return allErrors
41+
}
42+
43+
// IsValidIPv6Address tests that the argument is a valid IPv6 address.
44+
func IsValidIPv6Address(fldPath *field.Path, value string) field.ErrorList {
45+
var allErrors field.ErrorList
46+
ip := netutils.ParseIPSloppy(value)
47+
if ip == nil || ip.To4() != nil {
48+
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv6 address"))
49+
}
50+
return allErrors
51+
}
52+
53+
// IsValidCIDR tests that the argument is a valid CIDR value.
54+
func IsValidCIDR(fldPath *field.Path, value string) field.ErrorList {
55+
var allErrors field.ErrorList
56+
_, _, err := netutils.ParseCIDRSloppy(value)
57+
if err != nil {
58+
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid CIDR value, (e.g. 10.9.8.0/24 or 2001:db8::/64)"))
59+
}
60+
return allErrors
61+
}
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package validation
18+
19+
import (
20+
"strings"
21+
"testing"
22+
23+
"k8s.io/apimachinery/pkg/util/validation/field"
24+
)
25+
26+
func TestIsValidIP(t *testing.T) {
27+
for _, tc := range []struct {
28+
name string
29+
in string
30+
family int
31+
err string
32+
}{
33+
// GOOD VALUES
34+
{
35+
name: "ipv4",
36+
in: "1.2.3.4",
37+
family: 4,
38+
},
39+
{
40+
name: "ipv4, all zeros",
41+
in: "0.0.0.0",
42+
family: 4,
43+
},
44+
{
45+
name: "ipv4, max",
46+
in: "255.255.255.255",
47+
family: 4,
48+
},
49+
{
50+
name: "ipv6",
51+
in: "1234::abcd",
52+
family: 6,
53+
},
54+
{
55+
name: "ipv6, all zeros, collapsed",
56+
in: "::",
57+
family: 6,
58+
},
59+
{
60+
name: "ipv6, max",
61+
in: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
62+
family: 6,
63+
},
64+
65+
// GOOD, THOUGH NON-CANONICAL, VALUES
66+
{
67+
name: "ipv6, all zeros, expanded (non-canonical)",
68+
in: "0:0:0:0:0:0:0:0",
69+
family: 6,
70+
},
71+
{
72+
name: "ipv6, leading 0s (non-canonical)",
73+
in: "0001:002:03:4::",
74+
family: 6,
75+
},
76+
{
77+
name: "ipv6, capital letters (non-canonical)",
78+
in: "1234::ABCD",
79+
family: 6,
80+
},
81+
82+
// BAD VALUES WE CURRENTLY CONSIDER GOOD
83+
{
84+
name: "ipv4 with leading 0s",
85+
in: "1.1.1.01",
86+
family: 4,
87+
},
88+
{
89+
name: "ipv4-in-ipv6 value",
90+
in: "::ffff:1.1.1.1",
91+
family: 4,
92+
},
93+
94+
// BAD VALUES
95+
{
96+
name: "empty string",
97+
in: "",
98+
err: "must be a valid IP address",
99+
},
100+
{
101+
name: "junk",
102+
in: "aaaaaaa",
103+
err: "must be a valid IP address",
104+
},
105+
{
106+
name: "domain name",
107+
in: "myhost.mydomain",
108+
err: "must be a valid IP address",
109+
},
110+
{
111+
name: "cidr",
112+
in: "1.2.3.0/24",
113+
err: "must be a valid IP address",
114+
},
115+
{
116+
name: "ipv4 with out-of-range octets",
117+
in: "1.2.3.400",
118+
err: "must be a valid IP address",
119+
},
120+
{
121+
name: "ipv4 with negative octets",
122+
in: "-1.0.0.0",
123+
err: "must be a valid IP address",
124+
},
125+
{
126+
name: "ipv6 with out-of-range segment",
127+
in: "2001:db8::10005",
128+
err: "must be a valid IP address",
129+
},
130+
{
131+
name: "ipv4:port",
132+
in: "1.2.3.4:80",
133+
err: "must be a valid IP address",
134+
},
135+
{
136+
name: "ipv6 with brackets",
137+
in: "[2001:db8::1]",
138+
err: "must be a valid IP address",
139+
},
140+
{
141+
name: "[ipv6]:port",
142+
in: "[2001:db8::1]:80",
143+
err: "must be a valid IP address",
144+
},
145+
{
146+
name: "host:port",
147+
in: "example.com:80",
148+
err: "must be a valid IP address",
149+
},
150+
{
151+
name: "ipv6 with zone",
152+
in: "1234::abcd%eth0",
153+
err: "must be a valid IP address",
154+
},
155+
{
156+
name: "ipv4 with zone",
157+
in: "169.254.0.0%eth0",
158+
err: "must be a valid IP address",
159+
},
160+
} {
161+
t.Run(tc.name, func(t *testing.T) {
162+
errs := IsValidIP(field.NewPath(""), tc.in)
163+
if tc.err == "" {
164+
if len(errs) != 0 {
165+
t.Errorf("expected %q to be valid but got: %v", tc.in, errs)
166+
}
167+
} else {
168+
if len(errs) != 1 {
169+
t.Errorf("expected %q to have 1 error but got: %v", tc.in, errs)
170+
} else if !strings.Contains(errs[0].Detail, tc.err) {
171+
t.Errorf("expected error for %q to contain %q but got: %q", tc.in, tc.err, errs[0].Detail)
172+
}
173+
}
174+
175+
errs = IsValidIPv4Address(field.NewPath(""), tc.in)
176+
if tc.family == 4 {
177+
if len(errs) != 0 {
178+
t.Errorf("expected %q to pass IsValidIPv4Address but got: %v", tc.in, errs)
179+
}
180+
} else {
181+
if len(errs) == 0 {
182+
t.Errorf("expected %q to fail IsValidIPv4Address", tc.in)
183+
}
184+
}
185+
186+
errs = IsValidIPv6Address(field.NewPath(""), tc.in)
187+
if tc.family == 6 {
188+
if len(errs) != 0 {
189+
t.Errorf("expected %q to pass IsValidIPv6Address but got: %v", tc.in, errs)
190+
}
191+
} else {
192+
if len(errs) == 0 {
193+
t.Errorf("expected %q to fail IsValidIPv6Address", tc.in)
194+
}
195+
}
196+
})
197+
}
198+
}
199+
200+
func TestIsValidCIDR(t *testing.T) {
201+
for _, tc := range []struct {
202+
name string
203+
in string
204+
err string
205+
}{
206+
// GOOD VALUES
207+
{
208+
name: "ipv4",
209+
in: "1.0.0.0/8",
210+
},
211+
{
212+
name: "ipv4, all IPs",
213+
in: "0.0.0.0/0",
214+
},
215+
{
216+
name: "ipv4, single IP",
217+
in: "1.1.1.1/32",
218+
},
219+
{
220+
name: "ipv6",
221+
in: "2001:4860:4860::/48",
222+
},
223+
{
224+
name: "ipv6, all IPs",
225+
in: "::/0",
226+
},
227+
{
228+
name: "ipv6, single IP",
229+
in: "::1/128",
230+
},
231+
232+
// GOOD, THOUGH NON-CANONICAL, VALUES
233+
{
234+
name: "ipv6, extra 0s (non-canonical)",
235+
in: "2a00:79e0:2:0::/64",
236+
},
237+
{
238+
name: "ipv6, capital letters (non-canonical)",
239+
in: "2001:DB8::/64",
240+
},
241+
242+
// BAD VALUES WE CURRENTLY CONSIDER GOOD
243+
{
244+
name: "ipv4 with leading 0s",
245+
in: "1.1.01.0/24",
246+
},
247+
{
248+
name: "ipv4-in-ipv6 with ipv4-sized prefix",
249+
in: "::ffff:1.1.1.0/24",
250+
},
251+
{
252+
name: "ipv4-in-ipv6 with ipv6-sized prefix",
253+
in: "::ffff:1.1.1.0/120",
254+
},
255+
{
256+
name: "ipv4 with bits past prefix",
257+
in: "1.2.3.4/24",
258+
},
259+
{
260+
name: "ipv6 with bits past prefix",
261+
in: "2001:db8::1/64",
262+
},
263+
{
264+
name: "prefix length with leading 0s",
265+
in: "192.168.0.0/016",
266+
},
267+
268+
// BAD VALUES
269+
{
270+
name: "empty string",
271+
in: "",
272+
err: "must be a valid CIDR value",
273+
},
274+
{
275+
name: "junk",
276+
in: "aaaaaaa",
277+
err: "must be a valid CIDR value",
278+
},
279+
{
280+
name: "IP address",
281+
in: "1.2.3.4",
282+
err: "must be a valid CIDR value",
283+
},
284+
{
285+
name: "partial URL",
286+
in: "192.168.0.1/healthz",
287+
err: "must be a valid CIDR value",
288+
},
289+
{
290+
name: "partial URL 2",
291+
in: "192.168.0.1/0/99",
292+
err: "must be a valid CIDR value",
293+
},
294+
{
295+
name: "negative prefix length",
296+
in: "192.168.0.0/-16",
297+
err: "must be a valid CIDR value",
298+
},
299+
{
300+
name: "prefix length with sign",
301+
in: "192.168.0.0/+16",
302+
err: "must be a valid CIDR value",
303+
},
304+
} {
305+
t.Run(tc.name, func(t *testing.T) {
306+
errs := IsValidCIDR(field.NewPath(""), tc.in)
307+
if tc.err == "" {
308+
if len(errs) != 0 {
309+
t.Errorf("expected %q to be valid but got: %v", tc.in, errs)
310+
}
311+
} else {
312+
if len(errs) != 1 {
313+
t.Errorf("expected %q to have 1 error but got: %v", tc.in, errs)
314+
} else if !strings.Contains(errs[0].Detail, tc.err) {
315+
t.Errorf("expected error for %q to contain %q but got: %q", tc.in, tc.err, errs[0].Detail)
316+
}
317+
}
318+
})
319+
}
320+
}

0 commit comments

Comments
 (0)