1// Copyright 2021 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package netip_test
6
7import (
8	"bytes"
9	"encoding"
10	"fmt"
11	"net"
12	. "net/netip"
13	"reflect"
14	"strings"
15	"testing"
16)
17
18var corpus = []string{
19	// Basic zero IPv4 address.
20	"0.0.0.0",
21	// Basic non-zero IPv4 address.
22	"192.168.140.255",
23	// IPv4 address in windows-style "print all the digits" form.
24	"010.000.015.001",
25	// IPv4 address with a silly amount of leading zeros.
26	"000001.00000002.00000003.000000004",
27	// 4-in-6 with octet with leading zero
28	"::ffff:1.2.03.4",
29	// Basic zero IPv6 address.
30	"::",
31	// Localhost IPv6.
32	"::1",
33	// Fully expanded IPv6 address.
34	"fd7a:115c:a1e0:ab12:4843:cd96:626b:430b",
35	// IPv6 with elided fields in the middle.
36	"fd7a:115c::626b:430b",
37	// IPv6 with elided fields at the end.
38	"fd7a:115c:a1e0:ab12:4843:cd96::",
39	// IPv6 with single elided field at the end.
40	"fd7a:115c:a1e0:ab12:4843:cd96:626b::",
41	"fd7a:115c:a1e0:ab12:4843:cd96:626b:0",
42	// IPv6 with single elided field in the middle.
43	"fd7a:115c:a1e0::4843:cd96:626b:430b",
44	"fd7a:115c:a1e0:0:4843:cd96:626b:430b",
45	// IPv6 with the trailing 32 bits written as IPv4 dotted decimal. (4in6)
46	"::ffff:192.168.140.255",
47	"::ffff:192.168.140.255",
48	// IPv6 with a zone specifier.
49	"fd7a:115c:a1e0:ab12:4843:cd96:626b:430b%eth0",
50	// IPv6 with dotted decimal and zone specifier.
51	"1:2::ffff:192.168.140.255%eth1",
52	"1:2::ffff:c0a8:8cff%eth1",
53	// IPv6 with capital letters.
54	"FD9E:1A04:F01D::1",
55	"fd9e:1a04:f01d::1",
56	// Empty string.
57	"",
58	// Garbage non-IP.
59	"bad",
60	// Single number. Some parsers accept this as an IPv4 address in
61	// big-endian uint32 form, but we don't.
62	"1234",
63	// IPv4 with a zone specifier.
64	"1.2.3.4%eth0",
65	// IPv4 field must have at least one digit.
66	".1.2.3",
67	"1.2.3.",
68	"1..2.3",
69	// IPv4 address too long.
70	"1.2.3.4.5",
71	// IPv4 in dotted octal form.
72	"0300.0250.0214.0377",
73	// IPv4 in dotted hex form.
74	"0xc0.0xa8.0x8c.0xff",
75	// IPv4 in class B form.
76	"192.168.12345",
77	// IPv4 in class B form, with a small enough number to be
78	// parseable as a regular dotted decimal field.
79	"127.0.1",
80	// IPv4 in class A form.
81	"192.1234567",
82	// IPv4 in class A form, with a small enough number to be
83	// parseable as a regular dotted decimal field.
84	"127.1",
85	// IPv4 field has value >255.
86	"192.168.300.1",
87	// IPv4 with too many fields.
88	"192.168.0.1.5.6",
89	// IPv6 with not enough fields.
90	"1:2:3:4:5:6:7",
91	// IPv6 with too many fields.
92	"1:2:3:4:5:6:7:8:9",
93	// IPv6 with 8 fields and a :: expander.
94	"1:2:3:4::5:6:7:8",
95	// IPv6 with a field bigger than 2b.
96	"fe801::1",
97	// IPv6 with non-hex values in field.
98	"fe80:tail:scal:e::",
99	// IPv6 with a zone delimiter but no zone.
100	"fe80::1%",
101	// IPv6 with a zone specifier of zero.
102	"::ffff:0:0%0",
103	// IPv6 (without ellipsis) with too many fields for trailing embedded IPv4.
104	"ffff:ffff:ffff:ffff:ffff:ffff:ffff:192.168.140.255",
105	// IPv6 (with ellipsis) with too many fields for trailing embedded IPv4.
106	"ffff::ffff:ffff:ffff:ffff:ffff:ffff:192.168.140.255",
107	// IPv6 with invalid embedded IPv4.
108	"::ffff:192.168.140.bad",
109	// IPv6 with multiple ellipsis ::.
110	"fe80::1::1",
111	// IPv6 with invalid non hex/colon character.
112	"fe80:1?:1",
113	// IPv6 with truncated bytes after single colon.
114	"fe80:",
115	// AddrPort strings.
116	"1.2.3.4:51820",
117	"[fd7a:115c:a1e0:ab12:4843:cd96:626b:430b]:80",
118	"[::ffff:c000:0280]:65535",
119	"[::ffff:c000:0280%eth0]:1",
120	// Prefix strings.
121	"1.2.3.4/24",
122	"fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118",
123	"::ffff:c000:0280/96",
124	"::ffff:c000:0280%eth0/37",
125}
126
127func FuzzParse(f *testing.F) {
128	for _, seed := range corpus {
129		f.Add(seed)
130	}
131
132	f.Fuzz(func(t *testing.T, s string) {
133		ip, _ := ParseAddr(s)
134		checkStringParseRoundTrip(t, ip, ParseAddr)
135		checkEncoding(t, ip)
136
137		// Check that we match the net's IP parser, modulo zones.
138		if !strings.Contains(s, "%") {
139			stdip := net.ParseIP(s)
140			if !ip.IsValid() != (stdip == nil) {
141				t.Errorf("ParseAddr zero != net.ParseIP nil: ip=%q stdip=%q", ip, stdip)
142			}
143
144			if ip.IsValid() && !ip.Is4In6() {
145				buf, err := ip.MarshalText()
146				if err != nil {
147					t.Fatal(err)
148				}
149				buf2, err := stdip.MarshalText()
150				if err != nil {
151					t.Fatal(err)
152				}
153				if !bytes.Equal(buf, buf2) {
154					t.Errorf("Addr.MarshalText() != net.IP.MarshalText(): ip=%q stdip=%q", ip, stdip)
155				}
156				if ip.String() != stdip.String() {
157					t.Errorf("Addr.String() != net.IP.String(): ip=%q stdip=%q", ip, stdip)
158				}
159				if ip.IsGlobalUnicast() != stdip.IsGlobalUnicast() {
160					t.Errorf("Addr.IsGlobalUnicast() != net.IP.IsGlobalUnicast(): ip=%q stdip=%q", ip, stdip)
161				}
162				if ip.IsInterfaceLocalMulticast() != stdip.IsInterfaceLocalMulticast() {
163					t.Errorf("Addr.IsInterfaceLocalMulticast() != net.IP.IsInterfaceLocalMulticast(): ip=%q stdip=%q", ip, stdip)
164				}
165				if ip.IsLinkLocalMulticast() != stdip.IsLinkLocalMulticast() {
166					t.Errorf("Addr.IsLinkLocalMulticast() != net.IP.IsLinkLocalMulticast(): ip=%q stdip=%q", ip, stdip)
167				}
168				if ip.IsLinkLocalUnicast() != stdip.IsLinkLocalUnicast() {
169					t.Errorf("Addr.IsLinkLocalUnicast() != net.IP.IsLinkLocalUnicast(): ip=%q stdip=%q", ip, stdip)
170				}
171				if ip.IsLoopback() != stdip.IsLoopback() {
172					t.Errorf("Addr.IsLoopback() != net.IP.IsLoopback(): ip=%q stdip=%q", ip, stdip)
173				}
174				if ip.IsMulticast() != stdip.IsMulticast() {
175					t.Errorf("Addr.IsMulticast() != net.IP.IsMulticast(): ip=%q stdip=%q", ip, stdip)
176				}
177				if ip.IsPrivate() != stdip.IsPrivate() {
178					t.Errorf("Addr.IsPrivate() != net.IP.IsPrivate(): ip=%q stdip=%q", ip, stdip)
179				}
180				if ip.IsUnspecified() != stdip.IsUnspecified() {
181					t.Errorf("Addr.IsUnspecified() != net.IP.IsUnspecified(): ip=%q stdip=%q", ip, stdip)
182				}
183			}
184		}
185
186		// Check that .Next().Prev() and .Prev().Next() preserve the IP.
187		if ip.IsValid() && ip.Next().IsValid() && ip.Next().Prev() != ip {
188			t.Errorf(".Next.Prev did not round trip: ip=%q .next=%q .next.prev=%q", ip, ip.Next(), ip.Next().Prev())
189		}
190		if ip.IsValid() && ip.Prev().IsValid() && ip.Prev().Next() != ip {
191			t.Errorf(".Prev.Next did not round trip: ip=%q .prev=%q .prev.next=%q", ip, ip.Prev(), ip.Prev().Next())
192		}
193
194		port, err := ParseAddrPort(s)
195		if err == nil {
196			checkStringParseRoundTrip(t, port, ParseAddrPort)
197			checkEncoding(t, port)
198		}
199		port = AddrPortFrom(ip, 80)
200		checkStringParseRoundTrip(t, port, ParseAddrPort)
201		checkEncoding(t, port)
202
203		ipp, err := ParsePrefix(s)
204		if err == nil {
205			checkStringParseRoundTrip(t, ipp, ParsePrefix)
206			checkEncoding(t, ipp)
207		}
208		ipp = PrefixFrom(ip, 8)
209		checkStringParseRoundTrip(t, ipp, ParsePrefix)
210		checkEncoding(t, ipp)
211	})
212}
213
214// checkTextMarshaler checks that x's MarshalText and UnmarshalText functions round trip correctly.
215func checkTextMarshaler(t *testing.T, x encoding.TextMarshaler) {
216	buf, err := x.MarshalText()
217	if err != nil {
218		t.Fatal(err)
219	}
220	y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.TextUnmarshaler)
221	err = y.UnmarshalText(buf)
222	if err != nil {
223		t.Logf("(%v).MarshalText() = %q", x, buf)
224		t.Fatalf("(%T).UnmarshalText(%q) = %v", y, buf, err)
225	}
226	e := reflect.ValueOf(y).Elem().Interface()
227	if !reflect.DeepEqual(x, e) {
228		t.Logf("(%v).MarshalText() = %q", x, buf)
229		t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
230		t.Fatalf("MarshalText/UnmarshalText failed to round trip: %#v != %#v", x, e)
231	}
232	buf2, err := y.(encoding.TextMarshaler).MarshalText()
233	if err != nil {
234		t.Logf("(%v).MarshalText() = %q", x, buf)
235		t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
236		t.Fatalf("failed to MarshalText a second time: %v", err)
237	}
238	if !bytes.Equal(buf, buf2) {
239		t.Logf("(%v).MarshalText() = %q", x, buf)
240		t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y)
241		t.Logf("(%v).MarshalText() = %q", y, buf2)
242		t.Fatalf("second MarshalText differs from first: %q != %q", buf, buf2)
243	}
244}
245
246// checkBinaryMarshaler checks that x's MarshalText and UnmarshalText functions round trip correctly.
247func checkBinaryMarshaler(t *testing.T, x encoding.BinaryMarshaler) {
248	buf, err := x.MarshalBinary()
249	if err != nil {
250		t.Fatal(err)
251	}
252	y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.BinaryUnmarshaler)
253	err = y.UnmarshalBinary(buf)
254	if err != nil {
255		t.Logf("(%v).MarshalBinary() = %q", x, buf)
256		t.Fatalf("(%T).UnmarshalBinary(%q) = %v", y, buf, err)
257	}
258	e := reflect.ValueOf(y).Elem().Interface()
259	if !reflect.DeepEqual(x, e) {
260		t.Logf("(%v).MarshalBinary() = %q", x, buf)
261		t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
262		t.Fatalf("MarshalBinary/UnmarshalBinary failed to round trip: %#v != %#v", x, e)
263	}
264	buf2, err := y.(encoding.BinaryMarshaler).MarshalBinary()
265	if err != nil {
266		t.Logf("(%v).MarshalBinary() = %q", x, buf)
267		t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
268		t.Fatalf("failed to MarshalBinary a second time: %v", err)
269	}
270	if !bytes.Equal(buf, buf2) {
271		t.Logf("(%v).MarshalBinary() = %q", x, buf)
272		t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y)
273		t.Logf("(%v).MarshalBinary() = %q", y, buf2)
274		t.Fatalf("second MarshalBinary differs from first: %q != %q", buf, buf2)
275	}
276}
277
278func checkTextMarshalMatchesString(t *testing.T, x netipType) {
279	buf, err := x.MarshalText()
280	if err != nil {
281		t.Fatal(err)
282	}
283	str := x.String()
284	if string(buf) != str {
285		t.Fatalf("%v: MarshalText = %q, String = %q", x, buf, str)
286	}
287}
288
289type appendMarshaler interface {
290	encoding.TextMarshaler
291	AppendTo([]byte) []byte
292}
293
294// checkTextMarshalMatchesAppendTo checks that x's MarshalText matches x's AppendTo.
295func checkTextMarshalMatchesAppendTo(t *testing.T, x appendMarshaler) {
296	buf, err := x.MarshalText()
297	if err != nil {
298		t.Fatal(err)
299	}
300
301	buf2 := make([]byte, 0, len(buf))
302	buf2 = x.AppendTo(buf2)
303	if !bytes.Equal(buf, buf2) {
304		t.Fatalf("%v: MarshalText = %q, AppendTo = %q", x, buf, buf2)
305	}
306}
307
308type netipType interface {
309	encoding.BinaryMarshaler
310	encoding.TextMarshaler
311	fmt.Stringer
312	IsValid() bool
313}
314
315type netipTypeCmp interface {
316	comparable
317	netipType
318}
319
320// checkStringParseRoundTrip checks that x's String method and the provided parse function can round trip correctly.
321func checkStringParseRoundTrip[P netipTypeCmp](t *testing.T, x P, parse func(string) (P, error)) {
322	if !x.IsValid() {
323		// Ignore invalid values.
324		return
325	}
326
327	s := x.String()
328	y, err := parse(s)
329	if err != nil {
330		t.Fatalf("s=%q err=%v", s, err)
331	}
332	if x != y {
333		t.Fatalf("%T round trip identity failure: s=%q x=%#v y=%#v", x, s, x, y)
334	}
335	s2 := y.String()
336	if s != s2 {
337		t.Fatalf("%T String round trip identity failure: s=%#v s2=%#v", x, s, s2)
338	}
339}
340
341func checkEncoding(t *testing.T, x netipType) {
342	if x.IsValid() {
343		checkTextMarshaler(t, x)
344		checkBinaryMarshaler(t, x)
345		checkTextMarshalMatchesString(t, x)
346	}
347
348	if am, ok := x.(appendMarshaler); ok {
349		checkTextMarshalMatchesAppendTo(t, am)
350	}
351}
352