// Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package netip_test import ( "bytes" "encoding" "fmt" "net" . "net/netip" "reflect" "strings" "testing" ) var corpus = []string{ // Basic zero IPv4 address. "0.0.0.0", // Basic non-zero IPv4 address. "192.168.140.255", // IPv4 address in windows-style "print all the digits" form. "010.000.015.001", // IPv4 address with a silly amount of leading zeros. "000001.00000002.00000003.000000004", // 4-in-6 with octet with leading zero "::ffff:1.2.03.4", // Basic zero IPv6 address. "::", // Localhost IPv6. "::1", // Fully expanded IPv6 address. "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b", // IPv6 with elided fields in the middle. "fd7a:115c::626b:430b", // IPv6 with elided fields at the end. "fd7a:115c:a1e0:ab12:4843:cd96::", // IPv6 with single elided field at the end. "fd7a:115c:a1e0:ab12:4843:cd96:626b::", "fd7a:115c:a1e0:ab12:4843:cd96:626b:0", // IPv6 with single elided field in the middle. "fd7a:115c:a1e0::4843:cd96:626b:430b", "fd7a:115c:a1e0:0:4843:cd96:626b:430b", // IPv6 with the trailing 32 bits written as IPv4 dotted decimal. (4in6) "::ffff:192.168.140.255", "::ffff:192.168.140.255", // IPv6 with a zone specifier. "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b%eth0", // IPv6 with dotted decimal and zone specifier. "1:2::ffff:192.168.140.255%eth1", "1:2::ffff:c0a8:8cff%eth1", // IPv6 with capital letters. "FD9E:1A04:F01D::1", "fd9e:1a04:f01d::1", // Empty string. "", // Garbage non-IP. "bad", // Single number. Some parsers accept this as an IPv4 address in // big-endian uint32 form, but we don't. "1234", // IPv4 with a zone specifier. "1.2.3.4%eth0", // IPv4 field must have at least one digit. ".1.2.3", "1.2.3.", "1..2.3", // IPv4 address too long. "1.2.3.4.5", // IPv4 in dotted octal form. "0300.0250.0214.0377", // IPv4 in dotted hex form. "0xc0.0xa8.0x8c.0xff", // IPv4 in class B form. "192.168.12345", // IPv4 in class B form, with a small enough number to be // parseable as a regular dotted decimal field. "127.0.1", // IPv4 in class A form. "192.1234567", // IPv4 in class A form, with a small enough number to be // parseable as a regular dotted decimal field. "127.1", // IPv4 field has value >255. "192.168.300.1", // IPv4 with too many fields. "192.168.0.1.5.6", // IPv6 with not enough fields. "1:2:3:4:5:6:7", // IPv6 with too many fields. "1:2:3:4:5:6:7:8:9", // IPv6 with 8 fields and a :: expander. "1:2:3:4::5:6:7:8", // IPv6 with a field bigger than 2b. "fe801::1", // IPv6 with non-hex values in field. "fe80:tail:scal:e::", // IPv6 with a zone delimiter but no zone. "fe80::1%", // IPv6 with a zone specifier of zero. "::ffff:0:0%0", // IPv6 (without ellipsis) with too many fields for trailing embedded IPv4. "ffff:ffff:ffff:ffff:ffff:ffff:ffff:192.168.140.255", // IPv6 (with ellipsis) with too many fields for trailing embedded IPv4. "ffff::ffff:ffff:ffff:ffff:ffff:ffff:192.168.140.255", // IPv6 with invalid embedded IPv4. "::ffff:192.168.140.bad", // IPv6 with multiple ellipsis ::. "fe80::1::1", // IPv6 with invalid non hex/colon character. "fe80:1?:1", // IPv6 with truncated bytes after single colon. "fe80:", // AddrPort strings. "1.2.3.4:51820", "[fd7a:115c:a1e0:ab12:4843:cd96:626b:430b]:80", "[::ffff:c000:0280]:65535", "[::ffff:c000:0280%eth0]:1", // Prefix strings. "1.2.3.4/24", "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118", "::ffff:c000:0280/96", "::ffff:c000:0280%eth0/37", } func FuzzParse(f *testing.F) { for _, seed := range corpus { f.Add(seed) } f.Fuzz(func(t *testing.T, s string) { ip, _ := ParseAddr(s) checkStringParseRoundTrip(t, ip, ParseAddr) checkEncoding(t, ip) // Check that we match the net's IP parser, modulo zones. if !strings.Contains(s, "%") { stdip := net.ParseIP(s) if !ip.IsValid() != (stdip == nil) { t.Errorf("ParseAddr zero != net.ParseIP nil: ip=%q stdip=%q", ip, stdip) } if ip.IsValid() && !ip.Is4In6() { buf, err := ip.MarshalText() if err != nil { t.Fatal(err) } buf2, err := stdip.MarshalText() if err != nil { t.Fatal(err) } if !bytes.Equal(buf, buf2) { t.Errorf("Addr.MarshalText() != net.IP.MarshalText(): ip=%q stdip=%q", ip, stdip) } if ip.String() != stdip.String() { t.Errorf("Addr.String() != net.IP.String(): ip=%q stdip=%q", ip, stdip) } if ip.IsGlobalUnicast() != stdip.IsGlobalUnicast() { t.Errorf("Addr.IsGlobalUnicast() != net.IP.IsGlobalUnicast(): ip=%q stdip=%q", ip, stdip) } if ip.IsInterfaceLocalMulticast() != stdip.IsInterfaceLocalMulticast() { t.Errorf("Addr.IsInterfaceLocalMulticast() != net.IP.IsInterfaceLocalMulticast(): ip=%q stdip=%q", ip, stdip) } if ip.IsLinkLocalMulticast() != stdip.IsLinkLocalMulticast() { t.Errorf("Addr.IsLinkLocalMulticast() != net.IP.IsLinkLocalMulticast(): ip=%q stdip=%q", ip, stdip) } if ip.IsLinkLocalUnicast() != stdip.IsLinkLocalUnicast() { t.Errorf("Addr.IsLinkLocalUnicast() != net.IP.IsLinkLocalUnicast(): ip=%q stdip=%q", ip, stdip) } if ip.IsLoopback() != stdip.IsLoopback() { t.Errorf("Addr.IsLoopback() != net.IP.IsLoopback(): ip=%q stdip=%q", ip, stdip) } if ip.IsMulticast() != stdip.IsMulticast() { t.Errorf("Addr.IsMulticast() != net.IP.IsMulticast(): ip=%q stdip=%q", ip, stdip) } if ip.IsPrivate() != stdip.IsPrivate() { t.Errorf("Addr.IsPrivate() != net.IP.IsPrivate(): ip=%q stdip=%q", ip, stdip) } if ip.IsUnspecified() != stdip.IsUnspecified() { t.Errorf("Addr.IsUnspecified() != net.IP.IsUnspecified(): ip=%q stdip=%q", ip, stdip) } } } // Check that .Next().Prev() and .Prev().Next() preserve the IP. if ip.IsValid() && ip.Next().IsValid() && ip.Next().Prev() != ip { t.Errorf(".Next.Prev did not round trip: ip=%q .next=%q .next.prev=%q", ip, ip.Next(), ip.Next().Prev()) } if ip.IsValid() && ip.Prev().IsValid() && ip.Prev().Next() != ip { t.Errorf(".Prev.Next did not round trip: ip=%q .prev=%q .prev.next=%q", ip, ip.Prev(), ip.Prev().Next()) } port, err := ParseAddrPort(s) if err == nil { checkStringParseRoundTrip(t, port, ParseAddrPort) checkEncoding(t, port) } port = AddrPortFrom(ip, 80) checkStringParseRoundTrip(t, port, ParseAddrPort) checkEncoding(t, port) ipp, err := ParsePrefix(s) if err == nil { checkStringParseRoundTrip(t, ipp, ParsePrefix) checkEncoding(t, ipp) } ipp = PrefixFrom(ip, 8) checkStringParseRoundTrip(t, ipp, ParsePrefix) checkEncoding(t, ipp) }) } // checkTextMarshaler checks that x's MarshalText and UnmarshalText functions round trip correctly. func checkTextMarshaler(t *testing.T, x encoding.TextMarshaler) { buf, err := x.MarshalText() if err != nil { t.Fatal(err) } y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.TextUnmarshaler) err = y.UnmarshalText(buf) if err != nil { t.Logf("(%v).MarshalText() = %q", x, buf) t.Fatalf("(%T).UnmarshalText(%q) = %v", y, buf, err) } e := reflect.ValueOf(y).Elem().Interface() if !reflect.DeepEqual(x, e) { t.Logf("(%v).MarshalText() = %q", x, buf) t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y) t.Fatalf("MarshalText/UnmarshalText failed to round trip: %#v != %#v", x, e) } buf2, err := y.(encoding.TextMarshaler).MarshalText() if err != nil { t.Logf("(%v).MarshalText() = %q", x, buf) t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y) t.Fatalf("failed to MarshalText a second time: %v", err) } if !bytes.Equal(buf, buf2) { t.Logf("(%v).MarshalText() = %q", x, buf) t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y) t.Logf("(%v).MarshalText() = %q", y, buf2) t.Fatalf("second MarshalText differs from first: %q != %q", buf, buf2) } } // checkBinaryMarshaler checks that x's MarshalText and UnmarshalText functions round trip correctly. func checkBinaryMarshaler(t *testing.T, x encoding.BinaryMarshaler) { buf, err := x.MarshalBinary() if err != nil { t.Fatal(err) } y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.BinaryUnmarshaler) err = y.UnmarshalBinary(buf) if err != nil { t.Logf("(%v).MarshalBinary() = %q", x, buf) t.Fatalf("(%T).UnmarshalBinary(%q) = %v", y, buf, err) } e := reflect.ValueOf(y).Elem().Interface() if !reflect.DeepEqual(x, e) { t.Logf("(%v).MarshalBinary() = %q", x, buf) t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) t.Fatalf("MarshalBinary/UnmarshalBinary failed to round trip: %#v != %#v", x, e) } buf2, err := y.(encoding.BinaryMarshaler).MarshalBinary() if err != nil { t.Logf("(%v).MarshalBinary() = %q", x, buf) t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) t.Fatalf("failed to MarshalBinary a second time: %v", err) } if !bytes.Equal(buf, buf2) { t.Logf("(%v).MarshalBinary() = %q", x, buf) t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) t.Logf("(%v).MarshalBinary() = %q", y, buf2) t.Fatalf("second MarshalBinary differs from first: %q != %q", buf, buf2) } } func checkTextMarshalMatchesString(t *testing.T, x netipType) { buf, err := x.MarshalText() if err != nil { t.Fatal(err) } str := x.String() if string(buf) != str { t.Fatalf("%v: MarshalText = %q, String = %q", x, buf, str) } } type appendMarshaler interface { encoding.TextMarshaler AppendTo([]byte) []byte } // checkTextMarshalMatchesAppendTo checks that x's MarshalText matches x's AppendTo. func checkTextMarshalMatchesAppendTo(t *testing.T, x appendMarshaler) { buf, err := x.MarshalText() if err != nil { t.Fatal(err) } buf2 := make([]byte, 0, len(buf)) buf2 = x.AppendTo(buf2) if !bytes.Equal(buf, buf2) { t.Fatalf("%v: MarshalText = %q, AppendTo = %q", x, buf, buf2) } } type netipType interface { encoding.BinaryMarshaler encoding.TextMarshaler fmt.Stringer IsValid() bool } type netipTypeCmp interface { comparable netipType } // checkStringParseRoundTrip checks that x's String method and the provided parse function can round trip correctly. func checkStringParseRoundTrip[P netipTypeCmp](t *testing.T, x P, parse func(string) (P, error)) { if !x.IsValid() { // Ignore invalid values. return } s := x.String() y, err := parse(s) if err != nil { t.Fatalf("s=%q err=%v", s, err) } if x != y { t.Fatalf("%T round trip identity failure: s=%q x=%#v y=%#v", x, s, x, y) } s2 := y.String() if s != s2 { t.Fatalf("%T String round trip identity failure: s=%#v s2=%#v", x, s, s2) } } func checkEncoding(t *testing.T, x netipType) { if x.IsValid() { checkTextMarshaler(t, x) checkBinaryMarshaler(t, x) checkTextMarshalMatchesString(t, x) } if am, ok := x.(appendMarshaler); ok { checkTextMarshalMatchesAppendTo(t, am) } }