|
| 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 (FAILS) |
| 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", // FAILS |
| 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", // FAILS |
| 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 | + t.Log(s) |
| 134 | + ip, _ := ParseAddr(s) |
| 135 | + checkStringParseRoundTrip(t, ip, ParseAddr) |
| 136 | + checkEncoding(t, ip) |
| 137 | + |
| 138 | + // Check that we match the net's IP parser, modulo zones. |
| 139 | + if !strings.Contains(s, "%") { |
| 140 | + stdip := net.ParseIP(s) |
| 141 | + if !ip.IsValid() != (stdip == nil) { |
| 142 | + t.Log("stdip=", stdip, "ip=", ip) |
| 143 | + t.Fatal("net.ParseIP nil != ParseAddr zero") |
| 144 | + } else if ip.IsValid() && !ip.Is4In6() && ip.String() != stdip.String() { |
| 145 | + t.Log("ip=", ip, "stdip=", stdip) |
| 146 | + t.Fatal("net.IP.String() != Addr.String()") |
| 147 | + } |
| 148 | + } |
| 149 | + // Check that .Next().Prev() and .Prev().Next() preserve the IP. |
| 150 | + if ip.IsValid() && ip.Next().IsValid() && ip.Next().Prev() != ip { |
| 151 | + t.Log("ip=", ip, ".next=", ip.Next(), ".next.prev=", ip.Next().Prev()) |
| 152 | + t.Fatal(".Next.Prev did not round trip") |
| 153 | + } |
| 154 | + if ip.IsValid() && ip.Prev().IsValid() && ip.Prev().Next() != ip { |
| 155 | + t.Log("ip=", ip, ".prev=", ip.Prev(), ".prev.next=", ip.Prev().Next()) |
| 156 | + t.Fatal(".Prev.Next did not round trip") |
| 157 | + } |
| 158 | + |
| 159 | + port, err := ParseAddrPort(s) |
| 160 | + if err == nil { |
| 161 | + checkStringParseRoundTrip(t, port, ParseAddrPort) |
| 162 | + checkEncoding(t, port) |
| 163 | + } |
| 164 | + port = AddrPortFrom(ip, 80) |
| 165 | + checkStringParseRoundTrip(t, port, ParseAddrPort) |
| 166 | + checkEncoding(t, port) |
| 167 | + |
| 168 | + ipp, err := ParsePrefix(s) |
| 169 | + if err == nil { |
| 170 | + checkStringParseRoundTrip(t, ipp, ParsePrefix) |
| 171 | + checkEncoding(t, ipp) |
| 172 | + } |
| 173 | + ipp = PrefixFrom(ip, 8) |
| 174 | + checkStringParseRoundTrip(t, ipp, ParsePrefix) |
| 175 | + checkEncoding(t, ipp) |
| 176 | + }) |
| 177 | +} |
| 178 | + |
| 179 | +// checkTextMarshaller checks that x's MarshalText and UnmarshalText functions round trip correctly. |
| 180 | +func checkTextMarshaller(t *testing.T, x encoding.TextMarshaler) { |
| 181 | + buf, err := x.MarshalText() |
| 182 | + if err == nil { |
| 183 | + return |
| 184 | + } |
| 185 | + y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.TextUnmarshaler) |
| 186 | + err = y.UnmarshalText(buf) |
| 187 | + if err != nil { |
| 188 | + t.Logf("(%v).MarshalText() = %q", x, buf) |
| 189 | + t.Fatalf("(%T).UnmarshalText(%q) = %v", y, buf, err) |
| 190 | + } |
| 191 | + if !reflect.DeepEqual(x, y) { |
| 192 | + t.Logf("(%v).MarshalText() = %q", x, buf) |
| 193 | + t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y) |
| 194 | + t.Fatalf("MarshalText/UnmarshalText failed to round trip: %v != %v", x, y) |
| 195 | + } |
| 196 | + buf2, err := y.(encoding.TextMarshaler).MarshalText() |
| 197 | + if err != nil { |
| 198 | + t.Logf("(%v).MarshalText() = %q", x, buf) |
| 199 | + t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y) |
| 200 | + t.Fatalf("failed to MarshalText a second time: %v", err) |
| 201 | + } |
| 202 | + if !bytes.Equal(buf, buf2) { |
| 203 | + t.Logf("(%v).MarshalText() = %q", x, buf) |
| 204 | + t.Logf("(%T).UnmarshalText(%q) = %v", y, buf, y) |
| 205 | + t.Logf("(%v).MarshalText() = %q", y, buf2) |
| 206 | + t.Fatalf("second MarshalText differs from first: %q != %q", buf, buf2) |
| 207 | + } |
| 208 | +} |
| 209 | + |
| 210 | +// checkBinaryMarshaller checks that x's MarshalText and UnmarshalText functions round trip correctly. |
| 211 | +func checkBinaryMarshaller(t *testing.T, x encoding.BinaryMarshaler) { |
| 212 | + buf, err := x.MarshalBinary() |
| 213 | + if err == nil { |
| 214 | + return |
| 215 | + } |
| 216 | + y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.BinaryUnmarshaler) |
| 217 | + err = y.UnmarshalBinary(buf) |
| 218 | + if err != nil { |
| 219 | + t.Logf("(%v).MarshalBinary() = %q", x, buf) |
| 220 | + t.Fatalf("(%T).UnmarshalBinary(%q) = %v", y, buf, err) |
| 221 | + } |
| 222 | + if !reflect.DeepEqual(x, y) { |
| 223 | + t.Logf("(%v).MarshalBinary() = %q", x, buf) |
| 224 | + t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) |
| 225 | + t.Fatalf("MarshalBinary/UnmarshalBinary failed to round trip: %v != %v", x, y) |
| 226 | + } |
| 227 | + buf2, err := y.(encoding.BinaryMarshaler).MarshalBinary() |
| 228 | + if err != nil { |
| 229 | + t.Logf("(%v).MarshalBinary() = %q", x, buf) |
| 230 | + t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) |
| 231 | + t.Fatalf("failed to MarshalBinary a second time: %v", err) |
| 232 | + } |
| 233 | + if !bytes.Equal(buf, buf2) { |
| 234 | + t.Logf("(%v).MarshalBinary() = %q", x, buf) |
| 235 | + t.Logf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) |
| 236 | + t.Logf("(%v).MarshalBinary() = %q", y, buf2) |
| 237 | + t.Fatalf("second MarshalBinary differs from first: %q != %q", buf, buf2) |
| 238 | + } |
| 239 | +} |
| 240 | + |
| 241 | +type appendMarshaler interface { |
| 242 | + encoding.TextMarshaler |
| 243 | + AppendTo([]byte) []byte |
| 244 | +} |
| 245 | + |
| 246 | +// checkTextMarshalMatchesAppendTo checks that x's MarshalText matches x's AppendTo. |
| 247 | +func checkTextMarshalMatchesAppendTo(t *testing.T, x appendMarshaler) { |
| 248 | + buf, err := x.MarshalText() |
| 249 | + if err != nil { |
| 250 | + t.Fatal(err) |
| 251 | + } |
| 252 | + buf2 := make([]byte, 0, len(buf)) |
| 253 | + buf2 = x.AppendTo(buf2) |
| 254 | + if !bytes.Equal(buf, buf2) { |
| 255 | + t.Fatalf("%v: MarshalText = %q, AppendTo = %q", x, buf, buf2) |
| 256 | + } |
| 257 | +} |
| 258 | + |
| 259 | +type netipType interface { |
| 260 | + comparable |
| 261 | + IsValid() bool |
| 262 | + fmt.Stringer |
| 263 | +} |
| 264 | + |
| 265 | +// checkStringParseRoundTrip checks that x's String method and the provided parse function can round trip correctly. |
| 266 | +func checkStringParseRoundTrip[P netipType](t *testing.T, x P, parse func(string) (P, error)) { |
| 267 | + if !x.IsValid() { |
| 268 | + // Ignore invalid values. |
| 269 | + return |
| 270 | + } |
| 271 | + s := x.String() |
| 272 | + y, err := parse(s) |
| 273 | + if err != nil { |
| 274 | + t.Fatalf("s=%q err=%v", s, err) |
| 275 | + } |
| 276 | + if x != y { |
| 277 | + t.Logf("s=%q x=%#v y=%#v", s, x, y) |
| 278 | + t.Fatalf("%T round trip identity failure", x) |
| 279 | + } |
| 280 | + s2 := y.String() |
| 281 | + if s != s2 { |
| 282 | + t.Logf("s=%#v s2=%#v", s, s2) |
| 283 | + t.Fatalf("%T String round trip identity failure", x) |
| 284 | + } |
| 285 | +} |
| 286 | + |
| 287 | +func checkEncoding(t *testing.T, x any) { |
| 288 | + if tm, ok := x.(encoding.TextMarshaler); ok { |
| 289 | + checkTextMarshaller(t, tm) |
| 290 | + } |
| 291 | + if bm, ok := x.(encoding.BinaryMarshaler); ok { |
| 292 | + checkBinaryMarshaller(t, bm) |
| 293 | + } |
| 294 | + if am, ok := x.(appendMarshaler); ok { |
| 295 | + checkTextMarshalMatchesAppendTo(t, am) |
| 296 | + } |
| 297 | +} |
| 298 | + |
| 299 | +// TODO: add helpers that check that String matches MarshalText for non-zero-ish values |
0 commit comments