|
| 1 | +// Copyright 2020 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 | + |
| 5 | +package netip_test |
| 6 | + |
| 7 | +import ( |
| 8 | + "bytes" |
| 9 | + "encoding" |
| 10 | + "fmt" |
| 11 | + "net" |
| 12 | + . "net/netip" |
| 13 | + "reflect" |
| 14 | + "strings" |
| 15 | + "testing" |
| 16 | +) |
| 17 | + |
| 18 | +var 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 | + |
| 127 | +func 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.Logf("ip=%q stdip=%q", ip, stdip) |
| 142 | + t.Fatal("ParseAddr zero != net.ParseIP nil") |
| 143 | + } |
| 144 | + |
| 145 | + if ip.IsValid() && !ip.Is4In6() { |
| 146 | + if ip.String() != stdip.String() { |
| 147 | + t.Logf("ip=%q stdip=%q", ip, stdip) |
| 148 | + t.Fatal("Addr.String() != net.IP.String()") |
| 149 | + } |
| 150 | + if ip.IsGlobalUnicast() != stdip.IsGlobalUnicast() { |
| 151 | + t.Logf("ip=%q stdip=%q", ip, stdip) |
| 152 | + t.Fatal("Addr.IsGlobalUnicast() != net.IP.IsGlobalUnicast()") |
| 153 | + } |
| 154 | + if ip.IsInterfaceLocalMulticast() != stdip.IsInterfaceLocalMulticast() { |
| 155 | + t.Logf("ip=%q stdip=%q", ip, stdip) |
| 156 | + t.Fatal("Addr.IsInterfaceLocalMulticast() != net.IP.IsInterfaceLocalMulticast()") |
| 157 | + } |
| 158 | + if ip.IsLinkLocalMulticast() != stdip.IsLinkLocalMulticast() { |
| 159 | + t.Logf("ip=%q stdip=%q", ip, stdip) |
| 160 | + t.Fatal("Addr.IsLinkLocalMulticast() != net.IP.IsLinkLocalMulticast()") |
| 161 | + } |
| 162 | + if ip.IsLinkLocalUnicast() != stdip.IsLinkLocalUnicast() { |
| 163 | + t.Logf("ip=%q stdip=%q", ip, stdip) |
| 164 | + t.Fatal("Addr.IsLinkLocalUnicast() != net.IP.IsLinkLocalUnicast()") |
| 165 | + } |
| 166 | + if ip.IsLoopback() != stdip.IsLoopback() { |
| 167 | + t.Logf("ip=%q stdip=%q", ip, stdip) |
| 168 | + t.Fatal("Addr.IsLoopback() != net.IP.IsLoopback()") |
| 169 | + } |
| 170 | + if ip.IsMulticast() != stdip.IsMulticast() { |
| 171 | + t.Logf("ip=%q stdip=%q", ip, stdip) |
| 172 | + t.Fatal("Addr.IsMulticast() != net.IP.IsMulticast()") |
| 173 | + } |
| 174 | + if ip.IsPrivate() != stdip.IsPrivate() { |
| 175 | + t.Logf("ip=%q stdip=%q", ip, stdip) |
| 176 | + t.Fatal("Addr.IsPrivate() != net.IP.IsPrivate()") |
| 177 | + } |
| 178 | + if ip.IsUnspecified() != stdip.IsUnspecified() { |
| 179 | + t.Logf("ip=%q stdip=%q", ip, stdip) |
| 180 | + t.Fatal("Addr.IsUnspecified() != net.IP.IsUnspecified()") |
| 181 | + } |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + // Check that .Next().Prev() and .Prev().Next() preserve the IP. |
| 186 | + if ip.IsValid() && ip.Next().IsValid() && ip.Next().Prev() != ip { |
| 187 | + t.Log("ip=", ip, ".next=", ip.Next(), ".next.prev=", ip.Next().Prev()) |
| 188 | + t.Fatal(".Next.Prev did not round trip") |
| 189 | + } |
| 190 | + if ip.IsValid() && ip.Prev().IsValid() && ip.Prev().Next() != ip { |
| 191 | + t.Log("ip=", ip, ".prev=", ip.Prev(), ".prev.next=", ip.Prev().Next()) |
| 192 | + t.Fatal(".Prev.Next did not round trip") |
| 193 | + } |
| 194 | + |
| 195 | + port, err := ParseAddrPort(s) |
| 196 | + if err == nil { |
| 197 | + checkStringParseRoundTrip(t, port, ParseAddrPort) |
| 198 | + checkEncoding(t, port) |
| 199 | + } |
| 200 | + port = AddrPortFrom(ip, 80) |
| 201 | + checkStringParseRoundTrip(t, port, ParseAddrPort) |
| 202 | + checkEncoding(t, port) |
| 203 | + |
| 204 | + ipp, err := ParsePrefix(s) |
| 205 | + if err == nil { |
| 206 | + checkStringParseRoundTrip(t, ipp, ParsePrefix) |
| 207 | + checkEncoding(t, ipp) |
| 208 | + } |
| 209 | + ipp = PrefixFrom(ip, 8) |
| 210 | + checkStringParseRoundTrip(t, ipp, ParsePrefix) |
| 211 | + checkEncoding(t, ipp) |
| 212 | + }) |
| 213 | +} |
| 214 | + |
| 215 | +// checkTextMarshaller checks that x's MarshalText and UnmarshalText functions round trip correctly. |
| 216 | +func checkTextMarshaller(t *testing.T, x encoding.TextMarshaler) { |
| 217 | + buf, err := x.MarshalText() |
| 218 | + if err == nil { |
| 219 | + return |
| 220 | + } |
| 221 | + y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.TextUnmarshaler) |
| 222 | + err = y.UnmarshalText(buf) |
| 223 | + if err != nil { |
| 224 | + t.Logf("(%v).MarshalText() = %q", x, buf) |
| 225 | + t.Fatalf("(%T).UnmarshalText(%q) = %v", y, buf, err) |
| 226 | + } |
| 227 | + if !reflect.DeepEqual(x, y) { |
| 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, y) |
| 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 | +// checkBinaryMarshaller checks that x's MarshalText and UnmarshalText functions round trip correctly. |
| 247 | +func checkBinaryMarshaller(t *testing.T, x encoding.BinaryMarshaler) { |
| 248 | + buf, err := x.MarshalBinary() |
| 249 | + if err == nil { |
| 250 | + return |
| 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 | + if !reflect.DeepEqual(x, y) { |
| 259 | + t.Logf("(%v).MarshalBinary() = %q", x, buf) |
| 260 | + t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) |
| 261 | + t.Fatalf("MarshalBinary/UnmarshalBinary failed to round trip: %v != %v", x, y) |
| 262 | + } |
| 263 | + buf2, err := y.(encoding.BinaryMarshaler).MarshalBinary() |
| 264 | + if err != nil { |
| 265 | + t.Logf("(%v).MarshalBinary() = %q", x, buf) |
| 266 | + t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) |
| 267 | + t.Fatalf("failed to MarshalBinary a second time: %v", err) |
| 268 | + } |
| 269 | + if !bytes.Equal(buf, buf2) { |
| 270 | + t.Logf("(%v).MarshalBinary() = %q", x, buf) |
| 271 | + t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) |
| 272 | + t.Logf("(%v).MarshalBinary() = %q", y, buf2) |
| 273 | + t.Fatalf("second MarshalBinary differs from first: %q != %q", buf, buf2) |
| 274 | + } |
| 275 | +} |
| 276 | + |
| 277 | +func checkTextMarshalMatchesString(t *testing.T, x netipType) { |
| 278 | + if !x.IsValid() { |
| 279 | + // Invalid values will produce different outputs from |
| 280 | + // MarshalText and String. |
| 281 | + return |
| 282 | + } |
| 283 | + |
| 284 | + buf, err := x.MarshalText() |
| 285 | + if err != nil { |
| 286 | + t.Fatal(err) |
| 287 | + } |
| 288 | + str := x.String() |
| 289 | + if string(buf) != str { |
| 290 | + t.Fatalf("%v: MarshalText = %q, String = %q", x, buf, str) |
| 291 | + } |
| 292 | +} |
| 293 | + |
| 294 | +type appendMarshaler interface { |
| 295 | + encoding.TextMarshaler |
| 296 | + AppendTo([]byte) []byte |
| 297 | +} |
| 298 | + |
| 299 | +// checkTextMarshalMatchesAppendTo checks that x's MarshalText matches x's AppendTo. |
| 300 | +func checkTextMarshalMatchesAppendTo(t *testing.T, x appendMarshaler) { |
| 301 | + buf, err := x.MarshalText() |
| 302 | + if err != nil { |
| 303 | + t.Fatal(err) |
| 304 | + } |
| 305 | + |
| 306 | + buf2 := make([]byte, 0, len(buf)) |
| 307 | + buf2 = x.AppendTo(buf2) |
| 308 | + if !bytes.Equal(buf, buf2) { |
| 309 | + t.Fatalf("%v: MarshalText = %q, AppendTo = %q", x, buf, buf2) |
| 310 | + } |
| 311 | +} |
| 312 | + |
| 313 | +type netipType interface { |
| 314 | + encoding.BinaryMarshaler |
| 315 | + encoding.TextMarshaler |
| 316 | + fmt.Stringer |
| 317 | + IsValid() bool |
| 318 | +} |
| 319 | + |
| 320 | +type netipTypeCmp interface { |
| 321 | + comparable |
| 322 | + netipType |
| 323 | +} |
| 324 | + |
| 325 | +// checkStringParseRoundTrip checks that x's String method and the provided parse function can round trip correctly. |
| 326 | +func checkStringParseRoundTrip[P netipTypeCmp](t *testing.T, x P, parse func(string) (P, error)) { |
| 327 | + if !x.IsValid() { |
| 328 | + // Ignore invalid values. |
| 329 | + return |
| 330 | + } |
| 331 | + |
| 332 | + s := x.String() |
| 333 | + y, err := parse(s) |
| 334 | + if err != nil { |
| 335 | + t.Fatalf("s=%q err=%v", s, err) |
| 336 | + } |
| 337 | + if x != y { |
| 338 | + t.Logf("s=%q x=%#v y=%#v", s, x, y) |
| 339 | + t.Fatalf("%T round trip identity failure", x) |
| 340 | + } |
| 341 | + s2 := y.String() |
| 342 | + if s != s2 { |
| 343 | + t.Logf("s=%#v s2=%#v", s, s2) |
| 344 | + t.Fatalf("%T String round trip identity failure", x) |
| 345 | + } |
| 346 | +} |
| 347 | + |
| 348 | +func checkEncoding(t *testing.T, x netipType) { |
| 349 | + checkTextMarshaller(t, x) |
| 350 | + checkBinaryMarshaller(t, x) |
| 351 | + checkTextMarshalMatchesString(t, x) |
| 352 | + if am, ok := x.(appendMarshaler); ok { |
| 353 | + checkTextMarshalMatchesAppendTo(t, am) |
| 354 | + } |
| 355 | +} |
0 commit comments