From 1e1430f727e0faba0668bd717b63b5661db7a6bd Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 2 Apr 2021 11:39:48 -0700 Subject: [PATCH 01/73] README: document stance on quality and dependencies Rename "Maturity" section to "Status", move it to the top, and indicate that we take quality and dependencies seriously. Also, remove the TODO from the README. Signed-off-by: Josh Bleecher Snyder --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a3a8a03..65dd708 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,14 @@ This is a package containing a new IP address type for Go. See its docs: https://pkg.go.dev/inet.af/netaddr +## Status + +This package is mature, optimized, and used heavily in production at [Tailscale](https://tailscale.com). +However, API stability is not yet guaranteed. + +netaddr is intended to be a core, low-level package. +We take code review, testing, dependencies, and performance seriously, similar to Go's standard library or the golang.org/x repos. + ## Motivation See https://tailscale.com/blog/netaddr-new-ip-type-for-go/ for a long @@ -17,13 +25,6 @@ Other links: * https://github.com/golang/go/issues/18757 ("net: ParseIP should return an error, like other Parse functions") * https://github.com/golang/go/issues/37921 ("net: Unable to reliably distinguish IPv4-mapped-IPv6 addresses from regular IPv4 addresses") * merges net.IPAddr and net.IP (which the Go net package is a little torn between for legacy reasons) -* ... -* TODO: finish this list - -## Maturity - -This package is mature, optimized, and used heavily in production at [Tailscale](https://tailscale.com). -However, API stability is not yet guaranteed. ## Testing From 0326f649e067dd38b3f5427e9b6402738e8ed9c8 Mon Sep 17 00:00:00 2001 From: Lars Meyer Date: Tue, 13 Apr 2021 10:04:53 +0200 Subject: [PATCH 02/73] use https instead of ssh for submodule Signed-off-by: Lars Meyer --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 31357d5..e1ebefa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "corpus"] path = corpus - url = git@github.com:inetaf/netaddr-corpus.git + url = https://github.com/inetaf/netaddr-corpus.git From ec375f7b1492ec13150033498a5877eb550d8eac Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 18 Mar 2021 17:39:30 +0100 Subject: [PATCH 03/73] Add tools package to force go-fuzz dependency This will make sure go-fuzz will be added to go.mod as a dependency. See https://github.com/inetaf/netaddr/pull/150#pullrequestreview-613888945 Signed-off-by: Tobias Klauser --- tools/tools.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tools/tools.go diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 0000000..924fb4f --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,7 @@ +// +build tools + +package tools + +import ( + _ "github.com/dvyukov/go-fuzz/go-fuzz-build" +) From daf4c9bcf1f969656dc0eeb2b5d1d690e6770b6d Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 18 Mar 2021 17:41:44 +0100 Subject: [PATCH 04/73] Run go mod tidy as part of GitHub actions Make sure go.mod and go.sum match the current source in the module. Running `go mod tidy` also removes the indirect dependency on github.com/dvyukov/go-fuzz which was probably added when running the `go get` commands mentioned in the README to install the `go-fuzz` and `go-fuzz-build` in the package directory. Signed-off-by: Tobias Klauser --- .github/workflows/linux.yml | 6 ++++++ go.mod | 3 ++- go.sum | 35 ++++++++++++++++++++++++++++------- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6436599..4864ae7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -26,5 +26,11 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@v1 + - name: Check Go modules + if: matrix.go-version == '1.16' + run: | + go mod tidy + git diff --exit-code + - name: Run tests on linux run: go test ./... diff --git a/go.mod b/go.mod index 34323a7..0097802 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module inet.af/netaddr go 1.12 require ( - github.com/dvyukov/go-fuzz v0.0.0-20201127111758-49e582c6c23d // indirect + github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 go4.org/intern v0.0.0-20210108033219-3eb7198706b2 go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect + golang.org/x/tools v0.1.0 // indirect ) diff --git a/go.sum b/go.sum index d481baf..2995d08 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,33 @@ -github.com/dvyukov/go-fuzz v0.0.0-20201127111758-49e582c6c23d h1:e1v4V9Heb+c4xQCCONROFvlzNs6Gq8aRZRwt+WzSEqY= -github.com/dvyukov/go-fuzz v0.0.0-20201127111758-49e582c6c23d/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= -go4.org/intern v0.0.0-20201223054237-ef8cbcb8edd7 h1:yeDrXaQ3VRXbTN7lHj70DxW4LdPow83MVwPPRjpP70U= -go4.org/intern v0.0.0-20201223054237-ef8cbcb8edd7/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= -go4.org/intern v0.0.0-20210101010959-7cab76ca296a h1:28p852HIWWaOS019DYK/A3yTmpm1HJaUce63pvll4C8= -go4.org/intern v0.0.0-20210101010959-7cab76ca296a/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= +github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 h1:q1oJaUPdmpDm/VyXosjgPgr6wS7c5iV2p0PwJD73bUI= +github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg= go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e h1:ExUmGi0ZsQmiVo9giDQqXkr7vreeXPMkOGIusfsfbzI= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 78c777480f22def9d22d7a064ab204cb102b2ceb Mon Sep 17 00:00:00 2001 From: Maisem Ali Date: Fri, 16 Apr 2021 09:42:07 -0700 Subject: [PATCH 05/73] netaddr: have IP implement BinaryMarshaler/BinaryUnmarshaler. Signed-off-by: Maisem Ali --- fuzz.go | 13 +++++++++++ netaddr.go | 36 ++++++++++++++++++++++++++++ netaddr_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) diff --git a/fuzz.go b/fuzz.go index 0d0f55f..f40a004 100644 --- a/fuzz.go +++ b/fuzz.go @@ -31,6 +31,19 @@ func Fuzz(b []byte) int { if s2 != ip2.String() { panic("IP String round trip identity failure") } + + b, err := ip.MarshalBinary() + if err != nil { + panic(err) + } + var ip3 IP + if err := ip3.UnmarshalBinary(b); err != nil { + panic(err) + } + if ip != ip3 { + fmt.Printf("ip=%#v ip3=%#v\n", ip, ip3) + panic("IP binary marshal round trip identity failure") + } } // Check that we match the standard library's IP parser, modulo zones. if !strings.Contains(s, "%") { diff --git a/netaddr.go b/netaddr.go index 5923038..4e44e59 100644 --- a/netaddr.go +++ b/netaddr.go @@ -805,6 +805,42 @@ func (ip *IP) UnmarshalText(text []byte) error { return err } +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (ip IP) MarshalBinary() ([]byte, error) { + if ip.Is4() { + b := ip.As4() + return b[:], nil + } + b16 := ip.As16() + b := b16[:] + if z := ip.Zone(); z != "" { + b = append(b, []byte(z)...) + } + return b, nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +func (ip *IP) UnmarshalBinary(b []byte) error { + if ip.z != z0 { + return errors.New("netaddr: refusing to Unmarshal into non-zero IP") + } + n := len(b) + switch { + case n == 0: + return nil + case n == 4: + *ip = IPv4(b[0], b[1], b[2], b[3]) + return nil + case n == 16: + *ip = ipv6Slice(b) + return nil + case n > 16: + *ip = ipv6Slice(b[:16]).WithZone(string(b[16:])) + return nil + } + return fmt.Errorf("netaddr: unexpected ip size: %v", len(b)) +} + // IPPort is an IP & port number. // // It's meant to be used as a value type. diff --git a/netaddr_test.go b/netaddr_test.go index ad5fd3a..a8f23cb 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -310,6 +310,41 @@ func TestParseIP(t *testing.T) { } } +func TestIPMarshalUnmarshalBinary(t *testing.T) { + tests := []struct { + ip string + wantSize int + }{ + {"1.2.3.4", 4}, + {"fd7a:115c:a1e0:ab12:4843:cd96:626b:430b", 16}, + {"::ffff:c000:0280", 16}, + {"::ffff:c000:0280%eth0", 20}, + } + for _, tc := range tests { + ip := mustIP(tc.ip) + b, err := ip.MarshalBinary() + if err != nil { + t.Fatal(err) + } + if len(b) != tc.wantSize { + t.Fatalf("ipv4 got %d; want %d", len(b), tc.wantSize) + } + var ip2 IP + if err := ip2.UnmarshalBinary(b); err != nil { + t.Fatal(err) + } + if ip != ip2 { + t.Fatalf("got %v; want %v", ip2, ip) + } + } + + // Cannot unmarshal into a non-zero IP + ip := MustParseIP("1.2.3.4") + if err := ip.UnmarshalBinary([]byte{1, 1, 1, 1}); err == nil { + t.Fatal("unmarshaled into non-empty IP") + } +} + func TestIPMarshalUnmarshal(t *testing.T) { // This only tests the cases where Marshal/Unmarshal diverges from // the behavior of ParseIP/String. For the rest of the test cases, @@ -1615,6 +1650,33 @@ func mustIPs(strs ...string) []IP { return res } +func BenchmarkBinaryMarshalRoundTrip(b *testing.B) { + b.ReportAllocs() + tests := []struct { + name string + ip string + }{ + {"ipv4", "1.2.3.4"}, + {"ipv6", "2001:db8::1"}, + {"ipv6+zone", "2001:db8::1%eth0"}, + } + for _, tc := range tests { + b.Run(tc.name, func(b *testing.B) { + ip := mustIP(tc.ip) + for i := 0; i < b.N; i++ { + bt, err := ip.MarshalBinary() + if err != nil { + b.Fatal(err) + } + var ip2 IP + if err := ip2.UnmarshalBinary(bt); err != nil { + b.Fatal(err) + } + } + }) + } +} + func BenchmarkStdIPv4(b *testing.B) { b.ReportAllocs() ips := []net.IP{} From 1d252cf8125ef5a85fd17004eaa0077db5acc6dd Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Fri, 30 Apr 2021 16:16:28 -0400 Subject: [PATCH 06/73] netaddr: use value receiver for IP.Netmask (#158) Signed-off-by: Matt Layher --- netaddr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netaddr.go b/netaddr.go index 4e44e59..3bbfa21 100644 --- a/netaddr.go +++ b/netaddr.go @@ -613,7 +613,7 @@ func (ip IP) Prefix(bits uint8) (IPPrefix, error) { // zero value, a zero-value IPPrefix and a nil error are returned. If the // netmask length is not 4 for IPv4 or 16 for IPv6, an error is // returned. If the netmask is non-contiguous, an error is returned. -func (ip *IP) Netmask(mask []byte) (IPPrefix, error) { +func (ip IP) Netmask(mask []byte) (IPPrefix, error) { l := len(mask) switch ip.z { From a0a85d8cfa2958a0331c09c29fefc183ee43a4c8 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 7 May 2021 16:37:33 -0700 Subject: [PATCH 07/73] add benchmark for IPPrefix.MarshalText Signed-off-by: Josh Bleecher Snyder --- netaddr_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/netaddr_test.go b/netaddr_test.go index a8f23cb..123eaca 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -1885,6 +1885,14 @@ func BenchmarkIPPrefixMasking(b *testing.B) { } } +func BenchmarkIPPrefixMarshalText(b *testing.B) { + b.ReportAllocs() + ipp := MustParseIPPrefix("66.55.44.33/22") + for i := 0; i < b.N; i++ { + sinkBytes, _ = ipp.MarshalText() + } +} + func BenchmarkParseIPPort(b *testing.B) { for _, test := range parseBenchInputs { var ipp string @@ -2475,6 +2483,7 @@ var ( sinkIP4 [4]byte sinkBool bool sinkString string + sinkBytes []byte sinkUDPAddr = &net.UDPAddr{IP: make(net.IP, 0, 16)} ) From 243b617ad87aff6fc54831c5072ba1626b707fa0 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 7 May 2021 17:13:16 -0700 Subject: [PATCH 08/73] optimize IPPrefix.MarshalText MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This method accounted for about 2.5% of tailscaled's allocs, even while running iperf. Make it cheaper. We might eventually want to expose an IP.AppendTo method, but for now, internal-only append methods suffice. name old time/op new time/op delta IPPrefixMarshalText-8 222ns ± 0% 25ns ± 0% -88.60% (p=0.003 n=9+4) name old alloc/op new alloc/op delta IPPrefixMarshalText-8 72.0B ± 0% 24.0B ± 0% -66.67% (p=0.000 n=10+5) name old allocs/op new allocs/op delta IPPrefixMarshalText-8 4.00 ± 0% 1.00 ± 0% -75.00% (p=0.000 n=10+5) The extra function call brings a very small penalty to IP.String: name old time/op new time/op delta IPString/v4-8 22.0ns ± 0% 22.6ns ± 0% +2.66% (p=0.008 n=5+5) IPString/v6-8 61.7ns ± 0% 62.3ns ± 0% +1.01% (p=0.008 n=5+5) IPString/v6_ellipsis-8 56.6ns ± 0% 57.4ns ± 0% +1.56% (p=0.008 n=5+5) IPString/v6_v4-8 58.7ns ± 0% 59.2ns ± 2% +0.87% (p=0.040 n=5+5) IPString/v6_zone-8 57.3ns ± 0% 59.1ns ± 0% +3.13% (p=0.008 n=5+5) Signed-off-by: Josh Bleecher Snyder --- inlining_test.go | 1 - netaddr.go | 53 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/inlining_test.go b/inlining_test.go index c416759..e83566d 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -75,7 +75,6 @@ func TestInlining(t *testing.T) { "IPPort.UDPAddrAt", "IPPrefix.IsSingleIP", "IPPrefix.IsZero", - "IPPrefix.MarshalText", "IPPrefix.Masked", "IPPrefix.Valid", "IPRange.Prefixes", diff --git a/netaddr.go b/netaddr.go index 3bbfa21..ca10e50 100644 --- a/netaddr.go +++ b/netaddr.go @@ -723,6 +723,11 @@ func appendHex(b []byte, x uint16) []byte { func (ip IP) string4() string { const max = len("255.255.255.255") ret := make([]byte, 0, max) + ret = ip.appendTo4(ret) + return string(ret) +} + +func (ip IP) appendTo4(ret []byte) []byte { ret = appendDecimal(ret, ip.v4(0)) ret = append(ret, '.') ret = appendDecimal(ret, ip.v4(1)) @@ -730,7 +735,7 @@ func (ip IP) string4() string { ret = appendDecimal(ret, ip.v4(2)) ret = append(ret, '.') ret = appendDecimal(ret, ip.v4(3)) - return string(ret) + return ret } // string6 formats ip in IPv6 textual representation. It follows the @@ -739,6 +744,20 @@ func (ip IP) string4() string { // zeros, use :: to elide the longest run of zeros, and don't use :: // to compact a single zero field. func (ip IP) string6() string { + // Use a zone with a "plausibly long" name, so that most zone-ful + // IP addresses won't require additional allocation. + // + // The compiler does a cool optimization here, where ret ends up + // stack-allocated and so the only allocation this function does + // is to construct the returned string. As such, it's okay to be a + // bit greedy here, size-wise. + const max = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0") + ret := make([]byte, 0, max) + ret = ip.appendTo6(ret) + return string(ret) +} + +func (ip IP) appendTo6(ret []byte) []byte { zeroStart, zeroEnd := uint8(255), uint8(255) for i := uint8(0); i < 8; i++ { j := i @@ -750,15 +769,6 @@ func (ip IP) string6() string { } } - // Use a zone with a "plausibly long" name, so that most zone-ful - // IP addresses won't require additional allocation. - // - // The compiler does a cool optimization here, where ret ends up - // stack-allocated and so the only allocation this function does - // is to construct the returned string. As such, it's okay to be a - // bit greedy here, size-wise. - const max = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0") - ret := make([]byte, 0, max) for i := uint8(0); i < 8; i++ { if i == zeroStart { ret = append(ret, ':', ':') @@ -777,7 +787,7 @@ func (ip IP) string6() string { ret = append(ret, '%') ret = append(ret, ip.Zone()...) } - return string(ret) + return ret } // MarshalText implements the encoding.TextMarshaler interface, @@ -1200,11 +1210,28 @@ func (p IPPrefix) Overlaps(o IPPrefix) bool { // The encoding is the same as returned by String, with one exception: // If p is the zero value, the encoding is the empty string. func (p IPPrefix) MarshalText() ([]byte, error) { - if p == (IPPrefix{}) { + if p.IsZero() { return []byte(""), nil } + if !p.Valid() { + return []byte("invalid IP prefix"), nil + } - return []byte(p.String()), nil + // p.IP is non-zero, because p is valid. + var b []byte + if p.IP.z == z4 { + max := len("255.255.255.255/32") + b = make([]byte, 0, max) + b = p.IP.appendTo4(b) + } else { + max := len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0/128") + b = make([]byte, 0, max) + b = p.IP.appendTo6(b) + } + + b = append(b, '/') + b = appendDecimal(b, p.Bits) + return b, nil } // UnmarshalText implements the encoding.TextUnmarshaler interface. From da1c2a70a83defa9daaa41888a584a0f3bbec01d Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 7 May 2021 17:58:20 -0700 Subject: [PATCH 09/73] optimize IP.MarshalText MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Easy, now that we have IP.appendTo{4,6}. name old time/op new time/op delta IPMarshalText-8 41.6ns ± 0% 21.3ns ± 0% -48.79% (p=0.008 n=5+5) name old alloc/op new alloc/op delta IPMarshalText-8 32.0B ± 0% 16.0B ± 0% -50.00% (p=0.008 n=5+5) name old allocs/op new allocs/op delta IPMarshalText-8 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.008 n=5+5) Signed-off-by: Josh Bleecher Snyder --- inlining_test.go | 1 - netaddr.go | 12 ++++++++++-- netaddr_test.go | 8 ++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/inlining_test.go b/inlining_test.go index e83566d..4f90e00 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -60,7 +60,6 @@ func TestInlining(t *testing.T) { "IP.IsZero", "IP.Less", "IP.lessOrEq", - "IP.MarshalText", "IP.Next", "IP.Prior", "IP.Unmap", diff --git a/netaddr.go b/netaddr.go index ca10e50..7191018 100644 --- a/netaddr.go +++ b/netaddr.go @@ -794,10 +794,18 @@ func (ip IP) appendTo6(ret []byte) []byte { // The encoding is the same as returned by String, with one exception: // If ip is the zero value, the encoding is the empty string. func (ip IP) MarshalText() ([]byte, error) { - if ip.z == z0 { + switch ip.z { + case z0: return []byte(""), nil + case z4: + max := len("255.255.255.255") + b := make([]byte, 0, max) + return ip.appendTo4(b), nil + default: + max := len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0") + b := make([]byte, 0, max) + return ip.appendTo6(b), nil } - return []byte(ip.String()), nil } // UnmarshalText implements the encoding.TextUnmarshaler interface. diff --git a/netaddr_test.go b/netaddr_test.go index 123eaca..bd305f6 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -1821,6 +1821,14 @@ func BenchmarkIPString(b *testing.B) { } } +func BenchmarkIPMarshalText(b *testing.B) { + b.ReportAllocs() + ip := MustParseIP("66.55.44.33") + for i := 0; i < b.N; i++ { + sinkBytes, _ = ip.MarshalText() + } +} + func BenchmarkIPPrefixMasking(b *testing.B) { tests := []struct { name string From c5a623872717bee61dca2c226d3968533abe5054 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 11 May 2021 10:02:35 -0700 Subject: [PATCH 10/73] add BenchmarkIPPortMarshalText and BenchmarkIPPortString Signed-off-by: Josh Bleecher Snyder --- netaddr_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/netaddr_test.go b/netaddr_test.go index bd305f6..b39e3ab 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -1829,6 +1829,32 @@ func BenchmarkIPMarshalText(b *testing.B) { } } +func BenchmarkIPPortString(b *testing.B) { + for _, test := range parseBenchInputs { + ip := MustParseIP(test.ip) + ipp := IPPort{IP: ip, Port: 60000} + b.Run(test.name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sinkString = ipp.String() + } + }) + } +} + +func BenchmarkIPPortMarshalText(b *testing.B) { + for _, test := range parseBenchInputs { + ip := MustParseIP(test.ip) + ipp := IPPort{IP: ip, Port: 60000} + b.Run(test.name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sinkBytes, _ = ipp.MarshalText() + } + }) + } +} + func BenchmarkIPPrefixMasking(b *testing.B) { tests := []struct { name string From 37180328850cb14b4cef176a926580ec4c9d655e Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 11 May 2021 10:33:24 -0700 Subject: [PATCH 11/73] optimize IPPort.MarshalText MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit name old time/op new time/op delta IPPortMarshalText/v4-8 191ns ± 3% 33ns ± 2% -82.51% (p=0.008 n=5+5) IPPortMarshalText/v6-8 153ns ± 1% 71ns ± 1% -53.35% (p=0.008 n=5+5) IPPortMarshalText/v6_ellipsis-8 145ns ± 0% 68ns ± 0% -53.23% (p=0.008 n=5+5) IPPortMarshalText/v6_v4-8 141ns ± 0% 70ns ± 0% -50.51% (p=0.016 n=5+4) IPPortMarshalText/v6_zone-8 145ns ± 1% 70ns ± 1% -51.57% (p=0.008 n=5+5) name old alloc/op new alloc/op delta IPPortMarshalText/v4-8 50.0B ± 0% 24.0B ± 0% -52.00% (p=0.008 n=5+5) IPPortMarshalText/v6-8 149B ± 0% 64B ± 0% -57.05% (p=0.008 n=5+5) IPPortMarshalText/v6_ellipsis-8 93.0B ± 0% 64.0B ± 0% -31.18% (p=0.008 n=5+5) IPPortMarshalText/v6_v4-8 69.0B ± 0% 64.0B ± 0% -7.25% (p=0.008 n=5+5) IPPortMarshalText/v6_zone-8 93.0B ± 0% 64.0B ± 0% -31.18% (p=0.008 n=5+5) name old allocs/op new allocs/op delta IPPortMarshalText/v4-8 3.00 ± 0% 1.00 ± 0% -66.67% (p=0.008 n=5+5) IPPortMarshalText/v6-8 4.00 ± 0% 1.00 ± 0% -75.00% (p=0.008 n=5+5) IPPortMarshalText/v6_ellipsis-8 4.00 ± 0% 1.00 ± 0% -75.00% (p=0.008 n=5+5) IPPortMarshalText/v6_v4-8 4.00 ± 0% 1.00 ± 0% -75.00% (p=0.008 n=5+5) IPPortMarshalText/v6_zone-8 4.00 ± 0% 1.00 ± 0% -75.00% (p=0.008 n=5+5) Signed-off-by: Josh Bleecher Snyder --- inlining_test.go | 1 - netaddr.go | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/inlining_test.go b/inlining_test.go index 4f90e00..dcbd90b 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -68,7 +68,6 @@ func TestInlining(t *testing.T) { "IP.v6", "IP.v6u16", "IPPort.IsZero", - "IPPort.MarshalText", "IPPort.TCPAddr", "IPPort.UDPAddr", "IPPort.UDPAddrAt", diff --git a/netaddr.go b/netaddr.go index 7191018..953065f 100644 --- a/netaddr.go +++ b/netaddr.go @@ -948,10 +948,25 @@ func (p IPPort) String() string { // encoding is the same as returned by String, with one exception: if // p.IP is the zero value, the encoding is the empty string. func (p IPPort) MarshalText() ([]byte, error) { - if p.IP.z == z0 { + switch p.IP.z { + case z0: return []byte(""), nil + case z4: + max := len("255.255.255.255:65535") + b := make([]byte, 0, max) + b = p.IP.appendTo4(b) + b = append(b, ':') + b = strconv.AppendInt(b, int64(p.Port), 10) + return b, nil + default: + max := len("[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0]:65535") + b := make([]byte, 1, max) + b[0] = '[' + b = p.IP.appendTo6(b) + b = append(b, ']', ':') + b = strconv.AppendInt(b, int64(p.Port), 10) + return b, nil } - return []byte(p.String()), nil } // UnmarshalText implements the encoding.TextUnmarshaler From a8203e8354427a69a593f268c750ce938d2cf7a1 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 14 May 2021 14:21:27 -0700 Subject: [PATCH 12/73] add IPFrom4 Symmetric with IPFrom16, and useful when wrangling sockaddr structs. Signed-off-by: Josh Bleecher Snyder --- inlining_test.go | 1 + netaddr.go | 6 ++++++ netaddr_test.go | 16 ++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/inlining_test.go b/inlining_test.go index dcbd90b..4326ee6 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -80,6 +80,7 @@ func TestInlining(t *testing.T) { "IPRange.prefixFrom128AndBits-fm", "IPRange.entirelyBefore", "IPv4", + "IPFrom4", "IPv6LinkLocalAllNodes", "IPv6Unspecified", "MustParseIP", diff --git a/netaddr.go b/netaddr.go index 953065f..1966b25 100644 --- a/netaddr.go +++ b/netaddr.go @@ -121,6 +121,12 @@ func IPFrom16(addr [16]byte) IP { return IPv6Raw(addr).Unmap() } +// IPFrom4 returns the IPv4 address given by the bytes in addr. +// It is equivalent to calling IPv4(addr[0], addr[1], addr[2], addr[3]). +func IPFrom4(addr [4]byte) IP { + return IPv4(addr[0], addr[1], addr[2], addr[3]) +} + // ParseIP parses s as an IP address, returning the result. The string // s can be in dotted decimal ("192.0.2.1"), IPv6 ("2001:db8::68"), // or IPv6 with a scoped addressing zone ("fe80::1cc0:3e8c:119f:c2e1%ens18"). diff --git a/netaddr_test.go b/netaddr_test.go index b39e3ab..6cb5331 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -310,6 +310,21 @@ func TestParseIP(t *testing.T) { } } +func TestIPv4Constructors(t *testing.T) { + ips := []IP{ + IPv4(1, 2, 3, 4), + IPFrom4([4]byte{1, 2, 3, 4}), + MustParseIP("1.2.3.4"), + } + for i := range ips { + for j := i + 1; j < len(ips); j++ { + if ips[i] != ips[j] { + t.Errorf("%v != %v", ips[i], ips[j]) + } + } + } +} + func TestIPMarshalUnmarshalBinary(t *testing.T) { tests := []struct { ip string @@ -2578,6 +2593,7 @@ func TestNoAllocs(t *testing.T) { // IP constructors test("IPv4", func() { sinkIP = IPv4(1, 2, 3, 4) }) + test("IPFrom4", func() { sinkIP = IPFrom4([4]byte{1, 2, 3, 4}) }) test("IPv6", func() { sinkIP = IPv6Raw([16]byte{}) }) test("IPFrom16", func() { sinkIP = IPFrom16([16]byte{15: 1}) }) test("ParseIP/4", func() { sinkIP = panicIP(ParseIP("1.2.3.4")) }) From 9438c4d7bb0dab127691b6a607bb932421780885 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 14 May 2021 13:50:54 -0700 Subject: [PATCH 13/73] make IPPort type opaque API discussion in #154. Signed-off-by: Josh Bleecher Snyder --- inlining_test.go | 5 ++++ netaddr.go | 75 ++++++++++++++++++++++++++++-------------------- netaddr_test.go | 7 +++-- 3 files changed, 53 insertions(+), 34 deletions(-) diff --git a/inlining_test.go b/inlining_test.go index 4326ee6..a07cf26 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -71,6 +71,11 @@ func TestInlining(t *testing.T) { "IPPort.TCPAddr", "IPPort.UDPAddr", "IPPort.UDPAddrAt", + "IPPortFrom", + "IPPort.IP", + "IPPort.Port", + "IPPort.WithIP", + "IPPort.WithPort", "IPPrefix.IsSingleIP", "IPPrefix.IsZero", "IPPrefix.Masked", diff --git a/netaddr.go b/netaddr.go index 1966b25..8522e35 100644 --- a/netaddr.go +++ b/netaddr.go @@ -865,14 +865,27 @@ func (ip *IP) UnmarshalBinary(b []byte) error { return fmt.Errorf("netaddr: unexpected ip size: %v", len(b)) } -// IPPort is an IP & port number. -// -// It's meant to be used as a value type. +// IPPort is an IP and a port number. type IPPort struct { - IP IP - Port uint16 + ip IP + port uint16 } +// IPPortFrom returns an IPPort with ip ip and port port. +func IPPortFrom(ip IP, port uint16) IPPort { return IPPort{ip: ip, port: port} } + +// WithIP returns an IPPort with ip ip and port p.Port(). +func (p IPPort) WithIP(ip IP) IPPort { return IPPort{ip: ip, port: p.port} } + +// WithIP returns an IPPort with ip p.IP() and port port. +func (p IPPort) WithPort(port uint16) IPPort { return IPPort{ip: p.ip, port: port} } + +// IP returns p's IP. +func (p IPPort) IP() IP { return p.ip } + +// Port returns p's port. +func (p IPPort) Port() uint16 { return p.port } + // splitIPPort splits s into an IP address string and a port // string. It splits strings shaped like "foo:bar" or "[foo]:bar", // without further validating the substrings. v6 indicates whether the @@ -915,14 +928,14 @@ func ParseIPPort(s string) (IPPort, error) { if err != nil { return ipp, fmt.Errorf("invalid port %q parsing %q", port, s) } - ipp.Port = uint16(port16) - ipp.IP, err = ParseIP(ip) + ipp.port = uint16(port16) + ipp.ip, err = ParseIP(ip) if err != nil { return IPPort{}, err } - if v6 && ipp.IP.Is4() { + if v6 && ipp.ip.Is4() { return IPPort{}, fmt.Errorf("invalid ip:port %q, square brackets can only be used with IPv6 addresses", s) - } else if !v6 && ipp.IP.Is6() { + } else if !v6 && ipp.ip.Is6() { return IPPort{}, fmt.Errorf("invalid ip:port %q, IPv6 addresses must be surrounded by square brackets", s) } return ipp, nil @@ -942,35 +955,35 @@ func MustParseIPPort(s string) IPPort { func (p IPPort) IsZero() bool { return p == IPPort{} } func (p IPPort) String() string { - if p.IP.z == z4 { - a := p.IP.As4() - return fmt.Sprintf("%d.%d.%d.%d:%d", a[0], a[1], a[2], a[3], p.Port) + if p.ip.z == z4 { + a := p.ip.As4() + return fmt.Sprintf("%d.%d.%d.%d:%d", a[0], a[1], a[2], a[3], p.port) } // TODO: this could be more efficient allocation-wise: - return net.JoinHostPort(p.IP.String(), strconv.Itoa(int(p.Port))) + return net.JoinHostPort(p.ip.String(), strconv.Itoa(int(p.port))) } // MarshalText implements the encoding.TextMarshaler interface. The // encoding is the same as returned by String, with one exception: if -// p.IP is the zero value, the encoding is the empty string. +// p.IP() is the zero value, the encoding is the empty string. func (p IPPort) MarshalText() ([]byte, error) { - switch p.IP.z { + switch p.ip.z { case z0: return []byte(""), nil case z4: max := len("255.255.255.255:65535") b := make([]byte, 0, max) - b = p.IP.appendTo4(b) + b = p.ip.appendTo4(b) b = append(b, ':') - b = strconv.AppendInt(b, int64(p.Port), 10) + b = strconv.AppendInt(b, int64(p.port), 10) return b, nil default: max := len("[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0]:65535") b := make([]byte, 1, max) b[0] = '[' - b = p.IP.appendTo6(b) + b = p.ip.appendTo6(b) b = append(b, ']', ':') - b = strconv.AppendInt(b, int64(p.Port), 10) + b = strconv.AppendInt(b, int64(p.port), 10) return b, nil } } @@ -980,7 +993,7 @@ func (p IPPort) MarshalText() ([]byte, error) { // ParseIPPort. It returns an error if *p is not the IPPort zero // value. func (p *IPPort) UnmarshalText(text []byte) error { - if p.IP.z != z0 || p.Port != 0 { + if p.ip.z != z0 || p.port != 0 { return errors.New("netaddr: refusing to UnmarshalText into non-zero IP") } if len(text) == 0 { @@ -1006,20 +1019,20 @@ func FromStdAddr(stdIP net.IP, port int, zone string) (_ IPPort, ok bool) { } ip = ip.WithZone(zone) } - return IPPort{IP: ip, Port: uint16(port)}, true + return IPPort{ip: ip, port: uint16(port)}, true } // UDPAddr returns a standard library net.UDPAddr from p. -// The returned value is always non-nil. If p.IP is the zero +// The returned value is always non-nil. If p.IP() is the zero // value, then UDPAddr.IP is nil. // // UDPAddr necessarily does two allocations. If you have an existing // UDPAddr already allocated, see UDPAddrAt. func (p IPPort) UDPAddr() *net.UDPAddr { ret := &net.UDPAddr{ - Port: int(p.Port), + Port: int(p.port), } - ret.IP, ret.Zone = p.IP.ipZone(nil) + ret.IP, ret.Zone = p.ip.ipZone(nil) return ret } @@ -1028,19 +1041,19 @@ func (p IPPort) UDPAddr() *net.UDPAddr { // allocation-free. It returns at to facilitate using this method as a // wrapper. func (p IPPort) UDPAddrAt(at *net.UDPAddr) *net.UDPAddr { - at.Port = int(p.Port) - at.IP, at.Zone = p.IP.ipZone(at.IP) + at.Port = int(p.port) + at.IP, at.Zone = p.ip.ipZone(at.IP) return at } // TCPAddr returns a standard library net.TCPAddr from p. -// The returned value is always non-nil. If p.IP is the zero +// The returned value is always non-nil. If p.IP() is the zero // value, then TCPAddr.IP is nil. func (p IPPort) TCPAddr() *net.TCPAddr { - ip, zone := p.IP.ipZone(nil) + ip, zone := p.ip.ipZone(nil) return &net.TCPAddr{ IP: ip, - Port: int(p.Port), + Port: int(p.port), Zone: zone, } } @@ -1054,8 +1067,8 @@ type IPPrefix struct { Bits uint8 } -// Valid reports whether whether p.Bits has a valid range for p.IP. -// If p.IP is zero, Valid returns false. +// Valid reports whether whether p.Bits has a valid range for p.IP(). +// If p.IP() is zero, Valid returns false. func (p IPPrefix) Valid() bool { return !p.IP.IsZero() && p.Bits <= p.IP.BitLen() } // IsZero reports whether p is its zero value. diff --git a/netaddr_test.go b/netaddr_test.go index 6cb5331..befdaa1 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -1642,7 +1642,7 @@ func TestUDPAddrAllocs(t *testing.T) { ua := &net.UDPAddr{IP: make(net.IP, 0, 16)} n := int(testing.AllocsPerRun(1000, func() { ua := ipp.UDPAddrAt(ua) - if ua.Port != int(ipp.Port) { + if ua.Port != int(ipp.Port()) { t.Fatal("UDPAddr returned bogus result") } })) @@ -1847,7 +1847,7 @@ func BenchmarkIPMarshalText(b *testing.B) { func BenchmarkIPPortString(b *testing.B) { for _, test := range parseBenchInputs { ip := MustParseIP(test.ip) - ipp := IPPort{IP: ip, Port: 60000} + ipp := IPPortFrom(ip, 60000) b.Run(test.name, func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { @@ -1860,7 +1860,7 @@ func BenchmarkIPPortString(b *testing.B) { func BenchmarkIPPortMarshalText(b *testing.B) { for _, test := range parseBenchInputs { ip := MustParseIP(test.ip) - ipp := IPPort{IP: ip, Port: 60000} + ipp := IPPortFrom(ip, 60000) b.Run(test.name, func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { @@ -2638,6 +2638,7 @@ func TestNoAllocs(t *testing.T) { test("IP.Prior", func() { sinkIP = MustParseIP("1.2.3.4").Prior() }) // IPPort constructors + test("IPPortFrom", func() { sinkIPPort = IPPortFrom(IPv4(1, 2, 3, 4), 22) }) test("ParseIPPort", func() { sinkIPPort = panicIPP(ParseIPPort("[::1]:1234")) }) test("MustParseIPPort", func() { sinkIPPort = MustParseIPPort("[::1]:1234") }) test("FromStdAddr", func() { From 7fffabd27866188cdfff7a4196605a798afffb28 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 14 May 2021 14:01:42 -0700 Subject: [PATCH 14/73] make IPPrefix opaque API discussion in #154. Unlike IPPort, I did not add IPPrefix.WithIP and IPPrefix.WithBits. This is because we do not have significant need for them ourselves. Perhaps we will add them later or for symmetry if requested. Signed-off-by: Josh Bleecher Snyder --- inlining_test.go | 3 ++ ipset.go | 14 ++++----- netaddr.go | 81 +++++++++++++++++++++++++++--------------------- netaddr_test.go | 24 +++++++------- 4 files changed, 66 insertions(+), 56 deletions(-) diff --git a/inlining_test.go b/inlining_test.go index a07cf26..77f372e 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -80,6 +80,9 @@ func TestInlining(t *testing.T) { "IPPrefix.IsZero", "IPPrefix.Masked", "IPPrefix.Valid", + "IPPrefixFrom", + "IPPrefix.IP", + "IPPrefix.Bits", "IPRange.Prefixes", "IPRange.prefixFrom128AndBits", "IPRange.prefixFrom128AndBits-fm", diff --git a/ipset.go b/ipset.go index 27835d0..0c47f00 100644 --- a/ipset.go +++ b/ipset.go @@ -218,8 +218,8 @@ func (s *IPSetBuilder) Complement() { s.normalize() s.out = s.in s.in = []IPRange{ - IPPrefix{IP: IPv4(0, 0, 0, 0), Bits: 0}.Range(), - IPPrefix{IP: IPv6Unspecified(), Bits: 0}.Range(), + IPPrefix{ip: IPv4(0, 0, 0, 0), bits: 0}.Range(), + IPPrefix{ip: IPv6Unspecified(), bits: 0}.Range(), } } @@ -356,12 +356,12 @@ func (s *IPSet) RemoveFreePrefix(bitLen uint8) (p IPPrefix, newSet *IPSet, ok bo var bestFit IPPrefix for _, r := range s.rr { for _, prefix := range r.Prefixes() { - if prefix.Bits > bitLen { + if prefix.bits > bitLen { continue } - if bestFit.IP.IsZero() || prefix.Bits > bestFit.Bits { + if bestFit.ip.IsZero() || prefix.bits > bestFit.bits { bestFit = prefix - if bestFit.Bits == bitLen { + if bestFit.bits == bitLen { // exact match, done. break } @@ -369,11 +369,11 @@ func (s *IPSet) RemoveFreePrefix(bitLen uint8) (p IPPrefix, newSet *IPSet, ok bo } } - if bestFit.IP.IsZero() { + if bestFit.ip.IsZero() { return IPPrefix{}, s, false } - prefix := IPPrefix{IP: bestFit.IP, Bits: bitLen} + prefix := IPPrefix{ip: bestFit.ip, bits: bitLen} var b IPSetBuilder b.AddSet(s) diff --git a/netaddr.go b/netaddr.go index 8522e35..61dea4d 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1060,22 +1060,31 @@ func (p IPPort) TCPAddr() *net.TCPAddr { // IPPrefix is an IP address prefix (CIDR) representing an IP network. // -// The first Bits of IP are specified, the remaining bits match any address. -// The range of Bits is [0,32] for IPv4 or [0,128] for IPv6. +// The first Bits() of IP are specified. The remaining bits match any address. +// The range of Bits() is [0,32] for IPv4 or [0,128] for IPv6. type IPPrefix struct { - IP IP - Bits uint8 + ip IP + bits uint8 } -// Valid reports whether whether p.Bits has a valid range for p.IP(). +// IPPrefixFrom returns an IPPrefix with ip ip and port port. +func IPPrefixFrom(ip IP, bits uint8) IPPrefix { return IPPrefix{ip: ip, bits: bits} } + +// IP returns p's IP. +func (p IPPrefix) IP() IP { return p.ip } + +// Bits returns p's prefix length. +func (p IPPrefix) Bits() uint8 { return p.bits } + +// Valid reports whether whether p.Bits() has a valid range for p.IP(). // If p.IP() is zero, Valid returns false. -func (p IPPrefix) Valid() bool { return !p.IP.IsZero() && p.Bits <= p.IP.BitLen() } +func (p IPPrefix) Valid() bool { return !p.ip.IsZero() && p.bits <= p.ip.BitLen() } // IsZero reports whether p is its zero value. func (p IPPrefix) IsZero() bool { return p == IPPrefix{} } // IsSingleIP reports whether p contains exactly one IP. -func (p IPPrefix) IsSingleIP() bool { return p.Bits != 0 && p.Bits == p.IP.BitLen() } +func (p IPPrefix) IsSingleIP() bool { return p.bits != 0 && p.bits == p.ip.BitLen() } // FromStdIPNet returns an IPPrefix from the standard library's IPNet type. // If std is invalid, ok is false. @@ -1097,8 +1106,8 @@ func FromStdIPNet(std *net.IPNet) (prefix IPPrefix, ok bool) { } return IPPrefix{ - IP: ip, - Bits: uint8(ones), + ip: ip, + bits: uint8(ones), }, true } @@ -1129,8 +1138,8 @@ func ParseIPPrefix(s string) (IPPrefix, error) { return IPPrefix{}, fmt.Errorf("netaddr.ParseIPPrefix(%q): prefix length out of range", s) } return IPPrefix{ - IP: ip, - Bits: uint8(bits), + ip: ip, + bits: uint8(bits), }, nil } @@ -1144,10 +1153,10 @@ func MustParseIPPrefix(s string) IPPrefix { return ip } -// Masked returns p in its canonical form, with bits of p.IP not in p.Bits masked off. +// Masked returns p in its canonical form, with bits of p.IP() not in p.Bits() masked off. // If p is zero or otherwise invalid, Masked returns the zero value. func (p IPPrefix) Masked() IPPrefix { - if m, err := p.IP.Prefix(p.Bits); err == nil { + if m, err := p.ip.Prefix(p.bits); err == nil { return m } return IPPrefix{} @@ -1161,7 +1170,7 @@ func (p IPPrefix) Range() IPRange { if p.IsZero() { return IPRange{} } - return IPRange{From: p.IP, To: p.lastIP()} + return IPRange{From: p.ip, To: p.lastIP()} } // IPNet returns the net.IPNet representation of an IPPrefix. @@ -1171,10 +1180,10 @@ func (p IPPrefix) IPNet() *net.IPNet { if !p.Valid() { return &net.IPNet{} } - stdIP, _ := p.IP.ipZone(nil) + stdIP, _ := p.ip.ipZone(nil) return &net.IPNet{ IP: stdIP, - Mask: net.CIDRMask(int(p.Bits), int(p.IP.BitLen())), + Mask: net.CIDRMask(int(p.bits), int(p.ip.BitLen())), } } @@ -1187,7 +1196,7 @@ func (p IPPrefix) Contains(ip IP) bool { if !p.Valid() { return false } - if f1, f2 := p.IP.BitLen(), ip.BitLen(); f1 == 0 || f2 == 0 || f1 != f2 { + if f1, f2 := p.ip.BitLen(), ip.BitLen(); f1 == 0 || f2 == 0 || f1 != f2 { return false } if ip.Is4() { @@ -1195,16 +1204,16 @@ func (p IPPrefix) Contains(ip IP) bool { // Shift away the number of bits we don't care about. // Shifts in Go are more efficient if the compiler can prove // that the shift amount is smaller than the width of the shifted type (64 here). - // We know that p.Bits is in the range 0..32 because p is Valid; + // We know that p.bits is in the range 0..32 because p is Valid; // the compiler doesn't know that, so mask with 63 to help it. // Now truncate to 32 bits, because this is IPv4. // If all the bits we care about are equal, the result will be zero. - return uint32((ip.addr.lo^p.IP.addr.lo)>>((32-p.Bits)&63)) == 0 + return uint32((ip.addr.lo^p.ip.addr.lo)>>((32-p.bits)&63)) == 0 } else { // xor the IP addresses together. // Mask away the bits we don't care about. // If all the bits we care about are equal, the result will be zero. - return ip.addr.xor(p.IP.addr).and(mask6[p.Bits]).isZero() + return ip.addr.xor(p.ip.addr).and(mask6[p.bits]).isZero() } } @@ -1222,14 +1231,14 @@ func (p IPPrefix) Overlaps(o IPPrefix) bool { if p == o { return true } - if p.IP.Is4() != o.IP.Is4() { + if p.ip.Is4() != o.ip.Is4() { return false } var minBits uint8 - if p.Bits < o.Bits { - minBits = p.Bits + if p.bits < o.bits { + minBits = p.bits } else { - minBits = o.Bits + minBits = o.bits } if minBits == 0 { return true @@ -1239,13 +1248,13 @@ func (p IPPrefix) Overlaps(o IPPrefix) bool { // so the Prefix call on the one that's already minBits serves to zero // out any remaining bits in IP. var err error - if p, err = p.IP.Prefix(minBits); err != nil { + if p, err = p.ip.Prefix(minBits); err != nil { return false } - if o, err = o.IP.Prefix(minBits); err != nil { + if o, err = o.ip.Prefix(minBits); err != nil { return false } - return p.IP == o.IP + return p.ip == o.ip } // MarshalText implements the encoding.TextMarshaler interface, @@ -1261,18 +1270,18 @@ func (p IPPrefix) MarshalText() ([]byte, error) { // p.IP is non-zero, because p is valid. var b []byte - if p.IP.z == z4 { + if p.ip.z == z4 { max := len("255.255.255.255/32") b = make([]byte, 0, max) - b = p.IP.appendTo4(b) + b = p.ip.appendTo4(b) } else { max := len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0/128") b = make([]byte, 0, max) - b = p.IP.appendTo6(b) + b = p.ip.appendTo6(b) } b = append(b, '/') - b = appendDecimal(b, p.Bits) + b = appendDecimal(b, p.bits) return b, nil } @@ -1298,7 +1307,7 @@ func (p IPPrefix) String() string { if !p.Valid() { return "invalid IP prefix" } - return fmt.Sprintf("%s/%d", p.IP, p.Bits) + return fmt.Sprintf("%s/%d", p.ip, p.bits) } // lastIP returns the last IP in the prefix. @@ -1306,18 +1315,18 @@ func (p IPPrefix) lastIP() IP { if !p.Valid() { return IP{} } - a16 := p.IP.As16() + a16 := p.ip.As16() var off uint8 var bits uint8 = 128 - if p.IP.Is4() { + if p.ip.Is4() { off = 12 bits = 32 } - for b := p.Bits; b < bits; b++ { + for b := p.bits; b < bits; b++ { byteNum, bitInByte := b/8, 7-(b%8) a16[off+byteNum] |= 1 << uint(bitInByte) } - if p.IP.Is4() { + if p.ip.Is4() { return IPFrom16(a16) } else { return IPv6Raw(a16) // doesn't unmap diff --git a/netaddr_test.go b/netaddr_test.go index befdaa1..9b77736 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -1192,11 +1192,11 @@ func TestIPPrefixMasked(t *testing.T) { masked: mustIPPrefix("2000::/3"), }, { - prefix: IPPrefix{IP: mustIP("2000::"), Bits: 129}, + prefix: IPPrefixFrom(mustIP("2000::"), 129), masked: IPPrefix{}, }, { - prefix: IPPrefix{IP: mustIP("1.2.3.4"), Bits: 33}, + prefix: IPPrefixFrom(mustIP("1.2.3.4"), 33), masked: IPPrefix{}, }, } @@ -1303,11 +1303,11 @@ func TestIPPrefix(t *testing.T) { if err != nil { t.Fatal(err) } - if prefix.IP != test.ip { - t.Errorf("IP=%s, want %s", prefix.IP, test.ip) + if prefix.IP() != test.ip { + t.Errorf("IP=%s, want %s", prefix.IP(), test.ip) } - if prefix.Bits != test.bits { - t.Errorf("bits=%d, want %d", prefix.Bits, test.bits) + if prefix.Bits() != test.bits { + t.Errorf("bits=%d, want %d", prefix.Bits(), test.bits) } stdIPNet := prefix.IPNet() if !test.ipNet.IP.Equal(stdIPNet.IP) || !reflect.DeepEqual(stdIPNet.Mask, test.ipNet.Mask) { @@ -1771,10 +1771,7 @@ func BenchmarkIPv6(b *testing.B) { func BenchmarkIPv4Contains(b *testing.B) { b.ReportAllocs() - prefix := IPPrefix{ - IP: IPv4(192, 168, 1, 0), - Bits: 24, - } + prefix := IPPrefixFrom(IPv4(192, 168, 1, 0), 24) ip := IPv4(192, 168, 1, 1) for i := 0; i < b.N; i++ { prefix.Contains(ip) @@ -2062,11 +2059,11 @@ func TestIPPrefixOverlaps(t *testing.T) { {pfx("0100::0/8"), pfx("::1/128"), false}, // v6-mapped v4 should not overlap with IPv4. - {IPPrefix{IP: IPv6Raw(mustIP("1.2.0.0").As16()), Bits: 16}, pfx("1.2.3.0/24"), false}, + {IPPrefixFrom(IPv6Raw(mustIP("1.2.0.0").As16()), 16), pfx("1.2.3.0/24"), false}, // Invalid prefixes - {IPPrefix{IP: mustIP("1.2.3.4"), Bits: 33}, pfx("1.2.3.0/24"), false}, - {IPPrefix{IP: mustIP("2000::"), Bits: 129}, pfx("2000::/64"), false}, + {IPPrefixFrom(mustIP("1.2.3.4"), 33), pfx("1.2.3.0/24"), false}, + {IPPrefixFrom(mustIP("2000::"), 129), pfx("2000::/64"), false}, } for i, tt := range tests { if got := tt.a.Overlaps(tt.b); got != tt.want { @@ -2650,6 +2647,7 @@ func TestNoAllocs(t *testing.T) { test("UDPAddrAt", func() { sinkUDPAddr = MustParseIPPort("1.2.3.4:1234").UDPAddrAt(sinkUDPAddr) }) // IPPrefix constructors + test("IPPrefixFrom", func() { sinkIPPrefix = IPPrefixFrom(IPv4(1, 2, 3, 4), 32) }) test("ParseIPPrefix/4", func() { sinkIPPrefix = panicPfx(ParseIPPrefix("1.2.3.4/20")) }) test("ParseIPPrefix/6", func() { sinkIPPrefix = panicPfx(ParseIPPrefix("fe80::1/64")) }) test("MustParseIPPrefix", func() { sinkIPPrefix = MustParseIPPrefix("1.2.3.4/20") }) From ad03edc7c841f05a898c9a502f99a6bfdea3b7a9 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 14 May 2021 14:11:41 -0700 Subject: [PATCH 15/73] make IPRange opaque API discussion in #154. Unlike IPPort, I did not add IPRange.WithFrom and IPRange.WithTo. This is because we do not have significant need for them ourselves. Perhaps we will add them later or for symmetry if requested. Signed-off-by: Josh Bleecher Snyder --- example_test.go | 10 ++--- inlining_test.go | 3 ++ ipset.go | 10 ++--- ipset_test.go | 21 +++++------ netaddr.go | 98 +++++++++++++++++++++++++++--------------------- netaddr_test.go | 11 +++--- 6 files changed, 84 insertions(+), 69 deletions(-) diff --git a/example_test.go b/example_test.go index 37bb9e6..5280c4d 100644 --- a/example_test.go +++ b/example_test.go @@ -16,16 +16,16 @@ func ExampleIPSet() { b.AddPrefix(netaddr.MustParseIPPrefix("10.0.0.0/8")) b.RemovePrefix(netaddr.MustParseIPPrefix("10.0.0.0/16")) - b.AddRange(netaddr.IPRange{ - From: netaddr.MustParseIP("fed0::0400"), - To: netaddr.MustParseIP("fed0::04ff"), - }) + b.AddRange(netaddr.IPRangeFrom( + netaddr.MustParseIP("fed0::0400"), + netaddr.MustParseIP("fed0::04ff"), + )) s := b.IPSet() fmt.Println("Ranges:") for _, r := range s.Ranges() { - fmt.Printf(" %s - %s\n", r.From, r.To) + fmt.Printf(" %s - %s\n", r.From(), r.To()) } fmt.Println("Prefixes:") diff --git a/inlining_test.go b/inlining_test.go index 77f372e..e2a21e7 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -87,6 +87,9 @@ func TestInlining(t *testing.T) { "IPRange.prefixFrom128AndBits", "IPRange.prefixFrom128AndBits-fm", "IPRange.entirelyBefore", + "IPRangeFrom", + "IPRange.To", + "IPRange.From", "IPv4", "IPFrom4", "IPv6LinkLocalAllNodes", diff --git a/ipset.go b/ipset.go index 0c47f00..0c6be84 100644 --- a/ipset.go +++ b/ipset.go @@ -95,10 +95,10 @@ func (s *IPSetBuilder) normalize() { // f-------------t // f------t // out - min = append(min, IPRange{From: rin.From, To: rout.From.Prior()}) + min = append(min, IPRange{from: rin.from, to: rout.from.Prior()}) // Adjust in[0], not ir, because we want to consider the // mutated range on the next iteration. - in[0].From = rout.To.Next() + in[0].from = rout.to.Next() out = out[1:] if debug { debugf("out inside in; split in, append first in, drop out, adjust second in") @@ -111,7 +111,7 @@ func (s *IPSetBuilder) normalize() { // f------t // f------t // in - in[0].From = rout.To.Next() + in[0].from = rout.to.Next() // Can't move ir onto min yet, another later out might // trim it further. Just discard or and continue. out = out[1:] @@ -125,7 +125,7 @@ func (s *IPSetBuilder) normalize() { // f------t // f------t // in - min = append(min, IPRange{From: rin.From, To: rout.From.Prior()}) + min = append(min, IPRange{from: rin.from, to: rout.from.Prior()}) in = in[1:] if debug { debugf("merge out cuts end of in; append shortened in") @@ -294,7 +294,7 @@ func (s *IPSet) Contains(ip IP) bool { // TODO: data structure permitting more efficient lookups: // https://github.com/inetaf/netaddr/issues/139 i := sort.Search(len(s.rr), func(i int) bool { - return ip.Less(s.rr[i].From) + return ip.Less(s.rr[i].from) }) if i == 0 { return false diff --git a/ipset_test.go b/ipset_test.go index 67e6ce9..b94a6dd 100644 --- a/ipset_test.go +++ b/ipset_test.go @@ -314,7 +314,7 @@ func TestIPSet(t *testing.T) { t.Run("ranges", func(t *testing.T) { for _, v := range got { if !v.Valid() { - t.Errorf("invalid IPRange in result: %s -> %s", v.From, v.To) + t.Errorf("invalid IPRange in result: %s -> %s", v.From(), v.To()) } } if reflect.DeepEqual(got, tt.wantRanges) { @@ -322,11 +322,11 @@ func TestIPSet(t *testing.T) { } t.Error("failed. got:\n") for _, v := range got { - t.Errorf(" %s -> %s", v.From, v.To) + t.Errorf(" %s -> %s", v.From(), v.To()) } t.Error("want:\n") for _, v := range tt.wantRanges { - t.Errorf(" %s -> %s", v.From, v.To) + t.Errorf(" %s -> %s", v.From(), v.To()) } }) if tt.wantPrefixes != nil { @@ -575,11 +575,11 @@ func newRandomIPSet() (steps []string, s *IPSet, wantContains [256]bool) { switch op { case 0: steps = append(steps, fmt.Sprintf("add 0.0.0.%d-0.0.0.%d", ip1, ip2)) - b.AddRange(IPRange{From: IPv4(0, 0, 0, ip1), To: IPv4(0, 0, 0, ip2)}) + b.AddRange(IPRangeFrom(IPv4(0, 0, 0, ip1), IPv4(0, 0, 0, ip2))) v = true case 1: steps = append(steps, fmt.Sprintf("remove 0.0.0.%d-0.0.0.%d", ip1, ip2)) - b.RemoveRange(IPRange{From: IPv4(0, 0, 0, ip1), To: IPv4(0, 0, 0, ip2)}) + b.RemoveRange(IPRangeFrom(IPv4(0, 0, 0, ip1), IPv4(0, 0, 0, ip2))) } for i := ip1; i <= ip2; i++ { wantContains[i] = v @@ -609,7 +609,7 @@ func TestIPSetRanges(t *testing.T) { var from, to IP ranges := make([]IPRange, 0) flush := func() { - r := IPRange{From: from, To: to} + r := IPRangeFrom(from, to) build.AddRange(r) ranges = append(ranges, r) from, to = IP{}, IP{} @@ -651,10 +651,9 @@ func TestIPSetRangesStress(t *testing.T) { if a > b { a, b = b, a } - return a, b, IPRange{ - From: IPv4(0, 0, uint8(a>>8), uint8(a)), - To: IPv4(0, 0, uint8(b>>8), uint8(b)), - } + from := IPv4(0, 0, uint8(a>>8), uint8(a)) + to := IPv4(0, 0, uint8(b>>8), uint8(b)) + return a, b, IPRangeFrom(from, to) } for i := 0; i < n; i++ { var build IPSetBuilder @@ -682,7 +681,7 @@ func TestIPSetRangesStress(t *testing.T) { if i == 0 { continue } - if ranges[i-1].To.Compare(r.From) != -1 { + if ranges[i-1].To().Compare(r.From()) != -1 { t.Fatalf("overlapping ranges: %v", ranges) } } diff --git a/netaddr.go b/netaddr.go index 61dea4d..8f55250 100644 --- a/netaddr.go +++ b/netaddr.go @@ -871,13 +871,14 @@ type IPPort struct { port uint16 } -// IPPortFrom returns an IPPort with ip ip and port port. +// IPPortFrom returns an IPPort with IP ip and port port. +// It does not allocate. func IPPortFrom(ip IP, port uint16) IPPort { return IPPort{ip: ip, port: port} } -// WithIP returns an IPPort with ip ip and port p.Port(). +// WithIP returns an IPPort with IP ip and port p.Port(). func (p IPPort) WithIP(ip IP) IPPort { return IPPort{ip: ip, port: p.port} } -// WithIP returns an IPPort with ip p.IP() and port port. +// WithIP returns an IPPort with IP p.IP() and port port. func (p IPPort) WithPort(port uint16) IPPort { return IPPort{ip: p.ip, port: port} } // IP returns p's IP. @@ -1067,7 +1068,8 @@ type IPPrefix struct { bits uint8 } -// IPPrefixFrom returns an IPPrefix with ip ip and port port. +// IPPrefixFrom returns an IPPrefix with IP ip and port port. +// It does not allocate. func IPPrefixFrom(ip IP, bits uint8) IPPrefix { return IPPrefix{ip: ip, bits: bits} } // IP returns p's IP. @@ -1170,7 +1172,7 @@ func (p IPPrefix) Range() IPRange { if p.IsZero() { return IPRange{} } - return IPRange{From: p.ip, To: p.lastIP()} + return IPRange{from: p.ip, to: p.lastIP()} } // IPNet returns the net.IPNet representation of an IPPrefix. @@ -1336,21 +1338,31 @@ func (p IPPrefix) lastIP() IP { // IPRange represents an inclusive range of IP addresses // from the same address family. // -// The From and To IPs are inclusive bounds, both included in the +// The From() and To() IPs are inclusive bounds, both included in the // range. // -// To be valid, the From and To values be non-zero, have matching +// To be valid, the From() and To() values must be non-zero, have matching // address families (IPv4 vs IPv6), be in the same IPv6 zone (if any), -// and From must be less than or equal to To. +// and From() must be less than or equal to To(). // An invalid range may be ignored. type IPRange struct { - // From is the initial IP address in the range. - From IP + // from is the initial IP address in the range. + from IP - // To is the final IP address in the range. - To IP + // to is the final IP address in the range. + to IP } +// IPRangeFrom returns an IPRange from from to to. +// It does not allocate. +func IPRangeFrom(from, to IP) IPRange { return IPRange{from: from, to: to} } + +// From returns the lower bound of r. +func (r IPRange) From() IP { return r.from } + +// To returns the upper bound of r. +func (r IPRange) To() IP { return r.to } + // ParseIPRange parses a range out of two IPs separated by a hyphen. // // It returns an error if the range is not valid. @@ -1362,16 +1374,16 @@ func ParseIPRange(s string) (IPRange, error) { } from, to := s[:h], s[h+1:] var err error - r.From, err = ParseIP(from) + r.from, err = ParseIP(from) if err != nil { return r, fmt.Errorf("invalid From IP %q in range %q", from, s) } - r.To, err = ParseIP(to) + r.to, err = ParseIP(to) if err != nil { return r, fmt.Errorf("invalid To IP %q in range %q", to, s) } if !r.Valid() { - return r, fmt.Errorf("range %v to %v not valid", r.From, r.To) + return r, fmt.Errorf("range %v to %v not valid", r.from, r.to) } return r, nil } @@ -1383,21 +1395,21 @@ func ParseIPRange(s string) (IPRange, error) { // ParseIPRange. func (r IPRange) String() string { if r.Valid() { - return fmt.Sprintf("%s-%s", r.From, r.To) + return fmt.Sprintf("%s-%s", r.from, r.to) } - if r.From.IsZero() || r.To.IsZero() { + if r.from.IsZero() || r.to.IsZero() { return "zero IPRange" } return "invalid IPRange" } -// Valid reports whether r.From and r.To are both non-zero and obey +// Valid reports whether r.From() and r.To() are both non-zero and obey // the documented requirements: address families match, same IPv6 // zone, and From is less than or equal to To. func (r IPRange) Valid() bool { - return !r.From.IsZero() && - r.From.z == r.To.z && - !r.To.Less(r.From) + return !r.from.IsZero() && + r.from.z == r.to.z && + !r.to.Less(r.from) } // Contains reports whether the range r includes addr. @@ -1409,47 +1421,47 @@ func (r IPRange) Contains(addr IP) bool { // contains is like Contains, but without the validity check. For internal use. func (r IPRange) contains(addr IP) bool { - return r.From.Compare(addr) <= 0 && r.To.Compare(addr) >= 0 + return r.from.Compare(addr) <= 0 && r.to.Compare(addr) >= 0 } -// less returns whether r is "before" other. It is before if r.From is -// before other.From, or if they're equal, the shorter range is +// less returns whether r is "before" other. It is before if r.From() is +// before other.From(), or if they're equal, the shorter range is // before. func (r IPRange) less(other IPRange) bool { - if cmp := r.From.Compare(other.From); cmp != 0 { + if cmp := r.from.Compare(other.from); cmp != 0 { return cmp < 0 } - return r.To.Less(other.To) + return r.to.Less(other.to) } // entirelyBefore returns whether r lies entirely before other in IP // space. func (r IPRange) entirelyBefore(other IPRange) bool { - return r.To.Less(other.From) + return r.to.Less(other.from) } // entirelyWithin returns whether r is entirely contained within // other. func (r IPRange) coveredBy(other IPRange) bool { - return other.From.lessOrEq(r.From) && r.To.lessOrEq(other.To) + return other.from.lessOrEq(r.from) && r.to.lessOrEq(other.to) } // inMiddleOf returns whether r is inside other, but not touching the // edges of other. func (r IPRange) inMiddleOf(other IPRange) bool { - return other.From.Less(r.From) && r.To.Less(other.To) + return other.from.Less(r.from) && r.to.Less(other.to) } // overlapsStartOf returns whether r entirely overlaps the start of // other, but not all of other. func (r IPRange) overlapsStartOf(other IPRange) bool { - return r.From.lessOrEq(other.From) && r.To.Less(other.To) + return r.from.lessOrEq(other.from) && r.to.Less(other.to) } // overlapsEndOf returns whether r entirely overlaps the end of // other, but not all of other. func (r IPRange) overlapsEndOf(other IPRange) bool { - return other.From.Less(r.From) && other.To.lessOrEq(r.To) + return other.from.Less(r.from) && other.to.lessOrEq(r.to) } // mergeIPRanges returns the minimum and sorted set of IP ranges that @@ -1474,27 +1486,27 @@ func mergeIPRanges(rr []IPRange) (out []IPRange, valid bool) { // Invalid ranges make no sense to merge, refuse to // perform. return nil, false - case prev.To.Next() == r.From: + case prev.to.Next() == r.from: // prev and r touch, merge them. // // prev r // f------tf-----t - prev.To = r.To - case prev.To.Less(r.From): + prev.to = r.to + case prev.to.Less(r.from): // No overlap and not adjacent (per previous case), no // merging possible. // // prev r // f------t f-----t out = append(out, r) - case prev.To.Less(r.To): + case prev.to.Less(r.to): // Partial overlap, update prev // // prev // f------t // f-----t // r - prev.To = r.To + prev.to = r.to default: // r entirely contained in prev, nothing to do. // @@ -1514,8 +1526,8 @@ func mergeIPRanges(rr []IPRange) (out []IPRange, valid bool) { func (r IPRange) Overlaps(o IPRange) bool { return r.Valid() && o.Valid() && - r.From.Compare(o.To) <= 0 && - o.From.Compare(r.To) <= 0 + r.from.Compare(o.to) <= 0 && + o.from.Compare(r.to) <= 0 } // prefixMaker returns a address-family-corrected IPPrefix from a and bits, @@ -1539,12 +1551,12 @@ func (r IPRange) AppendPrefixes(dst []IPPrefix) []IPPrefix { if !r.Valid() { return nil } - return appendRangePrefixes(dst, r.prefixFrom128AndBits, r.From.addr, r.To.addr) + return appendRangePrefixes(dst, r.prefixFrom128AndBits, r.from.addr, r.to.addr) } func (r IPRange) prefixFrom128AndBits(a uint128, bits uint8) IPPrefix { - ip := IP{addr: a, z: r.From.z} - if r.From.Is4() { + ip := IP{addr: a, z: r.from.z} + if r.from.Is4() { bits -= 12 * 8 } return IPPrefix{ip, bits} @@ -1572,8 +1584,8 @@ func (r IPRange) Prefix() (p IPPrefix, ok bool) { if !r.Valid() { return } - if common, ok := comparePrefixes(r.From.addr, r.To.addr); ok { - return r.prefixFrom128AndBits(r.From.addr, common), true + if common, ok := comparePrefixes(r.from.addr, r.to.addr); ok { + return r.prefixFrom128AndBits(r.from.addr, common), true } return } diff --git a/netaddr_test.go b/netaddr_test.go index 9b77736..ce2fc20 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -2144,7 +2144,7 @@ func TestRangePrefixes(t *testing.T) { )}, } for _, tt := range tests { - r := IPRange{From: mustIP(tt.from), To: mustIP(tt.to)} + r := IPRangeFrom(mustIP(tt.from), mustIP(tt.to)) got := r.Prefixes() if !reflect.DeepEqual(got, tt.want) { t.Errorf("failed %s->%s. got:", tt.from, tt.to) @@ -2208,7 +2208,7 @@ func TestIPRangeContains(t *testing.T) { rtests []rtest }{ { - IPRange{From: mustIP("10.0.0.2"), To: mustIP("10.0.0.4")}, + IPRangeFrom(mustIP("10.0.0.2"), mustIP("10.0.0.4")), []rtest{ {mustIP("10.0.0.1"), false}, {mustIP("10.0.0.2"), true}, @@ -2220,7 +2220,7 @@ func TestIPRangeContains(t *testing.T) { }, }, { - IPRange{From: mustIP("::1"), To: mustIP("::ffff")}, + IPRangeFrom(mustIP("::1"), mustIP("::ffff")), []rtest{ {mustIP("::0"), false}, {mustIP("::1"), true}, @@ -2231,7 +2231,7 @@ func TestIPRangeContains(t *testing.T) { }, }, { - IPRange{From: mustIP("10.0.0.2"), To: mustIP("::")}, // invalid + IPRangeFrom(mustIP("10.0.0.2"), mustIP("::")), // invalid []rtest{ {mustIP("10.0.0.2"), false}, }, @@ -2323,7 +2323,7 @@ func TestIPRangeValid(t *testing.T) { for _, tt := range tests { got := tt.r.Valid() if got != tt.want { - t.Errorf("range %v to %v Valid = %v; want %v", tt.r.From, tt.r.To, got, tt.want) + t.Errorf("range %v to %v Valid = %v; want %v", tt.r.From(), tt.r.To(), got, tt.want) } } } @@ -2671,6 +2671,7 @@ func TestNoAllocs(t *testing.T) { test("IPPRefix.Range", func() { sinkIPRange = MustParseIPPrefix("1.2.3.4/16").Range() }) // IPRange constructors + test("IPRangeFrom", func() { sinkIPRange = IPRangeFrom(IPv4(1, 2, 3, 4), IPv4(4, 3, 2, 1)) }) test("ParseIPRange", func() { sinkIPRange = panicIPR(ParseIPRange("1.2.3.0-1.2.4.150")) }) // IPRange methods From ef308e93f43ab855d9ee4f3158d689903316cd0c Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 14 May 2021 17:43:26 -0700 Subject: [PATCH 16/73] fix IP.BinaryMarshal handling of zero IP Found by fuzzing. Signed-off-by: Josh Bleecher Snyder --- netaddr.go | 18 +++++++++++------- netaddr_test.go | 8 ++++++-- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/netaddr.go b/netaddr.go index 8f55250..38878b5 100644 --- a/netaddr.go +++ b/netaddr.go @@ -831,16 +831,20 @@ func (ip *IP) UnmarshalText(text []byte) error { // MarshalBinary implements the encoding.BinaryMarshaler interface. func (ip IP) MarshalBinary() ([]byte, error) { - if ip.Is4() { + switch ip.z { + case z0: + return nil, nil + case z4: b := ip.As4() return b[:], nil + default: + b16 := ip.As16() + b := b16[:] + if z := ip.Zone(); z != "" { + b = append(b, []byte(z)...) + } + return b, nil } - b16 := ip.As16() - b := b16[:] - if z := ip.Zone(); z != "" { - b = append(b, []byte(z)...) - } - return b, nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. diff --git a/netaddr_test.go b/netaddr_test.go index ce2fc20..b993638 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -330,19 +330,23 @@ func TestIPMarshalUnmarshalBinary(t *testing.T) { ip string wantSize int }{ + {"", 0}, // zero IP {"1.2.3.4", 4}, {"fd7a:115c:a1e0:ab12:4843:cd96:626b:430b", 16}, {"::ffff:c000:0280", 16}, {"::ffff:c000:0280%eth0", 20}, } for _, tc := range tests { - ip := mustIP(tc.ip) + var ip IP + if len(tc.ip) > 0 { + ip = mustIP(tc.ip) + } b, err := ip.MarshalBinary() if err != nil { t.Fatal(err) } if len(b) != tc.wantSize { - t.Fatalf("ipv4 got %d; want %d", len(b), tc.wantSize) + t.Fatalf("%q encoded to size %d; want %d", tc.ip, len(b), tc.wantSize) } var ip2 IP if err := ip2.UnmarshalBinary(b); err != nil { From 4573b60a69aa51c9772e2eb7ad043fed3056f7dc Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 14 May 2021 17:42:09 -0700 Subject: [PATCH 17/73] add Marshal method round trip fuzzers and additional corpus Signed-off-by: Josh Bleecher Snyder --- corpus | 2 +- fuzz.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/corpus b/corpus index a387dc3..e9ea3d3 160000 --- a/corpus +++ b/corpus @@ -1 +1 @@ -Subproject commit a387dc3e9cd548a0ca228236c043484221394e77 +Subproject commit e9ea3d34b156985e9c089cb4a7784c0798aca983 diff --git a/fuzz.go b/fuzz.go index f40a004..83bd72e 100644 --- a/fuzz.go +++ b/fuzz.go @@ -66,6 +66,36 @@ func Fuzz(b []byte) int { panic(".Prior.Next did not round trip") } + { + buf, err := ip.MarshalText() + if err != nil { + panic(err) + } + var ip2 IP + err = ip2.UnmarshalText(buf) + if err != nil { + panic(err) + } + if ip != ip2 { + panic(err) + } + } + + { + buf, err := ip.MarshalBinary() + if err != nil { + panic(err) + } + var ip2 IP + err = ip2.UnmarshalBinary(buf) + if err != nil { + panic(err) + } + if ip != ip2 { + panic(fmt.Sprintf("IP MarshalBinary round trip failed: %v != %v", ip, ip2)) + } + } + port, err := ParseIPPort(s) if err == nil { s2 := port.String() @@ -79,6 +109,19 @@ func Fuzz(b []byte) int { if port2.String() != s2 { panic("IPPort String round trip identity failure") } + + buf, err := port.MarshalText() + if err != nil { + panic(err) + } + port2 = IPPort{} + err = port2.UnmarshalText(buf) + if err != nil { + panic(err) + } + if port != port2 { + panic(err) + } } ipp, err := ParseIPPrefix(s) @@ -95,6 +138,19 @@ func Fuzz(b []byte) int { if ipp2.String() != s2 { panic("IPPrefix String round trip identity failure") } + + buf, err := ipp.MarshalText() + if err != nil { + panic(err) + } + ipp2 = IPPrefix{} + err = ipp2.UnmarshalText(buf) + if err != nil { + panic(err) + } + if ipp != ipp2 { + panic(err) + } } return 0 From cb881eeb7b9f3280e6727d063b9f7ca7ba2d2e63 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 18 May 2021 11:12:22 -0700 Subject: [PATCH 18/73] add AppendTo method to IP, IPPort, and IPPrefix This allows the caller to bring their own buffer, thereby saving an allocation. The format is identical to that generated by MarshalText. Fixes #166 Signed-off-by: Josh Bleecher Snyder --- fuzz.go | 16 +++++++++ netaddr.go | 87 +++++++++++++++++++++++++++++++++++-------------- netaddr_test.go | 38 ++++++++++++++++++++- 3 files changed, 115 insertions(+), 26 deletions(-) diff --git a/fuzz.go b/fuzz.go index 83bd72e..07bfc79 100644 --- a/fuzz.go +++ b/fuzz.go @@ -7,6 +7,7 @@ package netaddr import ( + "bytes" "fmt" "net" "strings" @@ -71,6 +72,11 @@ func Fuzz(b []byte) int { if err != nil { panic(err) } + buf2 := make([]byte, 0, len(buf)) + buf2 = ip.AppendTo(buf2) + if !bytes.Equal(buf, buf2) { + panic("IP AppendTo != MarshalText") + } var ip2 IP err = ip2.UnmarshalText(buf) if err != nil { @@ -114,6 +120,11 @@ func Fuzz(b []byte) int { if err != nil { panic(err) } + buf2 := make([]byte, 0, len(buf)) + buf2 = port.AppendTo(buf2) + if !bytes.Equal(buf, buf2) { + panic("IPPort AppendTo != MarshalText") + } port2 = IPPort{} err = port2.UnmarshalText(buf) if err != nil { @@ -143,6 +154,11 @@ func Fuzz(b []byte) int { if err != nil { panic(err) } + buf2 := make([]byte, 0, len(buf)) + buf2 = ipp.AppendTo(buf2) + if !bytes.Equal(buf, buf2) { + panic("IPPrefix AppendTo != MarshalText") + } ipp2 = IPPrefix{} err = ipp2.UnmarshalText(buf) if err != nil { diff --git a/netaddr.go b/netaddr.go index 38878b5..1adbdea 100644 --- a/netaddr.go +++ b/netaddr.go @@ -691,6 +691,20 @@ func (ip IP) String() string { } } +// AppendTo appends a text encoding of ip, +// as generated by MarshalText, +// to b and returns the extended buffer. +func (ip IP) AppendTo(b []byte) []byte { + switch ip.z { + case z0: + return b + case z4: + return ip.appendTo4(b) + default: + return ip.appendTo6(b) + } +} + // digits is a string of the hex digits from 0 to f. It's used in // appendDecimal and appendHex to format IP addresses. const digits = "0123456789abcdef" @@ -968,29 +982,40 @@ func (p IPPort) String() string { return net.JoinHostPort(p.ip.String(), strconv.Itoa(int(p.port))) } +// AppendTo appends a text encoding of p, +// as generated by MarshalText, +// to b and returns the extended buffer. +func (p IPPort) AppendTo(b []byte) []byte { + switch p.ip.z { + case z0: + return b + case z4: + b = p.ip.appendTo4(b) + default: + b = append(b, '[') + b = p.ip.appendTo6(b) + b = append(b, ']') + } + b = append(b, ':') + b = strconv.AppendInt(b, int64(p.port), 10) + return b +} + // MarshalText implements the encoding.TextMarshaler interface. The // encoding is the same as returned by String, with one exception: if // p.IP() is the zero value, the encoding is the empty string. func (p IPPort) MarshalText() ([]byte, error) { + var max int switch p.ip.z { case z0: - return []byte(""), nil case z4: - max := len("255.255.255.255:65535") - b := make([]byte, 0, max) - b = p.ip.appendTo4(b) - b = append(b, ':') - b = strconv.AppendInt(b, int64(p.port), 10) - return b, nil + max = len("255.255.255.255:65535") default: - max := len("[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0]:65535") - b := make([]byte, 1, max) - b[0] = '[' - b = p.ip.appendTo6(b) - b = append(b, ']', ':') - b = strconv.AppendInt(b, int64(p.port), 10) - return b, nil + max = len("[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0]:65535") } + b := make([]byte, 0, max) + b = p.AppendTo(b) + return b, nil } // UnmarshalText implements the encoding.TextUnmarshaler @@ -1263,31 +1288,43 @@ func (p IPPrefix) Overlaps(o IPPrefix) bool { return p.ip == o.ip } -// MarshalText implements the encoding.TextMarshaler interface, -// The encoding is the same as returned by String, with one exception: -// If p is the zero value, the encoding is the empty string. -func (p IPPrefix) MarshalText() ([]byte, error) { +// AppendTo appends a text encoding of p, +// as generated by MarshalText, +// to b and returns the extended buffer. +func (p IPPrefix) AppendTo(b []byte) []byte { if p.IsZero() { - return []byte(""), nil + return b } if !p.Valid() { - return []byte("invalid IP prefix"), nil + return append(b, "invalid IP prefix"...) } // p.IP is non-zero, because p is valid. - var b []byte if p.ip.z == z4 { - max := len("255.255.255.255/32") - b = make([]byte, 0, max) b = p.ip.appendTo4(b) } else { - max := len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0/128") - b = make([]byte, 0, max) b = p.ip.appendTo6(b) } b = append(b, '/') b = appendDecimal(b, p.bits) + return b +} + +// MarshalText implements the encoding.TextMarshaler interface, +// The encoding is the same as returned by String, with one exception: +// If p is the zero value, the encoding is the empty string. +func (p IPPrefix) MarshalText() ([]byte, error) { + var max int + switch p.ip.z { + case z0: + case z4: + max = len("255.255.255.255/32") + default: + max = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%enp5s0/128") + } + b := make([]byte, 0, max) + b = p.AppendTo(b) return b, nil } diff --git a/netaddr_test.go b/netaddr_test.go index b993638..b464ef6 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -7,6 +7,8 @@ package netaddr import ( + "bytes" + "encoding" "encoding/json" "flag" "fmt" @@ -195,6 +197,9 @@ func TestParseIP(t *testing.T) { t.Errorf("ParseIP(%q).String() got %q, want %q", test.in, s, wants) } + // Check that AppendTo matches MarshalText. + testAppendToMarshal(t, got) + // Check that MarshalText/UnmarshalText work similarly to // ParseIP/String (see TestIPMarshalUnmarshal for // marshal-specific behavior that's not common with @@ -1330,6 +1335,8 @@ func TestIPPrefix(t *testing.T) { if got := prefix.String(); got != test.prefix { t.Errorf("prefix.String()=%q, want %q", got, test.prefix) } + + testAppendToMarshal(t, prefix) }) } } @@ -1580,7 +1587,14 @@ func TestParseIPPort(t *testing.T) { } }) - // TextMarshal and TextUnmarhsal mostly behave like + t.Run(test.in+"/AppendTo", func(t *testing.T) { + got, err := ParseIPPort(test.in) + if err == nil { + testAppendToMarshal(t, got) + } + }) + + // TextMarshal and TextUnmarshal mostly behave like // ParseIPPort and String. Divergent behavior are handled in // TestIPPortMarshalUnmarshal. t.Run(test.in+"/Marshal", func(t *testing.T) { @@ -1633,10 +1647,32 @@ func TestIPPortMarshalUnmarshal(t *testing.T) { if orig != back { t.Errorf("Marshal = %q; want %q", back, orig) } + + testAppendToMarshal(t, ipp) }) } } +type appendMarshaller interface { + encoding.TextMarshaler + AppendTo([]byte) []byte +} + +// testAppendToMarshal tests that x's AppendTo and MarshalText methods yield the same results. +// x's MarshalText method must not return an error. +func testAppendToMarshal(t *testing.T, x appendMarshaller) { + t.Helper() + m, err := x.MarshalText() + if err != nil { + t.Fatalf("(%v).MarshalText: %v", x, err) + } + a := make([]byte, 0, len(m)) + a = x.AppendTo(a) + if !bytes.Equal(m, a) { + t.Errorf("(%v).MarshalText = %q, (%v).AppendTo = %q", x, m, x, a) + } +} + func TestUDPAddrAllocs(t *testing.T) { for _, ep := range []string{"1.2.3.4:1234", "[::1]:1234"} { ipp, err := ParseIPPort(ep) From 493aec6a484eb39bfc204bccba60229950790826 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 19 May 2021 08:58:13 -0700 Subject: [PATCH 19/73] add more corpus entries Signed-off-by: Josh Bleecher Snyder --- corpus | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corpus b/corpus index e9ea3d3..0e1b974 160000 --- a/corpus +++ b/corpus @@ -1 +1 @@ -Subproject commit e9ea3d34b156985e9c089cb4a7784c0798aca983 +Subproject commit 0e1b97442a887586d82768491a1fc00faa60145e From 518ec021c38dc798a1ae99cba0d755853cf1abb2 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 19 May 2021 11:47:49 -0700 Subject: [PATCH 20/73] more corpus --- corpus | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corpus b/corpus index 0e1b974..f845a17 160000 --- a/corpus +++ b/corpus @@ -1 +1 @@ -Subproject commit 0e1b97442a887586d82768491a1fc00faa60145e +Subproject commit f845a17e9dc8358a65fbdbba2d944758bfbd40eb From 59dddd1388d106cfb34783cf485506346348edd6 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 19 May 2021 11:24:59 -0700 Subject: [PATCH 21/73] use "invalid IPPrefix" instead of "invalid IP prefix" This makes it consistent with other uses, all of which use their type name: invalid IP invalid IPRange invalid IPPort Signed-off-by: Josh Bleecher Snyder --- netaddr.go | 4 ++-- netaddr_test.go | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/netaddr.go b/netaddr.go index 1adbdea..4639205 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1296,7 +1296,7 @@ func (p IPPrefix) AppendTo(b []byte) []byte { return b } if !p.Valid() { - return append(b, "invalid IP prefix"...) + return append(b, "invalid IPPrefix"...) } // p.IP is non-zero, because p is valid. @@ -1348,7 +1348,7 @@ func (p *IPPrefix) UnmarshalText(text []byte) error { // String returns the CIDR notation of p: "/". func (p IPPrefix) String() string { if !p.Valid() { - return "invalid IP prefix" + return "invalid IPPrefix" } return fmt.Sprintf("%s/%d", p.ip, p.bits) } diff --git a/netaddr_test.go b/netaddr_test.go index b464ef6..33d55b7 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -2726,3 +2726,20 @@ func TestNoAllocs(t *testing.T) { sinkIPPrefix = panicPfxOK(a.Prefix()) }) } + +func TestIPPrefixString(t *testing.T) { + tests := []struct { + ipp IPPrefix + want string + }{ + {IPPrefix{}, "invalid IPPrefix"}, + {IPPrefixFrom(IP{}, 8), "invalid IPPrefix"}, + {IPPrefixFrom(MustParseIP("1.2.3.4"), 88), "invalid IPPrefix"}, + } + + for _, tt := range tests { + if got := tt.ipp.String(); got != tt.want { + t.Errorf("(%#v).String() = %q want %q", tt.ipp, got, tt.want) + } + } +} From cd0030af84f5933ca31962a3b81c73dca1497914 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 19 May 2021 11:07:14 -0700 Subject: [PATCH 22/73] correctly parse IPPrefix "::%0/00/80" Fixes #170 Signed-off-by: Josh Bleecher Snyder --- netaddr.go | 2 +- netaddr_test.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/netaddr.go b/netaddr.go index 4639205..16df9b8 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1148,7 +1148,7 @@ func FromStdIPNet(std *net.IPNet) (prefix IPPrefix, ok bool) { // // Note that masked address bits are not zeroed. Use Masked for that. func ParseIPPrefix(s string) (IPPrefix, error) { - i := strings.IndexByte(s, '/') + i := strings.LastIndexByte(s, '/') if i < 0 { return IPPrefix{}, fmt.Errorf("netaddr.ParseIPPrefix(%q): no '/'", s) } diff --git a/netaddr_test.go b/netaddr_test.go index 33d55b7..6e28089 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -1305,6 +1305,17 @@ func TestIPPrefix(t *testing.T) { contains: mustIPs("2001:db8::1"), notContains: mustIPs("fe80::1"), }, + { + prefix: "::%0/00/80", + ip: mustIP("::%0/00"), + bits: 80, + ipNet: &net.IPNet{ + IP: net.ParseIP("::"), // net.IPNet drops zones + Mask: net.CIDRMask(80, 128), + }, + contains: mustIPs("::%0/00"), + notContains: mustIPs("ff::%0/00"), + }, } for _, test := range tests { t.Run(test.prefix, func(t *testing.T) { From 955e0037604f3bb7b4c81a1826f6501878865c93 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 19 May 2021 11:20:54 -0700 Subject: [PATCH 23/73] add IPPort.Valid This mirrors IPPrefix.Valid. Signed-off-by: Josh Bleecher Snyder --- netaddr.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netaddr.go b/netaddr.go index 16df9b8..ebb7b2c 100644 --- a/netaddr.go +++ b/netaddr.go @@ -973,6 +973,9 @@ func MustParseIPPort(s string) IPPort { // IsZero reports whether p is its zero value. func (p IPPort) IsZero() bool { return p == IPPort{} } +// Valid reports whether p.IP() is non-zero. +func (p IPPort) Valid() bool { return !p.ip.IsZero() } + func (p IPPort) String() string { if p.ip.z == z4 { a := p.ip.As4() From 01d0c2187b79ed906fd878093cf08eb8900794fc Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 19 May 2021 11:22:18 -0700 Subject: [PATCH 24/73] return "invalid IPPort" from String on invalid IPPorts Signed-off-by: Josh Bleecher Snyder --- netaddr.go | 11 ++++++++--- netaddr_test.go | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/netaddr.go b/netaddr.go index ebb7b2c..96ad0eb 100644 --- a/netaddr.go +++ b/netaddr.go @@ -974,15 +974,20 @@ func MustParseIPPort(s string) IPPort { func (p IPPort) IsZero() bool { return p == IPPort{} } // Valid reports whether p.IP() is non-zero. +// All ports are valid, including zero. func (p IPPort) Valid() bool { return !p.ip.IsZero() } func (p IPPort) String() string { - if p.ip.z == z4 { + switch p.ip.z { + case z0: + return "invalid IPPort" + case z4: a := p.ip.As4() return fmt.Sprintf("%d.%d.%d.%d:%d", a[0], a[1], a[2], a[3], p.port) + default: + // TODO: this could be more efficient allocation-wise: + return net.JoinHostPort(p.ip.String(), strconv.Itoa(int(p.port))) } - // TODO: this could be more efficient allocation-wise: - return net.JoinHostPort(p.ip.String(), strconv.Itoa(int(p.port))) } // AppendTo appends a text encoding of p, diff --git a/netaddr_test.go b/netaddr_test.go index 6e28089..10f6562 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -2754,3 +2754,19 @@ func TestIPPrefixString(t *testing.T) { } } } + +func TestInvalidIPPortString(t *testing.T) { + tests := []struct { + ipp IPPort + want string + }{ + {IPPort{}, "invalid IPPort"}, + {IPPortFrom(IP{}, 80), "invalid IPPort"}, + } + + for _, tt := range tests { + if got := tt.ipp.String(); got != tt.want { + t.Errorf("(%#v).String() = %q want %q", tt.ipp, got, tt.want) + } + } +} From f0b6b1eb602de39bc8c172f290e6045238b3bdeb Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 20 May 2021 19:28:09 -0700 Subject: [PATCH 25/73] Make IPRange.less sort subset ranges after their superset. This doesn't alter the behavior of dependent code, but does make the sort order slightly more "natural" and consistent. Fixes #145. Signed-off-by: David Anderson --- netaddr.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netaddr.go b/netaddr.go index 96ad0eb..26a9db6 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1473,14 +1473,14 @@ func (r IPRange) contains(addr IP) bool { return r.from.Compare(addr) <= 0 && r.to.Compare(addr) >= 0 } -// less returns whether r is "before" other. It is before if r.From() is -// before other.From(), or if they're equal, the shorter range is -// before. +// less reports whether r is "before" other. It is before if r.From() +// is before other.From(). If they're equal, then the larger range +// (higher To()) comes first. func (r IPRange) less(other IPRange) bool { if cmp := r.from.Compare(other.from); cmp != 0 { return cmp < 0 } - return r.to.Less(other.to) + return other.to.Less(r.to) } // entirelyBefore returns whether r lies entirely before other in IP From 147ddd82a8777d691ff37d792de0e2255f3120bc Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 19 May 2021 09:16:22 -0700 Subject: [PATCH 26/73] fuzz: refactor out helpers The fuzz function had gotten large and repetitive. Create helpers. Signed-off-by: Josh Bleecher Snyder --- fuzz.go | 245 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 126 insertions(+), 119 deletions(-) diff --git a/fuzz.go b/fuzz.go index 07bfc79..9b7f5ad 100644 --- a/fuzz.go +++ b/fuzz.go @@ -8,8 +8,10 @@ package netaddr import ( "bytes" + "encoding" "fmt" "net" + "reflect" "strings" ) @@ -18,33 +20,8 @@ func Fuzz(b []byte) int { ip, err := ParseIP(s) if err == nil { - s2 := ip.String() - // There's no guarantee that ip.String() will match s. - // But a round trip the other direction ought to succeed. - ip2, err := ParseIP(s2) - if err != nil { - panic(err) - } - if ip2 != ip { - fmt.Printf("ip=%#v ip2=%#v\n", ip, ip2) - panic("IP round trip identity failure") - } - if s2 != ip2.String() { - panic("IP String round trip identity failure") - } - - b, err := ip.MarshalBinary() - if err != nil { - panic(err) - } - var ip3 IP - if err := ip3.UnmarshalBinary(b); err != nil { - panic(err) - } - if ip != ip3 { - fmt.Printf("ip=%#v ip3=%#v\n", ip, ip3) - panic("IP binary marshal round trip identity failure") - } + checkStringParseRoundTrip(ip, parseIP) + checkEncoding(ip) } // Check that we match the standard library's IP parser, modulo zones. if !strings.Contains(s, "%") { @@ -67,107 +44,137 @@ func Fuzz(b []byte) int { panic(".Prior.Next did not round trip") } - { - buf, err := ip.MarshalText() - if err != nil { - panic(err) - } - buf2 := make([]byte, 0, len(buf)) - buf2 = ip.AppendTo(buf2) - if !bytes.Equal(buf, buf2) { - panic("IP AppendTo != MarshalText") - } - var ip2 IP - err = ip2.UnmarshalText(buf) - if err != nil { - panic(err) - } - if ip != ip2 { - panic(err) - } + port, err := ParseIPPort(s) + if err == nil { + checkStringParseRoundTrip(port, parseIPPort) + checkEncoding(port) } - { - buf, err := ip.MarshalBinary() - if err != nil { - panic(err) - } - var ip2 IP - err = ip2.UnmarshalBinary(buf) - if err != nil { - panic(err) - } - if ip != ip2 { - panic(fmt.Sprintf("IP MarshalBinary round trip failed: %v != %v", ip, ip2)) - } + ipp, err := ParseIPPrefix(s) + if err == nil { + checkStringParseRoundTrip(ipp, parseIPPrefix) + checkEncoding(ipp) } - port, err := ParseIPPort(s) - if err == nil { - s2 := port.String() - port2, err := ParseIPPort(s2) - if err != nil { - panic(err) - } - if port2 != port { - panic("IPPort round trip identity failure") - } - if port2.String() != s2 { - panic("IPPort String round trip identity failure") - } + return 0 +} - buf, err := port.MarshalText() - if err != nil { - panic(err) - } - buf2 := make([]byte, 0, len(buf)) - buf2 = port.AppendTo(buf2) - if !bytes.Equal(buf, buf2) { - panic("IPPort AppendTo != MarshalText") - } - port2 = IPPort{} - err = port2.UnmarshalText(buf) - if err != nil { - panic(err) - } - if port != port2 { - panic(err) - } +// Hopefully some of these generic helpers will eventually make their way to the standard library. +// See https://github.com/golang/go/issues/46268. + +// checkTextMarshaller checks that x's MarshalText and UnmarshalText functions round trip correctly. +func checkTextMarshaller(x encoding.TextMarshaler) { + buf, err := x.MarshalText() + if err == nil { + return + } + y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.TextUnmarshaler) + err = y.UnmarshalText(buf) + if err != nil { + fmt.Printf("(%v).MarshalText() = %q\n", x, buf) + panic(fmt.Sprintf("(%T).UnmarshalText(%q) = %v", y, buf, err)) + } + if !reflect.DeepEqual(x, y) { + fmt.Printf("(%v).MarshalText() = %q\n", x, buf) + fmt.Printf("(%T).UnmarshalText(%q) = %v", y, buf, y) + panic(fmt.Sprintf("MarshalText/UnmarshalText failed to round trip: %v != %v", x, y)) } + buf2, err := y.(encoding.TextMarshaler).MarshalText() + if err != nil { + fmt.Printf("(%v).MarshalText() = %q\n", x, buf) + fmt.Printf("(%T).UnmarshalText(%q) = %v", y, buf, y) + panic(fmt.Sprintf("failed to MarshalText a second time: %v", err)) + } + if !bytes.Equal(buf, buf2) { + fmt.Printf("(%v).MarshalText() = %q\n", x, buf) + fmt.Printf("(%T).UnmarshalText(%q) = %v", y, buf, y) + fmt.Printf("(%v).MarshalText() = %q\n", y, buf2) + panic(fmt.Sprintf("second MarshalText differs from first: %q != %q", buf, buf2)) + } +} - ipp, err := ParseIPPrefix(s) +// checkBinaryMarshaller checks that x's MarshalText and UnmarshalText functions round trip correctly. +func checkBinaryMarshaller(x encoding.BinaryMarshaler) { + buf, err := x.MarshalBinary() if err == nil { - s2 := ipp.String() - ipp2, err := ParseIPPrefix(s2) - if err != nil { - panic(err) - } - if ipp2 != ipp { - fmt.Printf("ipp=%#v ipp=%#v\n", ipp, ipp2) - panic("IPPrefix round trip identity failure") - } - if ipp2.String() != s2 { - panic("IPPrefix String round trip identity failure") - } + return + } + y := reflect.New(reflect.TypeOf(x)).Interface().(encoding.BinaryUnmarshaler) + err = y.UnmarshalBinary(buf) + if err != nil { + fmt.Printf("(%v).MarshalBinary() = %q\n", x, buf) + panic(fmt.Sprintf("(%T).UnmarshalBinary(%q) = %v", y, buf, err)) + } + if !reflect.DeepEqual(x, y) { + fmt.Printf("(%v).MarshalBinary() = %q\n", x, buf) + fmt.Printf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) + panic(fmt.Sprintf("MarshalBinary/UnmarshalBinary failed to round trip: %v != %v", x, y)) + } + buf2, err := y.(encoding.BinaryMarshaler).MarshalBinary() + if err != nil { + fmt.Printf("(%v).MarshalBinary() = %q\n", x, buf) + fmt.Printf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) + panic(fmt.Sprintf("failed to MarshalBinary a second time: %v", err)) + } + if !bytes.Equal(buf, buf2) { + fmt.Printf("(%v).MarshalBinary() = %q\n", x, buf) + fmt.Printf("(%T).UnmarshalBinary(%q) = %v", y, buf, y) + fmt.Printf("(%v).MarshalBinary() = %q\n", y, buf2) + panic(fmt.Sprintf("second MarshalBinary differs from first: %q != %q", buf, buf2)) + } +} - buf, err := ipp.MarshalText() - if err != nil { - panic(err) - } - buf2 := make([]byte, 0, len(buf)) - buf2 = ipp.AppendTo(buf2) - if !bytes.Equal(buf, buf2) { - panic("IPPrefix AppendTo != MarshalText") - } - ipp2 = IPPrefix{} - err = ipp2.UnmarshalText(buf) - if err != nil { - panic(err) - } - if ipp != ipp2 { - panic(err) - } +type appendMarshaller interface { + encoding.TextMarshaler + AppendTo([]byte) []byte +} + +// checkTextMarshalMatchesAppendTo checks that x's MarshalText matches x's AppendTo. +func checkTextMarshalMatchesAppendTo(x appendMarshaller) { + buf, err := x.MarshalText() + if err != nil { + panic(err) + } + buf2 := make([]byte, 0, len(buf)) + buf2 = x.AppendTo(buf2) + if !bytes.Equal(buf, buf2) { + panic(fmt.Sprintf("%v: MarshalText = %q, AppendTo = %q", x, buf, buf2)) } +} - return 0 +// parseType are trampoline functions that give ParseType functions the same signature. +// This would be nicer with generics. +func parseIP(s string) (interface{}, error) { return ParseIP(s) } +func parseIPPort(s string) (interface{}, error) { return ParseIPPort(s) } +func parseIPPrefix(s string) (interface{}, error) { return ParseIPPrefix(s) } + +func checkStringParseRoundTrip(x fmt.Stringer, parse func(string) (interface{}, error)) { + s := x.String() + y, err := parse(s) + if err != nil { + panic(err) + } + if !reflect.DeepEqual(x, y) { + fmt.Printf("x=%#v y=%#v\n", x, y) + panic(fmt.Sprintf("%T round trip identity failure", x)) + } + s2 := y.(fmt.Stringer).String() + if s != s2 { + fmt.Printf("s=%#v s2=%#v\n", s, s2) + panic(fmt.Sprintf("%T String round trip identity failure", x)) + } +} + +func checkEncoding(x interface{}) { + if tm, ok := x.(encoding.TextMarshaler); ok { + checkTextMarshaller(tm) + } + if bm, ok := x.(encoding.BinaryMarshaler); ok { + checkBinaryMarshaller(bm) + } + if am, ok := x.(appendMarshaller); ok { + checkTextMarshalMatchesAppendTo(am) + } } + +// TODO: add helpers that check that String matches MarshalText for non-zero-ish values From 9ee55bc0c50b982b23a55fce1e742e84c61d6908 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 19 May 2021 11:32:32 -0700 Subject: [PATCH 27/73] fuzz more invalid values Signed-off-by: Josh Bleecher Snyder --- fuzz.go | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/fuzz.go b/fuzz.go index 9b7f5ad..06d90a5 100644 --- a/fuzz.go +++ b/fuzz.go @@ -18,11 +18,10 @@ import ( func Fuzz(b []byte) int { s := string(b) - ip, err := ParseIP(s) - if err == nil { - checkStringParseRoundTrip(ip, parseIP) - checkEncoding(ip) - } + ip, _ := ParseIP(s) + checkStringParseRoundTrip(ip, parseIP) + checkEncoding(ip) + // Check that we match the standard library's IP parser, modulo zones. if !strings.Contains(s, "%") { stdip := net.ParseIP(s) @@ -49,12 +48,18 @@ func Fuzz(b []byte) int { checkStringParseRoundTrip(port, parseIPPort) checkEncoding(port) } + port = IPPortFrom(ip, 80) + checkStringParseRoundTrip(port, parseIPPort) + checkEncoding(port) ipp, err := ParseIPPrefix(s) if err == nil { checkStringParseRoundTrip(ipp, parseIPPrefix) checkEncoding(ipp) } + ipp = IPPrefixFrom(ip, 8) + checkStringParseRoundTrip(ipp, parseIPPrefix) + checkEncoding(ipp) return 0 } @@ -149,13 +154,25 @@ func parseIPPort(s string) (interface{}, error) { return ParseIPPort(s) } func parseIPPrefix(s string) (interface{}, error) { return ParseIPPrefix(s) } func checkStringParseRoundTrip(x fmt.Stringer, parse func(string) (interface{}, error)) { + v, vok := x.(interface{ Valid() bool }) + if vok && !v.Valid() { + // Ignore invalid values. + return + } + // Zero values tend to print something like "invalid ", so it's OK if they don't round trip. + // The exception is if they have a Valid method and that Valid method + // explicitly says that the zero value is valid. + z, zok := x.(interface{ IsZero() bool }) + if zok && z.IsZero() && !(vok && v.Valid()) { + return + } s := x.String() y, err := parse(s) if err != nil { - panic(err) + panic(fmt.Sprintf("s=%q err=%v", s, err)) } if !reflect.DeepEqual(x, y) { - fmt.Printf("x=%#v y=%#v\n", x, y) + fmt.Printf("s=%q x=%#v y=%#v\n", s, x, y) panic(fmt.Sprintf("%T round trip identity failure", x)) } s2 := y.(fmt.Stringer).String() From d57edf19c51753b7d583a5f5054d7d1d2d3cb1ad Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Sun, 23 May 2021 14:39:03 +0100 Subject: [PATCH 28/73] Add RFC references for IP.Is() methods These helped me to cross reference inet.af/netaddr, net.IP, and github.com/mellowdrifter/bogons for #151. Signed-off-by: Alex Willmer --- netaddr.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/netaddr.go b/netaddr.go index 26a9db6..d1da3b4 100644 --- a/netaddr.go +++ b/netaddr.go @@ -537,9 +537,13 @@ func (ip IP) WithZone(zone string) IP { // IsLinkLocalUnicast reports whether ip is a link-local unicast address. // If ip is the zero value, it will return false. func (ip IP) IsLinkLocalUnicast() bool { + // Dynamic Configuration of IPv4 Link-Local Addresses + // https://datatracker.ietf.org/doc/html/rfc3927#section-2.1 if ip.Is4() { return ip.v4(0) == 169 && ip.v4(1) == 254 } + // IP Version 6 Addressing Architecture (2.5.6 Link-Local IPv6 Unicast Addresses) + // https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.6 if ip.Is6() { return ip.v6u16(0) == 0xfe80 } @@ -549,9 +553,13 @@ func (ip IP) IsLinkLocalUnicast() bool { // IsLoopback reports whether ip is a loopback address. If ip is the zero value, // it will return false. func (ip IP) IsLoopback() bool { + // Requirements for Internet Hosts -- Communication Layers (3.2.1.3 Addressing) + // https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.1.3 if ip.Is4() { return ip.v4(0) == 127 } + // IP Version 6 Addressing Architecture (2.5.3 The Loopback Address) + // https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.3 if ip.Is6() { return ip.addr.hi == 0 && ip.addr.lo == 1 } @@ -561,6 +569,8 @@ func (ip IP) IsLoopback() bool { // IsMulticast reports whether ip is a multicast address. If ip is the zero // value, it will return false. func (ip IP) IsMulticast() bool { + // Host Extensions for IP Multicasting (4. HOST GROUP ADDRESSES) + // https://datatracker.ietf.org/doc/html/rfc1112#section-4 if ip.Is4() { return ip.v4(0)&0xf0 == 0xe0 } @@ -574,6 +584,8 @@ func (ip IP) IsMulticast() bool { // multicast address. If ip is the zero value or an IPv4 address, it will return // false. func (ip IP) IsInterfaceLocalMulticast() bool { + // IPv6 Addressing Architecture (2.7.1. Pre-Defined Multicast Addresses) + // https://datatracker.ietf.org/doc/html/rfc4291#section-2.7.1 if ip.Is6() { return ip.v6u16(0)&0xff0f == 0xff01 } @@ -583,9 +595,13 @@ func (ip IP) IsInterfaceLocalMulticast() bool { // IsLinkLocalMulticast reports whether ip is a link-local multicast address. // If ip is the zero value, it will return false. func (ip IP) IsLinkLocalMulticast() bool { + // IPv4 Multicast Guidelines (4. Local Network Control Block (224.0.0/24)) + // https://datatracker.ietf.org/doc/html/rfc5771#section-4 if ip.Is4() { return ip.v4(0) == 224 && ip.v4(1) == 0 && ip.v4(2) == 0 } + // IPv6 Addressing Architecture (2.7.1. Pre-Defined Multicast Addresses) + // https://datatracker.ietf.org/doc/html/rfc4291#section-2.7.1 if ip.Is6() { return ip.v6u16(0)&0xff0f == 0xff02 } From c0eff8545de6113ee7899372f356c02134f2e02a Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Tue, 25 May 2021 10:14:59 -0400 Subject: [PATCH 29/73] netaddr: add IP.StringExpanded to expand IPv6 address strings (#136) Signed-off-by: Matt Layher --- netaddr.go | 33 +++++++++++++++++++++++++++++++++ netaddr_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/netaddr.go b/netaddr.go index d1da3b4..ea72c6c 100644 --- a/netaddr.go +++ b/netaddr.go @@ -756,6 +756,11 @@ func appendHex(b []byte, x uint16) []byte { return append(b, digits[x&0xf]) } +// appendHexPad appends the fully padded hex string representation of x to b. +func appendHexPad(b []byte, x uint16) []byte { + return append(b, digits[x>>12], digits[x>>8&0xf], digits[x>>4&0xf], digits[x&0xf]) +} + func (ip IP) string4() string { const max = len("255.255.255.255") ret := make([]byte, 0, max) @@ -826,6 +831,34 @@ func (ip IP) appendTo6(ret []byte) []byte { return ret } +// StringExpanded is like String but IPv6 addresses are expanded with leading +// zeroes and no "::" compression. For example, "2001:db8::1" becomes +// "2001:0db8:0000:0000:0000:0000:0000:0001". +func (ip IP) StringExpanded() string { + switch ip.z { + case z0, z4: + return ip.String() + } + + const size = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + ret := make([]byte, 0, size) + for i := uint8(0); i < 8; i++ { + if i > 0 { + ret = append(ret, ':') + } + + ret = appendHexPad(ret, ip.v6u16(i)) + } + + if ip.z != z6noz { + // The addition of a zone will cause a second allocation, but when there + // is no zone the ret slice will be stack allocated. + ret = append(ret, '%') + ret = append(ret, ip.Zone()...) + } + return string(ret) +} + // MarshalText implements the encoding.TextMarshaler interface, // The encoding is the same as returned by String, with one exception: // If ip is the zero value, the encoding is the empty string. diff --git a/netaddr_test.go b/netaddr_test.go index 10f6562..10572be 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -784,6 +784,41 @@ func TestLessCompare(t *testing.T) { } } +func TestIPStringExpanded(t *testing.T) { + tests := []struct { + ip IP + s string + }{ + { + ip: IP{}, + s: "invalid IP", + }, + { + ip: mustIP("192.0.2.1"), + s: "192.0.2.1", + }, + { + ip: mustIP("2001:db8::1"), + s: "2001:0db8:0000:0000:0000:0000:0000:0001", + }, + { + ip: mustIP("2001:db8::1%eth0"), + s: "2001:0db8:0000:0000:0000:0000:0000:0001%eth0", + }, + } + + for _, tt := range tests { + t.Run(tt.ip.String(), func(t *testing.T) { + want := tt.s + got := tt.ip.StringExpanded() + + if got != want { + t.Fatalf("got %s, want %s", got, want) + } + }) + } +} + func TestIPPrefixMasking(t *testing.T) { type subtest struct { ip IP @@ -1884,6 +1919,18 @@ func BenchmarkIPString(b *testing.B) { } } +func BenchmarkIPStringExpanded(b *testing.B) { + for _, test := range parseBenchInputs { + ip := MustParseIP(test.ip) + b.Run(test.name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sinkString = ip.StringExpanded() + } + }) + } +} + func BenchmarkIPMarshalText(b *testing.B) { b.ReportAllocs() ip := MustParseIP("66.55.44.33") From db50905a50bec805c7b6105e7955bfd34634d7b2 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Sun, 23 May 2021 13:22:29 +0100 Subject: [PATCH 30/73] Add selected IP examples (ExampleIP(), Example_IP.Unmap(), etc) These helped me understand the finer details of netaddr.IP. They should get rendered in the docs, like https://pkg.go.dev/inet.af/netaddr#IPSet Signed-off-by: Alex Willmer --- example_test.go | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/example_test.go b/example_test.go index 5280c4d..68a2452 100644 --- a/example_test.go +++ b/example_test.go @@ -6,10 +6,161 @@ package netaddr_test import ( "fmt" + "os" + "text/tabwriter" "inet.af/netaddr" ) +func ExampleIP() { + ip, err := netaddr.ParseIP("192.0.2.3") + if err != nil { + panic(err) + } + + // netaddr.IP supports comparison using == + fmt.Println(ip == netaddr.IPv4(192, 0, 2, 3)) + + // netaddr.IP can be used as a map key + hosts := map[netaddr.IP]string{ip: "example.net"} + fmt.Println(hosts) + // Output: + // true + // map[192.0.2.3:example.net] +} + +func ExampleIP_properties() { + var zeroIP netaddr.IP + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, "String()\tZone()\tIsZero()\tIs4()\tIs6()\tIs4in6()") + for _, ip := range []netaddr.IP{ + zeroIP, + netaddr.MustParseIP("192.0.2.3"), + netaddr.MustParseIP("2001:db8::68"), + netaddr.MustParseIP("2001:db8::68%eth0"), + netaddr.MustParseIP("::ffff:192.0.2.3"), + } { + fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\n", ip, ip.Zone(), ip.IsZero(), ip.Is4(), ip.Is6(), ip.Is4in6()) + } + w.Flush() + // Output: + // String() Zone() IsZero() Is4() Is6() Is4in6() + // invalid IP true false false false + // 192.0.2.3 false true false false + // 2001:db8::68 false false true false + // 2001:db8::68%eth0 eth0 false false true false + // ::ffff:c000:203 false false true true +} + +func ExampleIP_Is4() { + var zeroIP netaddr.IP + ipv4 := netaddr.MustParseIP("192.0.2.3") + ipv6 := netaddr.MustParseIP("2001:db8::68") + ip4in6 := netaddr.MustParseIP("::ffff:192.0.2.3") + + fmt.Printf("IP{}.Is4() -> %v\n", zeroIP.Is4()) + fmt.Printf("(%v).Is4() -> %v\n", ipv4, ipv4.Is4()) + fmt.Printf("(%v).Is4() -> %v\n", ipv6, ipv6.Is4()) + fmt.Printf("(%v).Is4() -> %v\n", ip4in6, ip4in6.Is4()) + // Output: + // IP{}.Is4() -> false + // (192.0.2.3).Is4() -> true + // (2001:db8::68).Is4() -> false + // (::ffff:c000:203).Is4() -> false +} + +func ExampleIP_Is4in6() { + var zeroIP netaddr.IP + ipv4 := netaddr.MustParseIP("192.0.2.3") + ipv6 := netaddr.MustParseIP("2001:db8::68") + ip4in6 := netaddr.MustParseIP("::ffff:192.0.2.3") + + fmt.Printf("IP{}.Is4in6() -> %v\n", zeroIP.Is4in6()) + fmt.Printf("(%v).Is4in6() -> %v\n", ipv4, ipv4.Is4in6()) + fmt.Printf("(%v).Is4in6() -> %v\n", ipv6, ipv6.Is4in6()) + fmt.Printf("(%v).Is4in6() -> %v\n", ip4in6, ip4in6.Is4in6()) + // Output: + // IP{}.Is4in6() -> false + // (192.0.2.3).Is4in6() -> false + // (2001:db8::68).Is4in6() -> false + // (::ffff:c000:203).Is4in6() -> true +} + +func ExampleIP_Is6() { + var zeroIP netaddr.IP + ipv4 := netaddr.MustParseIP("192.0.2.3") + ipv6 := netaddr.MustParseIP("2001:db8::68") + ip4in6 := netaddr.MustParseIP("::ffff:192.0.2.3") + + fmt.Printf("IP{}.Is6() -> %v\n", zeroIP.Is4in6()) + fmt.Printf("(%v).Is6() -> %v\n", ipv4, ipv4.Is6()) + fmt.Printf("(%v).Is6() -> %v\n", ipv6, ipv6.Is6()) + fmt.Printf("(%v).Is6() -> %v\n", ip4in6, ip4in6.Is6()) + // Output: + // IP{}.Is6() -> false + // (192.0.2.3).Is6() -> false + // (2001:db8::68).Is6() -> true + // (::ffff:c000:203).Is6() -> true +} + +func ExampleIP_IsZero() { + var zeroIP netaddr.IP + ipv4AllZeroes := netaddr.MustParseIP("0.0.0.0") + ipv6AllZeroes := netaddr.MustParseIP("::") + + fmt.Printf("IP{}.IsZero() -> %v\n", zeroIP.IsZero()) + fmt.Printf("(%v).IsZero() -> %v\n", ipv4AllZeroes, ipv4AllZeroes.IsZero()) + fmt.Printf("(%v).IsZero() -> %v\n", ipv6AllZeroes, ipv6AllZeroes.IsZero()) + // Output: + // IP{}.IsZero() -> true + // (0.0.0.0).IsZero() -> false + // (::).IsZero() -> false +} + +func ExampleIP_String() { + ipv4 := netaddr.MustParseIP("192.0.2.3") + ipv6 := netaddr.MustParseIP("2001:db8::68") + ip4in6 := netaddr.MustParseIP("::ffff:192.0.2.3") + + fmt.Printf("(%v).String() -> %v\n", ipv4, ipv4.String()) + fmt.Printf("(%v).String() -> %v\n", ipv6, ipv6.String()) + fmt.Printf("(%v).String() -> %v\n", ip4in6, ip4in6.String()) + // Output: + // (192.0.2.3).String() -> 192.0.2.3 + // (2001:db8::68).String() -> 2001:db8::68 + // (::ffff:c000:203).String() -> ::ffff:c000:203 +} + +func ExampleIP_Unmap() { + ipv4 := netaddr.MustParseIP("192.0.2.3") + ipv6 := netaddr.MustParseIP("2001:db8::68") + ip4in6 := netaddr.MustParseIP("::ffff:192.0.2.3") + + fmt.Printf("(%v).Unmap() -> %v\n", ipv4, ipv4.Unmap()) + fmt.Printf("(%v).Unmap() -> %v\n", ipv6, ipv6.Unmap()) + fmt.Printf("(%v).Unmap() -> %v\n", ip4in6, ip4in6.Unmap()) + // Output: + // (192.0.2.3).Unmap() -> 192.0.2.3 + // (2001:db8::68).Unmap() -> 2001:db8::68 + // (::ffff:c000:203).Unmap() -> 192.0.2.3 +} + +func ExampleIP_WithZone() { + ipv4 := netaddr.MustParseIP("192.0.2.3") + ipv6 := netaddr.MustParseIP("2001:db8::68") + ipv6Zoned := netaddr.MustParseIP("2001:db8::68%eth0") + + fmt.Printf("(%v).WithZone(\"newzone\") -> %v\n", ipv4, ipv4.WithZone("newzone")) + fmt.Printf("(%v).WithZone(\"newzone\") -> %v\n", ipv6, ipv6.WithZone("newzone")) + fmt.Printf("(%v).WithZone(\"newzone\") -> %v\n", ipv6Zoned, ipv6Zoned.WithZone("newzone")) + fmt.Printf("(%v).WithZone(\"\") -> %v\n", ipv6Zoned, ipv6Zoned.WithZone("")) + // Output: + // (192.0.2.3).WithZone("newzone") -> 192.0.2.3 + // (2001:db8::68).WithZone("newzone") -> 2001:db8::68%newzone + // (2001:db8::68%eth0).WithZone("newzone") -> 2001:db8::68%newzone + // (2001:db8::68%eth0).WithZone("") -> 2001:db8::68 +} + func ExampleIPSet() { var b netaddr.IPSetBuilder From 5da4e46de2c2120af52ed0f7e2c9f5115eedb2d4 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 2 Jun 2021 08:18:30 -0700 Subject: [PATCH 31/73] AUTHORS: update --- AUTHORS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AUTHORS b/AUTHORS index 0c7e8ac..ac0d159 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1,4 @@ +Alex Willmer +Matt Layher Tailscale Inc. +Tobias Klauser From e4c51c86b9f85f324005b1c44a4eacf4fd1a5151 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Tue, 25 May 2021 23:52:53 +0100 Subject: [PATCH 32/73] fix IP.IsLinkLocalUnicast for IPv6 addresses >= 0xfe81 Signed-off-by: Alex Willmer Fixes #184 --- netaddr.go | 12 +++++++----- netaddr_test.go | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/netaddr.go b/netaddr.go index ea72c6c..37bc86f 100644 --- a/netaddr.go +++ b/netaddr.go @@ -542,10 +542,10 @@ func (ip IP) IsLinkLocalUnicast() bool { if ip.Is4() { return ip.v4(0) == 169 && ip.v4(1) == 254 } - // IP Version 6 Addressing Architecture (2.5.6 Link-Local IPv6 Unicast Addresses) - // https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.6 + // IP Version 6 Addressing Architecture (2.4 Address Type Identification) + // https://datatracker.ietf.org/doc/html/rfc4291#section-2.4 if ip.Is6() { - return ip.v6u16(0) == 0xfe80 + return ip.v6u16(0)&0xffc0 == 0xfe80 } return false // zero value } @@ -558,8 +558,8 @@ func (ip IP) IsLoopback() bool { if ip.Is4() { return ip.v4(0) == 127 } - // IP Version 6 Addressing Architecture (2.5.3 The Loopback Address) - // https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.3 + // IP Version 6 Addressing Architecture (2.4 Address Type Identification) + // https://datatracker.ietf.org/doc/html/rfc4291#section-2.4 if ip.Is6() { return ip.addr.hi == 0 && ip.addr.lo == 1 } @@ -574,6 +574,8 @@ func (ip IP) IsMulticast() bool { if ip.Is4() { return ip.v4(0)&0xf0 == 0xe0 } + // IP Version 6 Addressing Architecture (2.4 Address Type Identification) + // https://datatracker.ietf.org/doc/html/rfc4291#section-2.4 if ip.Is6() { return ip.addr.hi>>(64-8) == 0xff // ip.v6(0) == 0xff } diff --git a/netaddr_test.go b/netaddr_test.go index 10572be..866f9a0 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -563,6 +563,7 @@ func TestIPProperties(t *testing.T) { llu4 = mustIP("169.254.0.1") llu6 = mustIP("fe80::1") + llu6Last = mustIP("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff") lluZone6 = mustIP("fe80::1%eth0") loopback4 = mustIP("127.0.0.1") @@ -625,6 +626,11 @@ func TestIPProperties(t *testing.T) { ip: llu6, linkLocalUnicast: true, }, + { + name: "link-local unicast v6Addr upper bound", + ip: llu6Last, + linkLocalUnicast: true, + }, { name: "link-local unicast v6AddrZone", ip: lluZone6, From ce747b1e945a36262d158c66140a9d4754521073 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 10:27:28 -0700 Subject: [PATCH 33/73] Document v6 zone behavior in the package doc. Signed-off-by: David Anderson --- netaddr.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/netaddr.go b/netaddr.go index 37bc86f..6ff0796 100644 --- a/netaddr.go +++ b/netaddr.go @@ -10,6 +10,11 @@ // Notably, this package's IP type takes less memory, is immutable, // comparable (supports == and being a map key), and more. See // https://github.com/inetaf/netaddr for background. +// +// IPv6 Zones +// +// IP and IPPort are the only types in this package that support IPv6 +// zones. Other types silently drop any passed-in zones. package netaddr // import "inet.af/netaddr" import ( From 37e7462ee902babe670aa65d045eb79375132f74 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 10:40:39 -0700 Subject: [PATCH 34/73] Add a missing internal helper docstring. Signed-off-by: David Anderson --- netaddr.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netaddr.go b/netaddr.go index 6ff0796..f58a370 100644 --- a/netaddr.go +++ b/netaddr.go @@ -393,6 +393,8 @@ func (ip IP) v6(i uint8) uint8 { return uint8(*(ip.addr.halves()[(i/8)%2]) >> ((7 - i%8) * 8)) } +// v6u16 returns the i'th 16-bit word of ip. If ip is an IPv4 address, +// this accesses the IPv4-mapped IPv6 address form of the IP. func (ip IP) v6u16(i uint8) uint16 { return uint16(*(ip.addr.halves()[(i/4)%2]) >> ((3 - i%4) * 16)) } From ea4b9d5d827322928e788d182b4e7453c5bfa761 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 11:04:02 -0700 Subject: [PATCH 35/73] Strip IPv6 zones from IPPrefix construction. Signed-off-by: David Anderson --- netaddr.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/netaddr.go b/netaddr.go index f58a370..92be201 100644 --- a/netaddr.go +++ b/netaddr.go @@ -637,7 +637,7 @@ func (ip IP) Prefix(bits uint8) (IPPrefix, error) { } } ip.addr = ip.addr.and(mask6[effectiveBits]) - return IPPrefix{ip, bits}, nil + return IPPrefixFrom(ip, bits), nil } // Netmask applies a bit mask to IP, producing an IPPrefix. If IP is the @@ -1165,7 +1165,12 @@ type IPPrefix struct { // IPPrefixFrom returns an IPPrefix with IP ip and port port. // It does not allocate. -func IPPrefixFrom(ip IP, bits uint8) IPPrefix { return IPPrefix{ip: ip, bits: bits} } +func IPPrefixFrom(ip IP, bits uint8) IPPrefix { + return IPPrefix{ + ip: ip.WithZone(""), + bits: bits, + } +} // IP returns p's IP. func (p IPPrefix) IP() IP { return p.ip } From 0067cbf61f288c1fbd6a2f77b6e4e6e7e0fd5e5c Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 11:08:48 -0700 Subject: [PATCH 36/73] Fix IPPrefix docstring to match new opaque type. Signed-off-by: David Anderson --- netaddr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netaddr.go b/netaddr.go index 92be201..809bbe8 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1156,7 +1156,7 @@ func (p IPPort) TCPAddr() *net.TCPAddr { // IPPrefix is an IP address prefix (CIDR) representing an IP network. // -// The first Bits() of IP are specified. The remaining bits match any address. +// The first Bits() of IP() are specified. The remaining bits match any address. // The range of Bits() is [0,32] for IPv4 or [0,128] for IPv6. type IPPrefix struct { ip IP From 5c1c0f949c8f08c7794c6a014b7f86c5ee909180 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 11:40:57 -0700 Subject: [PATCH 37/73] Make IPPrefix uniformly strip/ignore IPv6 zones. Enabler for #167. Signed-off-by: David Anderson --- netaddr.go | 6 ++---- netaddr_test.go | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/netaddr.go b/netaddr.go index 809bbe8..2e8e7b4 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1239,10 +1239,7 @@ func ParseIPPrefix(s string) (IPPrefix, error) { if bits < 0 || bits > maxBits { return IPPrefix{}, fmt.Errorf("netaddr.ParseIPPrefix(%q): prefix length out of range", s) } - return IPPrefix{ - ip: ip, - bits: uint8(bits), - }, nil + return IPPrefixFrom(ip, uint8(bits)), nil } // MustParseIPPrefix calls ParseIPPrefix(s) and panics on error. @@ -1294,6 +1291,7 @@ func (p IPPrefix) IPNet() *net.IPNet { // An IPv4 address will not match an IPv6 prefix. // A v6-mapped IPv6 address will not match an IPv4 prefix. // A zero-value IP will not match any prefix. +// ip's zone, if any, is ignored. func (p IPPrefix) Contains(ip IP) bool { if !p.Valid() { return false diff --git a/netaddr_test.go b/netaddr_test.go index 866f9a0..b71361e 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -1134,7 +1134,6 @@ func TestIPPrefixMarshalUnmarshal(t *testing.T) { "0.0.0.0/0", "::/0", "::1/128", - "fe80::1cc0:3e8c:119f:c2e1%ens18/128", "::ffff:c000:1234/128", "2001:db8::/32", } @@ -1163,6 +1162,26 @@ func TestIPPrefixMarshalUnmarshal(t *testing.T) { } } +func TestIPPrefixMarshalUnmarshalZone(t *testing.T) { + orig := `"fe80::1cc0:3e8c:119f:c2e1%ens18/128"` + unzoned := `"fe80::1cc0:3e8c:119f:c2e1/128"` + + var p IPPrefix + if err := json.Unmarshal([]byte(orig), &p); err != nil { + t.Fatalf("failed to unmarshal: %v", err) + } + + pb, err := json.Marshal(p) + if err != nil { + t.Fatalf("failed to marshal: %v", err) + } + + back := string(pb) + if back != unzoned { + t.Errorf("Marshal = %q; want %q", back, unzoned) + } +} + func TestIPPrefixUnmarshalTextNonZero(t *testing.T) { ip := mustIPPrefix("fe80::/64") if err := ip.UnmarshalText([]byte("xxx")); err == nil { @@ -1266,6 +1285,7 @@ func TestIPPrefix(t *testing.T) { ip IP bits uint8 ipNet *net.IPNet + str string contains []IP notContains []IP }{ @@ -1348,14 +1368,15 @@ func TestIPPrefix(t *testing.T) { }, { prefix: "::%0/00/80", - ip: mustIP("::%0/00"), + ip: mustIP("::"), bits: 80, + str: "::/80", ipNet: &net.IPNet{ IP: net.ParseIP("::"), // net.IPNet drops zones Mask: net.CIDRMask(80, 128), }, - contains: mustIPs("::%0/00"), - notContains: mustIPs("ff::%0/00"), + contains: mustIPs("::%0/00", "::%1/23"), + notContains: mustIPs("ff::%0/00", "ff::%1/23"), }, } for _, test := range tests { @@ -1384,8 +1405,12 @@ func TestIPPrefix(t *testing.T) { t.Errorf("contains %s", ip) } } - if got := prefix.String(); got != test.prefix { - t.Errorf("prefix.String()=%q, want %q", got, test.prefix) + want := test.str + if want == "" { + want = test.prefix + } + if got := prefix.String(); got != want { + t.Errorf("prefix.String()=%q, want %q", got, want) } testAppendToMarshal(t, prefix) From fd0dfce7826222eddd2df6c7629c64fff1f59b73 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 12:05:00 -0700 Subject: [PATCH 38/73] Make IPRange uniformly strip and ignore v6 zones. Signed-off-by: David Anderson --- netaddr.go | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/netaddr.go b/netaddr.go index 2e8e7b4..874bfb1 100644 --- a/netaddr.go +++ b/netaddr.go @@ -541,6 +541,16 @@ func (ip IP) WithZone(zone string) IP { return ip } +// noZone unconditionally strips the zone from IP. +// It's similar to WithZone, but small enough to be inlinable. +func (ip IP) withoutZone() IP { + if !ip.Is6() { + return ip + } + ip.z = z6noz + return ip +} + // IsLinkLocalUnicast reports whether ip is a link-local unicast address. // If ip is the zero value, it will return false. func (ip IP) IsLinkLocalUnicast() bool { @@ -1167,7 +1177,7 @@ type IPPrefix struct { // It does not allocate. func IPPrefixFrom(ip IP, bits uint8) IPPrefix { return IPPrefix{ - ip: ip.WithZone(""), + ip: ip.withoutZone(), bits: bits, } } @@ -1269,7 +1279,7 @@ func (p IPPrefix) Range() IPRange { if p.IsZero() { return IPRange{} } - return IPRange{from: p.ip, to: p.lastIP()} + return IPRangeFrom(p.ip, p.lastIP()) } // IPNet returns the net.IPNet representation of an IPPrefix. @@ -1452,8 +1462,8 @@ func (p IPPrefix) lastIP() IP { // range. // // To be valid, the From() and To() values must be non-zero, have matching -// address families (IPv4 vs IPv6), be in the same IPv6 zone (if any), -// and From() must be less than or equal to To(). +// address families (IPv4 vs IPv6), and From() must be less than or equal to To(). +// IPv6 zones are stripped out and ignored. // An invalid range may be ignored. type IPRange struct { // from is the initial IP address in the range. @@ -1465,7 +1475,12 @@ type IPRange struct { // IPRangeFrom returns an IPRange from from to to. // It does not allocate. -func IPRangeFrom(from, to IP) IPRange { return IPRange{from: from, to: to} } +func IPRangeFrom(from, to IP) IPRange { + return IPRange{ + from: from.withoutZone(), + to: to.withoutZone(), + } +} // From returns the lower bound of r. func (r IPRange) From() IP { return r.from } @@ -1488,10 +1503,12 @@ func ParseIPRange(s string) (IPRange, error) { if err != nil { return r, fmt.Errorf("invalid From IP %q in range %q", from, s) } + r.from = r.from.withoutZone() r.to, err = ParseIP(to) if err != nil { return r, fmt.Errorf("invalid To IP %q in range %q", to, s) } + r.to = r.to.withoutZone() if !r.Valid() { return r, fmt.Errorf("range %v to %v not valid", r.from, r.to) } @@ -1513,9 +1530,9 @@ func (r IPRange) String() string { return "invalid IPRange" } -// Valid reports whether r.From() and r.To() are both non-zero and obey -// the documented requirements: address families match, same IPv6 -// zone, and From is less than or equal to To. +// Valid reports whether r.From() and r.To() are both non-zero and +// obey the documented requirements: address families match, and From +// is less than or equal to To. func (r IPRange) Valid() bool { return !r.from.IsZero() && r.from.z == r.to.z && @@ -1526,10 +1543,11 @@ func (r IPRange) Valid() bool { // // An invalid range always reports false. func (r IPRange) Contains(addr IP) bool { - return r.Valid() && r.contains(addr) + return r.Valid() && r.contains(addr.withoutZone()) } -// contains is like Contains, but without the validity check. For internal use. +// contains is like Contains, but without the validity check. +// addr must not have a zone. func (r IPRange) contains(addr IP) bool { return r.from.Compare(addr) <= 0 && r.to.Compare(addr) >= 0 } From f78dda9011a90325b19f15874876c9d43599baff Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 12:05:51 -0700 Subject: [PATCH 39/73] move IP.Next and IP.Prior to the part of the file that defines IP. Signed-off-by: David Anderson --- netaddr.go | 64 +++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/netaddr.go b/netaddr.go index 874bfb1..45e8c5a 100644 --- a/netaddr.go +++ b/netaddr.go @@ -705,6 +705,38 @@ func (ip IP) As4() [4]byte { panic("As4 called on IPv6 address") } +// Next returns the IP following ip. +// If there is none, it returns the IP zero value. +func (ip IP) Next() IP { + ip.addr = ip.addr.addOne() + if ip.Is4() { + if uint32(ip.addr.lo) == 0 { + // Overflowed. + return IP{} + } + } else { + if ip.addr.isZero() { + // Overflowed + return IP{} + } + } + return ip +} + +// Prior returns the IP before ip. +// If there is none, it returns the IP zero value. +func (ip IP) Prior() IP { + if ip.Is4() { + if uint32(ip.addr.lo) == 0 { + return IP{} + } + } else if ip.addr.isZero() { + return IP{} + } + ip.addr = ip.addr.subOne() + return ip +} + // String returns the string form of the IP address ip. // It returns one of 4 forms: // @@ -1730,35 +1762,3 @@ func appendRangePrefixes(dst []IPPrefix, makePrefix prefixMaker, a, b uint128) [ dst = appendRangePrefixes(dst, makePrefix, b.bitsClearedFrom(common+1), b) return dst } - -// Next returns the IP following ip. -// If there is none, it returns the IP zero value. -func (ip IP) Next() IP { - ip.addr = ip.addr.addOne() - if ip.Is4() { - if uint32(ip.addr.lo) == 0 { - // Overflowed. - return IP{} - } - } else { - if ip.addr.isZero() { - // Overflowed - return IP{} - } - } - return ip -} - -// Prior returns the IP before ip. -// If there is none, it returns the IP zero value. -func (ip IP) Prior() IP { - if ip.Is4() { - if uint32(ip.addr.lo) == 0 { - return IP{} - } - } else if ip.addr.isZero() { - return IP{} - } - ip.addr = ip.addr.subOne() - return ip -} From 630eb035592170d866f52b4897500c0bf21c52bc Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 12:59:39 -0700 Subject: [PATCH 40/73] Lock in a few more importantly inlinable functions. Signed-off-by: David Anderson --- inlining_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inlining_test.go b/inlining_test.go index e2a21e7..7b3cc0a 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -67,6 +67,7 @@ func TestInlining(t *testing.T) { "IP.v4", "IP.v6", "IP.v6u16", + "IP.withoutZone", "IPPort.IsZero", "IPPort.TCPAddr", "IPPort.UDPAddr", @@ -74,6 +75,7 @@ func TestInlining(t *testing.T) { "IPPortFrom", "IPPort.IP", "IPPort.Port", + "IPPort.Valid", "IPPort.WithIP", "IPPort.WithPort", "IPPrefix.IsSingleIP", From 630aabfed50d014c0fdf7e91043315c10152ce46 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 13:09:39 -0700 Subject: [PATCH 41/73] Make IPSetBuilder and IPSet uniformly strip/ignore zones. Fixes #128. Signed-off-by: David Anderson --- inlining_test.go | 2 -- ipset.go | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/inlining_test.go b/inlining_test.go index 7b3cc0a..e68ff0b 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -42,10 +42,8 @@ func TestInlining(t *testing.T) { return nil }) for _, want := range []string{ - "(*IPSetBuilder).Add", "(*IPSetBuilder).Clone", "(*IPSetBuilder).IPSet", - "(*IPSetBuilder).Remove", "(*IPSetBuilder).RemoveRange", "(*IPSet).Ranges", "(*uint128).halves", diff --git a/ipset.go b/ipset.go index 0c6be84..fa4a86e 100644 --- a/ipset.go +++ b/ipset.go @@ -158,7 +158,7 @@ func (s *IPSetBuilder) Clone() *IPSetBuilder { } // Add adds ip to s. -func (s *IPSetBuilder) Add(ip IP) { s.AddRange(IPRange{ip, ip}) } +func (s *IPSetBuilder) Add(ip IP) { s.AddRange(IPRangeFrom(ip, ip)) } // AddPrefix adds all IPs in p to s. func (s *IPSetBuilder) AddPrefix(p IPPrefix) { s.AddRange(p.Range()) } @@ -185,7 +185,7 @@ func (s *IPSetBuilder) AddSet(b *IPSet) { } // Remove removes ip from s. -func (s *IPSetBuilder) Remove(ip IP) { s.RemoveRange(IPRange{ip, ip}) } +func (s *IPSetBuilder) Remove(ip IP) { s.RemoveRange(IPRangeFrom(ip, ip)) } // RemovePrefix removes all IPs in p from s. func (s *IPSetBuilder) RemovePrefix(p IPPrefix) { s.RemoveRange(p.Range()) } @@ -300,7 +300,7 @@ func (s *IPSet) Contains(ip IP) bool { return false } i-- - return s.rr[i].contains(ip) + return s.rr[i].contains(ip.withoutZone()) } // ContainsRange reports whether all IPs in r are in s. From 17df09014a1d08dbf92b7bd7c4c7f97d53edbfd4 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 13:49:53 -0700 Subject: [PATCH 42/73] Make IPSetBuilder accumulate and return construction errors. Fixes #146. Signed-off-by: David Anderson --- example_test.go | 2 +- inlining_test.go | 2 -- ipset.go | 75 ++++++++++++++++++++++++++++++++++++++++++------ ipset_test.go | 38 ++++++++++++++---------- 4 files changed, 90 insertions(+), 27 deletions(-) diff --git a/example_test.go b/example_test.go index 68a2452..737fbde 100644 --- a/example_test.go +++ b/example_test.go @@ -172,7 +172,7 @@ func ExampleIPSet() { netaddr.MustParseIP("fed0::04ff"), )) - s := b.IPSet() + s, _ := b.IPSet() fmt.Println("Ranges:") for _, r := range s.Ranges() { diff --git a/inlining_test.go b/inlining_test.go index e68ff0b..4d5b63f 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -43,8 +43,6 @@ func TestInlining(t *testing.T) { }) for _, want := range []string{ "(*IPSetBuilder).Clone", - "(*IPSetBuilder).IPSet", - "(*IPSetBuilder).RemoveRange", "(*IPSet).Ranges", "(*uint128).halves", "IP.BitLen", diff --git a/ipset.go b/ipset.go index fa4a86e..f62e002 100644 --- a/ipset.go +++ b/ipset.go @@ -4,7 +4,12 @@ package netaddr -import "sort" +import ( + "errors" + "fmt" + "sort" + "strings" +) // IPSetBuilder builds an immutable IPSet. // @@ -20,6 +25,9 @@ type IPSetBuilder struct { // out are the ranges to be removed from 'in'. out []IPRange + + // errs are errors accumulated during construction. + errs multiErr } // normalize normalizes s: s.in becomes the minimal sorted list of @@ -158,15 +166,27 @@ func (s *IPSetBuilder) Clone() *IPSetBuilder { } // Add adds ip to s. -func (s *IPSetBuilder) Add(ip IP) { s.AddRange(IPRangeFrom(ip, ip)) } +func (s *IPSetBuilder) Add(ip IP) { + if ip.IsZero() { + return + } + s.AddRange(IPRangeFrom(ip, ip)) +} // AddPrefix adds all IPs in p to s. -func (s *IPSetBuilder) AddPrefix(p IPPrefix) { s.AddRange(p.Range()) } +func (s *IPSetBuilder) AddPrefix(p IPPrefix) { + if r := p.Range(); r.Valid() { + s.AddRange(r) + } else { + s.errs = append(s.errs, fmt.Errorf("AddPrefix of invalid prefix %q", p)) + } +} // AddRange adds r to s. // If r is not Valid, AddRange does nothing. func (s *IPSetBuilder) AddRange(r IPRange) { if !r.Valid() { + s.errs = append(s.errs, fmt.Errorf("AddRange of invalid range %q", r)) return } // If there are any removals (s.out), then we need to compact the set @@ -179,26 +199,46 @@ func (s *IPSetBuilder) AddRange(r IPRange) { // AddSet adds all IPs in b to s. func (s *IPSetBuilder) AddSet(b *IPSet) { + if b == nil { + return + } for _, r := range b.rr { s.AddRange(r) } } // Remove removes ip from s. -func (s *IPSetBuilder) Remove(ip IP) { s.RemoveRange(IPRangeFrom(ip, ip)) } +func (s *IPSetBuilder) Remove(ip IP) { + if ip.IsZero() { + s.errs = append(s.errs, errors.New("ignored Remove of zero IP")) + } else { + s.RemoveRange(IPRangeFrom(ip, ip)) + } +} // RemovePrefix removes all IPs in p from s. -func (s *IPSetBuilder) RemovePrefix(p IPPrefix) { s.RemoveRange(p.Range()) } +func (s *IPSetBuilder) RemovePrefix(p IPPrefix) { + if r := p.Range(); r.Valid() { + s.RemoveRange(r) + } else { + s.errs = append(s.errs, fmt.Errorf("RemovePrefix of invalid prefix %q", p)) + } +} // RemoveRange removes all IPs in r from s. func (s *IPSetBuilder) RemoveRange(r IPRange) { if r.Valid() { s.out = append(s.out, r) + } else { + s.errs = append(s.errs, fmt.Errorf("RemoveRange of invalid range %q", r)) } } // RemoveSet removes all IPs in o from s. func (s *IPSetBuilder) RemoveSet(b *IPSet) { + if b == nil { + return + } for _, r := range b.rr { s.RemoveRange(r) } @@ -237,12 +277,18 @@ func discardf(format string, args ...interface{}) {} var debugf = discardf // IPSet returns an immutable IPSet representing the current state of -// s. The builder remains usable after calling IPSet. -func (s *IPSetBuilder) IPSet() *IPSet { +// s, along with any errors accumulated during the construction of s. +// The builder remains usable after calling IPSet. +func (s *IPSetBuilder) IPSet() (*IPSet, error) { s.normalize() - return &IPSet{ + ret := &IPSet{ rr: append([]IPRange{}, s.in...), } + if len(s.errs) == 0 { + return ret, nil + } else { + return ret, s.errs + } } // IPSet represents a set of IP addresses. @@ -378,5 +424,16 @@ func (s *IPSet) RemoveFreePrefix(bitLen uint8) (p IPPrefix, newSet *IPSet, ok bo var b IPSetBuilder b.AddSet(s) b.RemovePrefix(prefix) - return prefix, b.IPSet(), true + newSet, _ = b.IPSet() + return prefix, newSet, true +} + +type multiErr []error + +func (e multiErr) Error() string { + var ret []string + for _, err := range e { + ret = append(ret, err.Error()) + } + return strings.Join(ret, ", ") } diff --git a/ipset_test.go b/ipset_test.go index b94a6dd..30c8ab2 100644 --- a/ipset_test.go +++ b/ipset_test.go @@ -13,6 +13,14 @@ import ( "testing" ) +func buildIPSet(b *IPSetBuilder) *IPSet { + ret, err := b.IPSet() + if err != nil { + panic(err) + } + return ret +} + func TestIPSet(t *testing.T) { tests := []struct { name string @@ -272,7 +280,7 @@ func TestIPSet(t *testing.T) { t.AddRange(IPRange{mustIP("2.2.2.2"), mustIP("3.3.3.3")}) s.AddRange(IPRange{mustIP("1.1.1.1"), mustIP("4.4.4.4")}) - s.Intersect(t.IPSet()) + s.Intersect(buildIPSet(&t)) }, wantRanges: []IPRange{ {mustIP("2.2.2.2"), mustIP("3.3.3.3")}, @@ -285,7 +293,7 @@ func TestIPSet(t *testing.T) { t.AddRange(IPRange{mustIP("1.1.1.1"), mustIP("2.2.2.2")}) s.AddRange(IPRange{mustIP("3.3.3.3"), mustIP("4.4.4.4")}) - s.Intersect(t.IPSet()) + s.Intersect(buildIPSet(&t)) }, wantRanges: []IPRange{}, }, @@ -296,7 +304,7 @@ func TestIPSet(t *testing.T) { t.AddRange(IPRange{mustIP("1.1.1.1"), mustIP("3.3.3.3")}) s.AddRange(IPRange{mustIP("2.2.2.2"), mustIP("4.4.4.4")}) - s.Intersect(t.IPSet()) + s.Intersect(buildIPSet(&t)) }, wantRanges: []IPRange{ {mustIP("2.2.2.2"), mustIP("3.3.3.3")}, @@ -309,7 +317,7 @@ func TestIPSet(t *testing.T) { defer func() { debugf = discardf }() var build IPSetBuilder tt.f(&build) - s := build.IPSet() + s := buildIPSet(&build) got := s.Ranges() t.Run("ranges", func(t *testing.T) { for _, v := range got { @@ -397,7 +405,7 @@ func TestIPSetRemoveFreePrefix(t *testing.T) { debugf = t.Logf var build IPSetBuilder tt.f(&build) - s := build.IPSet() + s := buildIPSet(&build) gotPrefix, gotSet, ok := s.RemoveFreePrefix(tt.b) if ok != tt.wantOK { t.Errorf("extractPrefix() ok = %t, wantOK %t", ok, tt.wantOK) @@ -407,7 +415,7 @@ func TestIPSetRemoveFreePrefix(t *testing.T) { t.Errorf("extractPrefix() = %v, want %v", gotPrefix, tt.wantPrefix) } if !reflect.DeepEqual(gotSet.Prefixes(), tt.wantPrefixes) { - t.Errorf("extractPrefix() = %v, want %v", build.IPSet().Prefixes(), tt.wantPrefixes) + t.Errorf("extractPrefix() = %v, want %v", gotSet.Prefixes(), tt.wantPrefixes) } }) } @@ -429,7 +437,7 @@ func mustIPSet(ranges ...string) *IPSet { panic(fmt.Sprintf("unknown command %q", r[0])) } } - return ret.IPSet() + return buildIPSet(&ret) } func TestIPSetOverlaps(t *testing.T) { @@ -496,7 +504,7 @@ func TestIPSetContains(t *testing.T) { build.AddPrefix(mustIPPrefix("10.0.0.0/8")) build.AddPrefix(mustIPPrefix("1.2.3.4/32")) build.AddPrefix(mustIPPrefix("fc00::/7")) - s := build.IPSet() + s := buildIPSet(&build) tests := []struct { ip string @@ -588,7 +596,7 @@ func newRandomIPSet() (steps []string, s *IPSet, wantContains [256]bool) { } } } - s = b.IPSet() + s = buildIPSet(b) return } @@ -630,7 +638,7 @@ func TestIPSetRanges(t *testing.T) { if !from.IsZero() { flush() } - got := build.IPSet().Ranges() + got := buildIPSet(&build).Ranges() if !reflect.DeepEqual(got, ranges) { t.Errorf("for %016b: got %v; want %v", pat, got, ranges) } @@ -674,7 +682,7 @@ func TestIPSetRangesStress(t *testing.T) { } build.RemoveRange(r) } - ranges := build.IPSet().Ranges() + ranges := buildIPSet(&build).Ranges() // Make sure no ranges are adjacent or overlapping for i, r := range ranges { @@ -693,7 +701,7 @@ func TestIPSetRangesStress(t *testing.T) { for _, r := range ranges { build2.AddRange(r) } - s2 := build2.IPSet() + s2 := buildIPSet(&build2) for i, want := range want { if got := s2.Contains(IPv4(0, 0, uint8(i>>8), uint8(i))); got != want { t.Fatal("failed") @@ -708,7 +716,7 @@ func TestIPSetEqual(t *testing.T) { assertEqual := func(want bool) { t.Helper() - if got := a.IPSet().Equal(b.IPSet()); got != want { + if got := buildIPSet(a).Equal(buildIPSet(b)); got != want { t.Errorf("%v.Equal(%v) = %v want %v", a, b, got, want) } } @@ -720,9 +728,9 @@ func TestIPSetEqual(t *testing.T) { b.Add(MustParseIP("1.1.1.2")) assertEqual(true) - a.RemoveSet(a.IPSet()) + a.RemoveSet(buildIPSet(a)) assertEqual(false) - b.RemoveSet(b.IPSet()) + b.RemoveSet(buildIPSet(b)) assertEqual(true) a.Add(MustParseIP("1.1.1.0")) From 50f8686885e3248d54a8b8aa24cb10793b1a09ec Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 31 May 2021 15:11:29 -0700 Subject: [PATCH 43/73] Update corpus submodule with new inputs. Signed-off-by: David Anderson --- corpus | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corpus b/corpus index f845a17..00da777 160000 --- a/corpus +++ b/corpus @@ -1 +1 @@ -Subproject commit f845a17e9dc8358a65fbdbba2d944758bfbd40eb +Subproject commit 00da7779387ff2ce0e18188f44dbe2d9ffde8efb From bf05d8b52ddacde0309015f0320eb3f8b3ce8079 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 2 Jun 2021 12:39:52 -0700 Subject: [PATCH 44/73] make Contains methods return false when called with an IP method with a zone IPPrefix, IPRange, and IPSet strips zones. As a consequence, they cannot contain any IP with an IPv6 zone. Signed-off-by: Josh Bleecher Snyder --- inlining_test.go | 1 + ipset.go | 7 ++++++- ipset_test.go | 3 +++ netaddr.go | 15 ++++++++++++--- netaddr_test.go | 8 ++++++-- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/inlining_test.go b/inlining_test.go index 4d5b63f..07ef7f5 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -46,6 +46,7 @@ func TestInlining(t *testing.T) { "(*IPSet).Ranges", "(*uint128).halves", "IP.BitLen", + "IP.hasZone", "IP.IPAddr", "IP.Is4", "IP.Is4in6", diff --git a/ipset.go b/ipset.go index f62e002..81f6bbe 100644 --- a/ipset.go +++ b/ipset.go @@ -336,7 +336,12 @@ func (s *IPSet) Equal(o *IPSet) bool { } // Contains reports whether ip is in s. +// If ip has an IPv6 zone, Contains returns false, +// because IPSets do not track zones. func (s *IPSet) Contains(ip IP) bool { + if ip.hasZone() { + return false + } // TODO: data structure permitting more efficient lookups: // https://github.com/inetaf/netaddr/issues/139 i := sort.Search(len(s.rr), func(i int) bool { @@ -346,7 +351,7 @@ func (s *IPSet) Contains(ip IP) bool { return false } i-- - return s.rr[i].contains(ip.withoutZone()) + return s.rr[i].contains(ip) } // ContainsRange reports whether all IPs in r are in s. diff --git a/ipset_test.go b/ipset_test.go index 30c8ab2..c5d4f72 100644 --- a/ipset_test.go +++ b/ipset_test.go @@ -528,6 +528,9 @@ func TestIPSetContains(t *testing.T) { {"fc00::1", true}, {"fd00::1", true}, {"ff00::1", false}, + + {"fd00::%a", false}, + {"fd00::1%a", false}, } for _, tt := range tests { got := s.Contains(mustIP(tt.ip)) diff --git a/netaddr.go b/netaddr.go index 45e8c5a..391960d 100644 --- a/netaddr.go +++ b/netaddr.go @@ -551,6 +551,11 @@ func (ip IP) withoutZone() IP { return ip } +// hasZone reports whether IP has an IPv6 zone. +func (ip IP) hasZone() bool { + return ip.z != z0 && ip.z != z4 && ip.z != z6noz +} + // IsLinkLocalUnicast reports whether ip is a link-local unicast address. // If ip is the zero value, it will return false. func (ip IP) IsLinkLocalUnicast() bool { @@ -1333,9 +1338,10 @@ func (p IPPrefix) IPNet() *net.IPNet { // An IPv4 address will not match an IPv6 prefix. // A v6-mapped IPv6 address will not match an IPv4 prefix. // A zero-value IP will not match any prefix. -// ip's zone, if any, is ignored. +// If ip has an IPv6 zone, Contains returns false, +// because IPPrefixes strip zones. func (p IPPrefix) Contains(ip IP) bool { - if !p.Valid() { + if !p.Valid() || ip.hasZone() { return false } if f1, f2 := p.ip.BitLen(), ip.BitLen(); f1 == 0 || f2 == 0 || f1 != f2 { @@ -1574,8 +1580,11 @@ func (r IPRange) Valid() bool { // Contains reports whether the range r includes addr. // // An invalid range always reports false. +// +// If ip has an IPv6 zone, Contains returns false, +// because IPPrefixes strip zones. func (r IPRange) Contains(addr IP) bool { - return r.Valid() && r.contains(addr.withoutZone()) + return r.Valid() && !addr.hasZone() && r.contains(addr) } // contains is like Contains, but without the validity check. diff --git a/netaddr_test.go b/netaddr_test.go index b71361e..33263fa 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -1375,8 +1375,8 @@ func TestIPPrefix(t *testing.T) { IP: net.ParseIP("::"), // net.IPNet drops zones Mask: net.CIDRMask(80, 128), }, - contains: mustIPs("::%0/00", "::%1/23"), - notContains: mustIPs("ff::%0/00", "ff::%1/23"), + contains: mustIPs("::"), + notContains: mustIPs("ff::%0/00", "ff::%1/23", "::%0/00", "::%1/23"), }, } for _, test := range tests { @@ -2353,6 +2353,7 @@ func TestIPRangeContains(t *testing.T) { []rtest{ {mustIP("::0"), false}, {mustIP("::1"), true}, + {mustIP("::1%z"), false}, {mustIP("::ffff"), true}, {mustIP("1::"), false}, {mustIP("0.0.0.1"), false}, @@ -2606,6 +2607,9 @@ func TestIPPrefixContains(t *testing.T) { {mustIPPrefix("::1/127"), mustIP("::2"), false}, {mustIPPrefix("::1/128"), mustIP("::1"), true}, {mustIPPrefix("::1/127"), mustIP("::2"), false}, + // zones support + {mustIPPrefix("::1%a/128"), mustIP("::1"), true}, // prefix zones are stripped... + {mustIPPrefix("::1%a/128"), mustIP("::1%a"), false}, // but ip zones are not // invalid IP {mustIPPrefix("::1/0"), IP{}, false}, {mustIPPrefix("1.2.3.4/0"), IP{}, false}, From 4392f0bb2d877792bf8ffaabd554f8a7f4536cb9 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 2 Jun 2021 12:05:44 -0700 Subject: [PATCH 45/73] improve IPSetBuilder.IPSet documentation Explain in more detail about the slightly unusual API. Signed-off-by: Josh Bleecher Snyder --- ipset.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ipset.go b/ipset.go index 81f6bbe..03ae6b9 100644 --- a/ipset.go +++ b/ipset.go @@ -276,8 +276,16 @@ func discardf(format string, args ...interface{}) {} // debugf is reassigned by tests. var debugf = discardf -// IPSet returns an immutable IPSet representing the current state of -// s, along with any errors accumulated during the construction of s. +// IPSet returns an immutable IPSet representing the current state of s. +// +// Most IPSetBuilder methods do not return errors. +// Rather, the builder ignores any invalid inputs (such as an invalid IPPrefix), +// and accumulates a list of any such errors that it encountered. +// +// IPSet also reports any such accumulated errors. +// Even if the returned error is non-nil, the returned IPSet is usable +// and contains all modifications made with valid inputs. +// // The builder remains usable after calling IPSet. func (s *IPSetBuilder) IPSet() (*IPSet, error) { s.normalize() From bedca89b3741475282c3fddef70652796d91c443 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 2 Jun 2021 12:07:29 -0700 Subject: [PATCH 46/73] make a call to IPSetBuilder.IPSet clear the builder's errors That way people don't have to track whether they've already handled errors when continuing to use an IPSetBuilder. Signed-off-by: Josh Bleecher Snyder --- ipset.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ipset.go b/ipset.go index 03ae6b9..94847d6 100644 --- a/ipset.go +++ b/ipset.go @@ -287,6 +287,7 @@ var debugf = discardf // and contains all modifications made with valid inputs. // // The builder remains usable after calling IPSet. +// Calling IPSet clears any accumulated errors. func (s *IPSetBuilder) IPSet() (*IPSet, error) { s.normalize() ret := &IPSet{ @@ -295,7 +296,9 @@ func (s *IPSetBuilder) IPSet() (*IPSet, error) { if len(s.errs) == 0 { return ret, nil } else { - return ret, s.errs + errs := s.errs + s.errs = nil + return ret, errs } } From 23690f5b6f447ada3123266ab4182eb2a98df914 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 2 Jun 2021 12:09:41 -0700 Subject: [PATCH 47/73] note unusual API in IPSetBuilder type documentation Signed-off-by: Josh Bleecher Snyder --- ipset.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipset.go b/ipset.go index 94847d6..f6e258c 100644 --- a/ipset.go +++ b/ipset.go @@ -19,6 +19,9 @@ import ( // Removals only affect the current membership of the set, so in // general Adds should be called first. Input ranges may overlap in // any way. +// +// Most IPSetBuilder methods do not return errors. +// Instead, errors are accumulated and reported by IPSetBuilder.IPSet. type IPSetBuilder struct { // in are the ranges in the set. in []IPRange From 1a7bd7ddb8b299941478674f7e427d7c547051af Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Sat, 3 Jul 2021 10:20:58 -0400 Subject: [PATCH 48/73] netaddr: add IP.IsGlobalUnicast (#192) Signed-off-by: Matt Layher --- example_test.go | 40 +++++++++++++++++++++++++++ netaddr.go | 27 +++++++++++++++++++ netaddr_test.go | 72 ++++++++++++++++++++++++++++++------------------- 3 files changed, 111 insertions(+), 28 deletions(-) diff --git a/example_test.go b/example_test.go index 737fbde..12ed175 100644 --- a/example_test.go +++ b/example_test.go @@ -117,6 +117,46 @@ func ExampleIP_IsZero() { // (::).IsZero() -> false } +func ExampleIP_IsGlobalUnicast() { + var ( + zeroIP netaddr.IP + + ipv4AllZeroes = netaddr.MustParseIP("0.0.0.0") + ipv4 = netaddr.MustParseIP("192.0.2.3") + + ipv6AllZeroes = netaddr.MustParseIP("::") + ipv6LinkLocal = netaddr.MustParseIP("fe80::1") + ipv6 = netaddr.MustParseIP("2001:db8::68") + ipv6Unassigned = netaddr.MustParseIP("4000::1") + ip4in6 = netaddr.MustParseIP("::ffff:192.0.2.3") + ) + + fmt.Printf("IP{}.IsGlobalUnicast() -> %v\n", zeroIP.IsGlobalUnicast()) + + ips := []netaddr.IP{ + ipv4AllZeroes, + ipv4, + ipv6AllZeroes, + ipv6LinkLocal, + ipv6, + ipv6Unassigned, + ip4in6, + } + + for _, ip := range ips { + fmt.Printf("(%v).IsGlobalUnicast() -> %v\n", ip, ip.IsGlobalUnicast()) + } + // Output: + // IP{}.IsGlobalUnicast() -> false + // (0.0.0.0).IsGlobalUnicast() -> false + // (192.0.2.3).IsGlobalUnicast() -> true + // (::).IsGlobalUnicast() -> false + // (fe80::1).IsGlobalUnicast() -> false + // (2001:db8::68).IsGlobalUnicast() -> true + // (4000::1).IsGlobalUnicast() -> true + // (::ffff:c000:203).IsGlobalUnicast() -> true +} + func ExampleIP_String() { ipv4 := netaddr.MustParseIP("192.0.2.3") ipv6 := netaddr.MustParseIP("2001:db8::68") diff --git a/netaddr.go b/netaddr.go index 391960d..f8d9e47 100644 --- a/netaddr.go +++ b/netaddr.go @@ -632,6 +632,33 @@ func (ip IP) IsLinkLocalMulticast() bool { return false // zero value } +// IsGlobalUnicast reports whether ip is a global unicast address. +// +// It returns true for IPv6 addresses which fall outside of the current +// IANA-allocated 2000::/3 global unicast space, with the exception of the +// link-local address space. It also returns true even if ip is in the IPv4 +// private address space or IPv6 unique local address space. If ip is the zero +// value, it will return false. +// +// For reference, see RFC 1122, RFC 4291, and RFC 4632. +func (ip IP) IsGlobalUnicast() bool { + if ip.z == z0 { + // Invalid or zero-value. + return false + } + + // Match the stdlib's IsGlobalUnicast logic. Notably private IPv4 addresses + // and ULA IPv6 addresses are still considered "global unicast". + if ip.Is4() && (ip == IPv4(0, 0, 0, 0) || ip == IPv4(255, 255, 255, 255)) { + return false + } + + return ip != IPv6Unspecified() && + !ip.IsLoopback() && + !ip.IsMulticast() && + !ip.IsLinkLocalUnicast() +} + // Prefix applies a CIDR mask of leading bits to IP, producing an IPPrefix // of the specified length. If IP is the zero value, a zero-value IPPrefix and // a nil error are returned. If bits is larger than 32 for an IPv4 address or diff --git a/netaddr_test.go b/netaddr_test.go index 33263fa..ea6f5f9 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -553,9 +553,10 @@ func TestIPProperties(t *testing.T) { var ( nilIP IP - unicast4 = mustIP("192.0.2.1") - unicast6 = mustIP("2001:db8::1") - unicastZone6 = mustIP("2001:db8::1%eth0") + unicast4 = mustIP("192.0.2.1") + unicast6 = mustIP("2001:db8::1") + unicastZone6 = mustIP("2001:db8::1%eth0") + unicast6Unassigned = mustIP("4000::1") // not in 2000::/3. multicast4 = mustIP("224.0.0.1") multicast6 = mustIP("ff02::1") @@ -576,45 +577,54 @@ func TestIPProperties(t *testing.T) { tests := []struct { name string ip IP - multicast bool + globalUnicast bool interfaceLocalMulticast bool linkLocalMulticast bool linkLocalUnicast bool loopback bool + multicast bool }{ { name: "nil", ip: nilIP, }, { - name: "unicast v4Addr", - ip: unicast4, + name: "unicast v4Addr", + ip: unicast4, + globalUnicast: true, + }, + { + name: "unicast v6Addr", + ip: unicast6, + globalUnicast: true, }, { - name: "unicast v6Addr", - ip: unicast6, + name: "unicast v6AddrZone", + ip: unicastZone6, + globalUnicast: true, }, { - name: "unicast v6AddrZone", - ip: unicastZone6, + name: "unicast v6Addr unassigned", + ip: unicast6Unassigned, + globalUnicast: true, }, { name: "multicast v4Addr", ip: multicast4, - multicast: true, linkLocalMulticast: true, + multicast: true, }, { name: "multicast v6Addr", ip: multicast6, - multicast: true, linkLocalMulticast: true, + multicast: true, }, { name: "multicast v6AddrZone", ip: multicastZone6, - multicast: true, linkLocalMulticast: true, + multicast: true, }, { name: "link-local unicast v4Addr", @@ -649,22 +659,27 @@ func TestIPProperties(t *testing.T) { { name: "interface-local multicast v6Addr", ip: ilm6, - multicast: true, interfaceLocalMulticast: true, + multicast: true, }, { name: "interface-local multicast v6AddrZone", ip: ilmZone6, - multicast: true, interfaceLocalMulticast: true, + multicast: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - multicast := tt.ip.IsMulticast() - if multicast != tt.multicast { - t.Errorf("IsMulticast(%v) = %v; want %v", tt.ip, multicast, tt.multicast) + gu := tt.ip.IsGlobalUnicast() + if gu != tt.globalUnicast { + t.Errorf("IsGlobalUnicast(%v) = %v; want %v", tt.ip, gu, tt.globalUnicast) + } + + ilm := tt.ip.IsInterfaceLocalMulticast() + if ilm != tt.interfaceLocalMulticast { + t.Errorf("IsInterfaceLocalMulticast(%v) = %v; want %v", tt.ip, ilm, tt.interfaceLocalMulticast) } llu := tt.ip.IsLinkLocalUnicast() @@ -672,19 +687,19 @@ func TestIPProperties(t *testing.T) { t.Errorf("IsLinkLocalUnicast(%v) = %v; want %v", tt.ip, llu, tt.linkLocalUnicast) } + llm := tt.ip.IsLinkLocalMulticast() + if llm != tt.linkLocalMulticast { + t.Errorf("IsLinkLocalMulticast(%v) = %v; want %v", tt.ip, llm, tt.linkLocalMulticast) + } + lo := tt.ip.IsLoopback() if lo != tt.loopback { t.Errorf("IsLoopback(%v) = %v; want %v", tt.ip, lo, tt.loopback) } - ilm := tt.ip.IsInterfaceLocalMulticast() - if ilm != tt.interfaceLocalMulticast { - t.Errorf("IsInterfaceLocalMulticast(%v) = %v; want %v", tt.ip, ilm, tt.interfaceLocalMulticast) - } - - llm := tt.ip.IsLinkLocalMulticast() - if llm != tt.linkLocalMulticast { - t.Errorf("IsLinkLocalMulticast(%v) = %v; want %v", tt.ip, llm, tt.linkLocalMulticast) + multicast := tt.ip.IsMulticast() + if multicast != tt.multicast { + t.Errorf("IsMulticast(%v) = %v; want %v", tt.ip, multicast, tt.multicast) } }) } @@ -2755,11 +2770,12 @@ func TestNoAllocs(t *testing.T) { test("IP.Is4in6", func() { sinkBool = MustParseIP("fe80::1").Is4in6() }) test("IP.Unmap", func() { sinkIP = MustParseIP("ffff::2.3.4.5").Unmap() }) test("IP.WithZone", func() { sinkIP = MustParseIP("fe80::1").WithZone("") }) + test("IP.IsGlobalUnicast", func() { sinkBool = MustParseIP("2001:db8::1").IsGlobalUnicast() }) + test("IP.IsInterfaceLocalMulticast", func() { sinkBool = MustParseIP("fe80::1").IsInterfaceLocalMulticast() }) + test("IP.IsLinkLocalMulticast", func() { sinkBool = MustParseIP("fe80::1").IsLinkLocalMulticast() }) test("IP.IsLinkLocalUnicast", func() { sinkBool = MustParseIP("fe80::1").IsLinkLocalUnicast() }) test("IP.IsLoopback", func() { sinkBool = MustParseIP("fe80::1").IsLoopback() }) test("IP.IsMulticast", func() { sinkBool = MustParseIP("fe80::1").IsMulticast() }) - test("IP.IsInterfaceLocalMulticast", func() { sinkBool = MustParseIP("fe80::1").IsInterfaceLocalMulticast() }) - test("IP.IsLinkLocalMulticast", func() { sinkBool = MustParseIP("fe80::1").IsLinkLocalMulticast() }) test("IP.Prefix/4", func() { sinkIPPrefix = panicPfx(MustParseIP("1.2.3.4").Prefix(20)) }) test("IP.Prefix/6", func() { sinkIPPrefix = panicPfx(MustParseIP("fe80::1").Prefix(64)) }) test("IP.As16", func() { sinkIP16 = MustParseIP("1.2.3.4").As16() }) From 9c3b66f0f46b5f1fd6b7082ea0a05103370bd2f7 Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Sun, 4 Jul 2021 07:58:29 -0400 Subject: [PATCH 49/73] netaddr: add IP.IsUnspecified (#196) Signed-off-by: Matt Layher --- example_test.go | 14 ++++++++++++++ netaddr.go | 12 +++++++++++- netaddr_test.go | 20 ++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/example_test.go b/example_test.go index 12ed175..bfe3845 100644 --- a/example_test.go +++ b/example_test.go @@ -157,6 +157,20 @@ func ExampleIP_IsGlobalUnicast() { // (::ffff:c000:203).IsGlobalUnicast() -> true } +func ExampleIP_IsUnspecified() { + var zeroIP netaddr.IP + ipv4AllZeroes := netaddr.MustParseIP("0.0.0.0") + ipv6AllZeroes := netaddr.MustParseIP("::") + + fmt.Printf("IP{}.IsUnspecified() -> %v\n", zeroIP.IsUnspecified()) + fmt.Printf("(%v).IsUnspecified() -> %v\n", ipv4AllZeroes, ipv4AllZeroes.IsUnspecified()) + fmt.Printf("(%v).IsUnspecified() -> %v\n", ipv6AllZeroes, ipv6AllZeroes.IsUnspecified()) + // Output: + // IP{}.IsUnspecified() -> false + // (0.0.0.0).IsUnspecified() -> true + // (::).IsUnspecified() -> true +} + func ExampleIP_String() { ipv4 := netaddr.MustParseIP("192.0.2.3") ipv6 := netaddr.MustParseIP("2001:db8::68") diff --git a/netaddr.go b/netaddr.go index f8d9e47..162a060 100644 --- a/netaddr.go +++ b/netaddr.go @@ -402,7 +402,8 @@ func (ip IP) v6u16(i uint8) uint16 { // IsZero reports whether ip is the zero value of the IP type. // The zero value is not a valid IP address of any type. // -// Note that "0.0.0.0" and "::" are not the zero value. +// Note that "0.0.0.0" and "::" are not the zero value. Use IsUnspecified to +// check for these values instead. func (ip IP) IsZero() bool { // Faster than comparing ip == IP{}, but effectively equivalent, // as there's no way to make an IP with a nil z from this package. @@ -659,6 +660,15 @@ func (ip IP) IsGlobalUnicast() bool { !ip.IsLinkLocalUnicast() } +// IsUnspecified reports whether ip is an unspecified address, either the IPv4 +// address "0.0.0.0" or the IPv6 address "::". +// +// Note that the IP zero value is not an unspecified address. Use IsZero to +// check for the zero value instead. +func (ip IP) IsUnspecified() bool { + return ip == IPv4(0, 0, 0, 0) || ip == IPv6Unspecified() +} + // Prefix applies a CIDR mask of leading bits to IP, producing an IPPrefix // of the specified length. If IP is the zero value, a zero-value IPPrefix and // a nil error are returned. If bits is larger than 32 for an IPv4 address or diff --git a/netaddr_test.go b/netaddr_test.go index ea6f5f9..2ac2dec 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -572,6 +572,9 @@ func TestIPProperties(t *testing.T) { ilm6 = mustIP("ff01::1") ilmZone6 = mustIP("ff01::1%eth0") + + unspecified4 = IPv4(0, 0, 0, 0) + unspecified6 = IPv6Unspecified() ) tests := []struct { @@ -583,6 +586,7 @@ func TestIPProperties(t *testing.T) { linkLocalUnicast bool loopback bool multicast bool + unspecified bool }{ { name: "nil", @@ -668,6 +672,16 @@ func TestIPProperties(t *testing.T) { interfaceLocalMulticast: true, multicast: true, }, + { + name: "unspecified v4Addr", + ip: unspecified4, + unspecified: true, + }, + { + name: "unspecified v6Addr", + ip: unspecified6, + unspecified: true, + }, } for _, tt := range tests { @@ -701,6 +715,11 @@ func TestIPProperties(t *testing.T) { if multicast != tt.multicast { t.Errorf("IsMulticast(%v) = %v; want %v", tt.ip, multicast, tt.multicast) } + + unspecified := tt.ip.IsUnspecified() + if unspecified != tt.unspecified { + t.Errorf("IsUnspecified(%v) = %v; want %v", tt.ip, unspecified, tt.unspecified) + } }) } } @@ -2776,6 +2795,7 @@ func TestNoAllocs(t *testing.T) { test("IP.IsLinkLocalUnicast", func() { sinkBool = MustParseIP("fe80::1").IsLinkLocalUnicast() }) test("IP.IsLoopback", func() { sinkBool = MustParseIP("fe80::1").IsLoopback() }) test("IP.IsMulticast", func() { sinkBool = MustParseIP("fe80::1").IsMulticast() }) + test("IP.IsUnspecified", func() { sinkBool = IPv6Unspecified().IsUnspecified() }) test("IP.Prefix/4", func() { sinkIPPrefix = panicPfx(MustParseIP("1.2.3.4").Prefix(20)) }) test("IP.Prefix/6", func() { sinkIPPrefix = panicPfx(MustParseIP("fe80::1").Prefix(64)) }) test("IP.As16", func() { sinkIP16 = MustParseIP("1.2.3.4").As16() }) From d1acf45c35140d20b88b874a1c022d0859a2045e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 3 Jul 2021 20:42:40 -0700 Subject: [PATCH 50/73] Add IsValid method to IP, fix Valid method naming on the other types. This adds IsValid to IP, and corrects the spelling of IPPrefix.Valid, IPPort.Valid, and IPRange.Valid to be IsValid, keeping deprecated Valid wrappers for now while code is fixed. Fixes #194 Signed-off-by: Brad Fitzpatrick --- netaddr.go | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/netaddr.go b/netaddr.go index 162a060..29718e7 100644 --- a/netaddr.go +++ b/netaddr.go @@ -410,6 +410,12 @@ func (ip IP) IsZero() bool { return ip.z == z0 } +// IsValid whether the IP is an initialized value and not the IP +// type's zero value. +// +// Note that both "0.0.0.0" and "::" are valid, non-zero values. +func (ip IP) IsValid() bool { return ip.z != z0 } + // BitLen returns the number of bits in the IP address: // 32 for IPv4 or 128 for IPv6. // For the zero value (see IP.IsZero), it returns 0. @@ -1115,9 +1121,15 @@ func MustParseIPPort(s string) IPPort { // IsZero reports whether p is its zero value. func (p IPPort) IsZero() bool { return p == IPPort{} } -// Valid reports whether p.IP() is non-zero. +// IsValid reports whether p.IP() is valid. // All ports are valid, including zero. -func (p IPPort) Valid() bool { return !p.ip.IsZero() } +func (p IPPort) IsValid() bool { return p.ip.IsValid() } + +// Valid reports whether p.IP() is valid. +// All ports are valid, including zero. +// +// Deprecated: use the correctly named and identical IsValid method instead. +func (p IPPort) Valid() bool { return p.IsValid() } func (p IPPort) String() string { switch p.ip.z { @@ -1262,9 +1274,15 @@ func (p IPPrefix) IP() IP { return p.ip } // Bits returns p's prefix length. func (p IPPrefix) Bits() uint8 { return p.bits } +// IsValid reports whether whether p.Bits() has a valid range for p.IP(). +// If p.IP() is zero, Valid returns false. +func (p IPPrefix) IsValid() bool { return !p.ip.IsZero() && p.bits <= p.ip.BitLen() } + // Valid reports whether whether p.Bits() has a valid range for p.IP(). // If p.IP() is zero, Valid returns false. -func (p IPPrefix) Valid() bool { return !p.ip.IsZero() && p.bits <= p.ip.BitLen() } +// +// Deprecated: use the correctly named and identical IsValid method instead. +func (p IPPrefix) Valid() bool { return p.IsValid() } // IsZero reports whether p is its zero value. func (p IPPrefix) IsZero() bool { return p == IPPrefix{} } @@ -1605,15 +1623,22 @@ func (r IPRange) String() string { return "invalid IPRange" } -// Valid reports whether r.From() and r.To() are both non-zero and +// IsValid reports whether r.From() and r.To() are both non-zero and // obey the documented requirements: address families match, and From // is less than or equal to To. -func (r IPRange) Valid() bool { +func (r IPRange) IsValid() bool { return !r.from.IsZero() && r.from.z == r.to.z && !r.to.Less(r.from) } +// Valid reports whether r.From() and r.To() are both non-zero and +// obey the documented requirements: address families match, and From +// is less than or equal to To. +// +// Deprecated: use the correctly named and identical IsValid method instead. +func (r IPRange) Valid() bool { return r.IsValid() } + // Contains reports whether the range r includes addr. // // An invalid range always reports false. From 8f449c8272cc8b2520bcbca2b5115cdff2675c10 Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Tue, 6 Jul 2021 22:46:13 -0400 Subject: [PATCH 51/73] netaddr: add invalid from/to test cases for ParseIPRange (#199) Signed-off-by: Matt Layher --- netaddr_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/netaddr_test.go b/netaddr_test.go index 2ac2dec..f299435 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -2337,6 +2337,8 @@ func TestParseIPRange(t *testing.T) { want interface{} }{ {"", "no hyphen in range \"\""}, + {"foo-", `invalid From IP "foo" in range "foo-"`}, + {"1.2.3.4-foo", `invalid To IP "foo" in range "1.2.3.4-foo"`}, {"1.2.3.4-5.6.7.8", IPRange{mustIP("1.2.3.4"), mustIP("5.6.7.8")}}, {"1.2.3.4-0.1.2.3", "range 1.2.3.4 to 0.1.2.3 not valid"}, {"::1-::5", IPRange{mustIP("::1"), mustIP("::5")}}, From 70468d781e6c8410772579e1cafa96963530ff55 Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Wed, 7 Jul 2021 16:29:01 -0400 Subject: [PATCH 52/73] netaddr: add IP.IsPrivate (#198) Signed-off-by: Matt Layher --- example_test.go | 31 +++++++++++++++++++++++++++++++ netaddr.go | 23 +++++++++++++++++++++++ netaddr_test.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/example_test.go b/example_test.go index bfe3845..23f2e0d 100644 --- a/example_test.go +++ b/example_test.go @@ -157,6 +157,37 @@ func ExampleIP_IsGlobalUnicast() { // (::ffff:c000:203).IsGlobalUnicast() -> true } +func ExampleIP_IsPrivate() { + var ( + zeroIP netaddr.IP + + ipv4 = netaddr.MustParseIP("192.0.2.3") + ipv4Private = netaddr.MustParseIP("192.168.1.1") + + ipv6 = netaddr.MustParseIP("2001:db8::68") + ipv6Private = netaddr.MustParseIP("fd00::1") + ) + + fmt.Printf("IP{}.IsPrivate() -> %v\n", zeroIP.IsPrivate()) + + ips := []netaddr.IP{ + ipv4, + ipv4Private, + ipv6, + ipv6Private, + } + + for _, ip := range ips { + fmt.Printf("(%v).IsPrivate() -> %v\n", ip, ip.IsPrivate()) + } + // Output: + // IP{}.IsPrivate() -> false + // (192.0.2.3).IsPrivate() -> false + // (192.168.1.1).IsPrivate() -> true + // (2001:db8::68).IsPrivate() -> false + // (fd00::1).IsPrivate() -> true +} + func ExampleIP_IsUnspecified() { var zeroIP netaddr.IP ipv4AllZeroes := netaddr.MustParseIP("0.0.0.0") diff --git a/netaddr.go b/netaddr.go index 29718e7..6b747df 100644 --- a/netaddr.go +++ b/netaddr.go @@ -666,6 +666,29 @@ func (ip IP) IsGlobalUnicast() bool { !ip.IsLinkLocalUnicast() } +// IsPrivate reports whether ip is a private address, according to RFC 1918 +// (IPv4 addresses) and RFC 4193 (IPv6 addresses). That is, it reports whether +// ip is in 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, or fc00::/7. This is the +// same as the standard library's net.IP.IsPrivate. +func (ip IP) IsPrivate() bool { + // Match the stdlib's IsPrivate logic. + if ip.Is4() { + // RFC 1918 allocates 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 as + // private IPv4 address subnets. + return ip.v4(0) == 10 || + (ip.v4(0) == 172 && ip.v4(1)&0xf0 == 16) || + (ip.v4(0) == 192 && ip.v4(1) == 168) + } + + if ip.Is6() { + // RFC 4193 allocates fc00::/7 as the unique local unicast IPv6 address + // subnet. + return ip.v6(0)&0xfe == 0xfc + } + + return false // zero value +} + // IsUnspecified reports whether ip is an unspecified address, either the IPv4 // address "0.0.0.0" or the IPv6 address "::". // diff --git a/netaddr_test.go b/netaddr_test.go index f299435..ddae5ba 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -573,6 +573,11 @@ func TestIPProperties(t *testing.T) { ilm6 = mustIP("ff01::1") ilmZone6 = mustIP("ff01::1%eth0") + private4a = mustIP("10.0.0.1") + private4b = mustIP("172.16.0.1") + private4c = mustIP("192.168.1.1") + private6 = mustIP("fd00::1") + unspecified4 = IPv4(0, 0, 0, 0) unspecified6 = IPv6Unspecified() ) @@ -586,6 +591,7 @@ func TestIPProperties(t *testing.T) { linkLocalUnicast bool loopback bool multicast bool + private bool unspecified bool }{ { @@ -672,6 +678,30 @@ func TestIPProperties(t *testing.T) { interfaceLocalMulticast: true, multicast: true, }, + { + name: "private v4Addr 10/8", + ip: private4a, + globalUnicast: true, + private: true, + }, + { + name: "private v4Addr 172.16/12", + ip: private4b, + globalUnicast: true, + private: true, + }, + { + name: "private v4Addr 192.168/16", + ip: private4c, + globalUnicast: true, + private: true, + }, + { + name: "private v6Addr", + ip: private6, + globalUnicast: true, + private: true, + }, { name: "unspecified v4Addr", ip: unspecified4, @@ -716,6 +746,11 @@ func TestIPProperties(t *testing.T) { t.Errorf("IsMulticast(%v) = %v; want %v", tt.ip, multicast, tt.multicast) } + private := tt.ip.IsPrivate() + if private != tt.private { + t.Errorf("IsPrivate(%v) = %v; want %v", tt.ip, private, tt.private) + } + unspecified := tt.ip.IsUnspecified() if unspecified != tt.unspecified { t.Errorf("IsUnspecified(%v) = %v; want %v", tt.ip, unspecified, tt.unspecified) @@ -2797,6 +2832,7 @@ func TestNoAllocs(t *testing.T) { test("IP.IsLinkLocalUnicast", func() { sinkBool = MustParseIP("fe80::1").IsLinkLocalUnicast() }) test("IP.IsLoopback", func() { sinkBool = MustParseIP("fe80::1").IsLoopback() }) test("IP.IsMulticast", func() { sinkBool = MustParseIP("fe80::1").IsMulticast() }) + test("IP.IsPrivate", func() { sinkBool = MustParseIP("fd00::1").IsPrivate() }) test("IP.IsUnspecified", func() { sinkBool = IPv6Unspecified().IsUnspecified() }) test("IP.Prefix/4", func() { sinkIPPrefix = panicPfx(MustParseIP("1.2.3.4").Prefix(20)) }) test("IP.Prefix/6", func() { sinkIPPrefix = panicPfx(MustParseIP("fe80::1").Prefix(64)) }) From 2fa904114b6fe8ac9da84ae79fbd31066278bffd Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Sun, 18 Jul 2021 00:25:21 -0700 Subject: [PATCH 53/73] netaddr: use IsValid instead of Valid (#205) The Valid method is deprecated, use IsValid instead. Signed-off-by: Joe Tsai --- fuzz.go | 6 +++--- ipset.go | 10 +++++----- ipset_test.go | 2 +- netaddr.go | 28 ++++++++++++++-------------- netaddr_test.go | 8 ++++---- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/fuzz.go b/fuzz.go index 06d90a5..8066f7e 100644 --- a/fuzz.go +++ b/fuzz.go @@ -154,8 +154,8 @@ func parseIPPort(s string) (interface{}, error) { return ParseIPPort(s) } func parseIPPrefix(s string) (interface{}, error) { return ParseIPPrefix(s) } func checkStringParseRoundTrip(x fmt.Stringer, parse func(string) (interface{}, error)) { - v, vok := x.(interface{ Valid() bool }) - if vok && !v.Valid() { + v, vok := x.(interface{ IsValid() bool }) + if vok && !v.IsValid() { // Ignore invalid values. return } @@ -163,7 +163,7 @@ func checkStringParseRoundTrip(x fmt.Stringer, parse func(string) (interface{}, // The exception is if they have a Valid method and that Valid method // explicitly says that the zero value is valid. z, zok := x.(interface{ IsZero() bool }) - if zok && z.IsZero() && !(vok && v.Valid()) { + if zok && z.IsZero() && !(vok && v.IsValid()) { return } s := x.String() diff --git a/ipset.go b/ipset.go index f6e258c..fb5587b 100644 --- a/ipset.go +++ b/ipset.go @@ -64,7 +64,7 @@ func (s *IPSetBuilder) normalize() { } switch { - case !rout.Valid() || !rin.Valid(): + case !rout.IsValid() || !rin.IsValid(): // mergeIPRanges should have prevented invalid ranges from // sneaking in. panic("invalid IPRanges during Ranges merge") @@ -178,7 +178,7 @@ func (s *IPSetBuilder) Add(ip IP) { // AddPrefix adds all IPs in p to s. func (s *IPSetBuilder) AddPrefix(p IPPrefix) { - if r := p.Range(); r.Valid() { + if r := p.Range(); r.IsValid() { s.AddRange(r) } else { s.errs = append(s.errs, fmt.Errorf("AddPrefix of invalid prefix %q", p)) @@ -188,7 +188,7 @@ func (s *IPSetBuilder) AddPrefix(p IPPrefix) { // AddRange adds r to s. // If r is not Valid, AddRange does nothing. func (s *IPSetBuilder) AddRange(r IPRange) { - if !r.Valid() { + if !r.IsValid() { s.errs = append(s.errs, fmt.Errorf("AddRange of invalid range %q", r)) return } @@ -221,7 +221,7 @@ func (s *IPSetBuilder) Remove(ip IP) { // RemovePrefix removes all IPs in p from s. func (s *IPSetBuilder) RemovePrefix(p IPPrefix) { - if r := p.Range(); r.Valid() { + if r := p.Range(); r.IsValid() { s.RemoveRange(r) } else { s.errs = append(s.errs, fmt.Errorf("RemovePrefix of invalid prefix %q", p)) @@ -230,7 +230,7 @@ func (s *IPSetBuilder) RemovePrefix(p IPPrefix) { // RemoveRange removes all IPs in r from s. func (s *IPSetBuilder) RemoveRange(r IPRange) { - if r.Valid() { + if r.IsValid() { s.out = append(s.out, r) } else { s.errs = append(s.errs, fmt.Errorf("RemoveRange of invalid range %q", r)) diff --git a/ipset_test.go b/ipset_test.go index c5d4f72..459bf57 100644 --- a/ipset_test.go +++ b/ipset_test.go @@ -321,7 +321,7 @@ func TestIPSet(t *testing.T) { got := s.Ranges() t.Run("ranges", func(t *testing.T) { for _, v := range got { - if !v.Valid() { + if !v.IsValid() { t.Errorf("invalid IPRange in result: %s -> %s", v.From(), v.To()) } } diff --git a/netaddr.go b/netaddr.go index 6b747df..501f420 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1401,7 +1401,7 @@ func (p IPPrefix) Range() IPRange { // The returned value is always non-nil. // Any zone identifier is dropped in the conversion. func (p IPPrefix) IPNet() *net.IPNet { - if !p.Valid() { + if !p.IsValid() { return &net.IPNet{} } stdIP, _ := p.ip.ipZone(nil) @@ -1419,7 +1419,7 @@ func (p IPPrefix) IPNet() *net.IPNet { // If ip has an IPv6 zone, Contains returns false, // because IPPrefixes strip zones. func (p IPPrefix) Contains(ip IP) bool { - if !p.Valid() || ip.hasZone() { + if !p.IsValid() || ip.hasZone() { return false } if f1, f2 := p.ip.BitLen(), ip.BitLen(); f1 == 0 || f2 == 0 || f1 != f2 { @@ -1451,7 +1451,7 @@ func (p IPPrefix) Contains(ip IP) bool { // // If either has a Bits of zero, it returns true. func (p IPPrefix) Overlaps(o IPPrefix) bool { - if !p.Valid() || !o.Valid() { + if !p.IsValid() || !o.IsValid() { return false } if p == o { @@ -1490,7 +1490,7 @@ func (p IPPrefix) AppendTo(b []byte) []byte { if p.IsZero() { return b } - if !p.Valid() { + if !p.IsValid() { return append(b, "invalid IPPrefix"...) } @@ -1542,7 +1542,7 @@ func (p *IPPrefix) UnmarshalText(text []byte) error { // String returns the CIDR notation of p: "/". func (p IPPrefix) String() string { - if !p.Valid() { + if !p.IsValid() { return "invalid IPPrefix" } return fmt.Sprintf("%s/%d", p.ip, p.bits) @@ -1550,7 +1550,7 @@ func (p IPPrefix) String() string { // lastIP returns the last IP in the prefix. func (p IPPrefix) lastIP() IP { - if !p.Valid() { + if !p.IsValid() { return IP{} } a16 := p.ip.As16() @@ -1625,7 +1625,7 @@ func ParseIPRange(s string) (IPRange, error) { return r, fmt.Errorf("invalid To IP %q in range %q", to, s) } r.to = r.to.withoutZone() - if !r.Valid() { + if !r.IsValid() { return r, fmt.Errorf("range %v to %v not valid", r.from, r.to) } return r, nil @@ -1637,7 +1637,7 @@ func ParseIPRange(s string) (IPRange, error) { // separating the IPs, the same format recognized by // ParseIPRange. func (r IPRange) String() string { - if r.Valid() { + if r.IsValid() { return fmt.Sprintf("%s-%s", r.from, r.to) } if r.from.IsZero() || r.to.IsZero() { @@ -1669,7 +1669,7 @@ func (r IPRange) Valid() bool { return r.IsValid() } // If ip has an IPv6 zone, Contains returns false, // because IPPrefixes strip zones. func (r IPRange) Contains(addr IP) bool { - return r.Valid() && !addr.hasZone() && r.contains(addr) + return r.IsValid() && !addr.hasZone() && r.contains(addr) } // contains is like Contains, but without the validity check. @@ -1736,7 +1736,7 @@ func mergeIPRanges(rr []IPRange) (out []IPRange, valid bool) { for _, r := range rr[1:] { prev := &out[len(out)-1] switch { - case !r.Valid(): + case !r.IsValid(): // Invalid ranges make no sense to merge, refuse to // perform. return nil, false @@ -1778,8 +1778,8 @@ func mergeIPRanges(rr []IPRange) (out []IPRange, valid bool) { // If p and o are of different address families or either are invalid, // it reports false. func (r IPRange) Overlaps(o IPRange) bool { - return r.Valid() && - o.Valid() && + return r.IsValid() && + o.IsValid() && r.from.Compare(o.to) <= 0 && o.from.Compare(r.to) <= 0 } @@ -1802,7 +1802,7 @@ func (r IPRange) Prefixes() []IPPrefix { // AppendPrefixes is an append version of IPRange.Prefixes. It appends // the IPPrefix entries that cover r to dst. func (r IPRange) AppendPrefixes(dst []IPPrefix) []IPPrefix { - if !r.Valid() { + if !r.IsValid() { return nil } return appendRangePrefixes(dst, r.prefixFrom128AndBits, r.from.addr, r.to.addr) @@ -1835,7 +1835,7 @@ func comparePrefixes(a, b uint128) (common uint8, aZeroBSet bool) { // Prefix returns r as an IPPrefix, if it can be presented exactly as such. // If r is not valid or is not exactly equal to one prefix, ok is false. func (r IPRange) Prefix() (p IPPrefix, ok bool) { - if !r.Valid() { + if !r.IsValid() { return } if common, ok := comparePrefixes(r.from.addr, r.to.addr); ok { diff --git a/netaddr_test.go b/netaddr_test.go index ddae5ba..51613fc 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -1506,9 +1506,9 @@ func TestIPPrefixValid(t *testing.T) { {IPPrefix{IP{}, 128}, false}, } for _, tt := range tests { - got := tt.ipp.Valid() + got := tt.ipp.IsValid() if got != tt.want { - t.Errorf("(%v).Valid() = %v want %v", tt.ipp, got, tt.want) + t.Errorf("(%v).IsValid() = %v want %v", tt.ipp, got, tt.want) } } } @@ -2522,7 +2522,7 @@ func TestIPRangeValid(t *testing.T) { {IPRange{mustIP("1.2.3.4"), mustIP("::1")}, false}, // family mismatch } for _, tt := range tests { - got := tt.r.Valid() + got := tt.r.IsValid() if got != tt.want { t.Errorf("range %v to %v Valid = %v; want %v", tt.r.From(), tt.r.To(), got, tt.want) } @@ -2882,7 +2882,7 @@ func TestNoAllocs(t *testing.T) { test("ParseIPRange", func() { sinkIPRange = panicIPR(ParseIPRange("1.2.3.0-1.2.4.150")) }) // IPRange methods - test("IPRange.Valid", func() { sinkBool = panicIPR(ParseIPRange("1.2.3.0-1.2.4.150")).Valid() }) + test("IPRange.Valid", func() { sinkBool = panicIPR(ParseIPRange("1.2.3.0-1.2.4.150")).IsValid() }) test("IPRange.Overlaps", func() { a := panicIPR(ParseIPRange("1.2.3.0-1.2.3.150")) b := panicIPR(ParseIPRange("1.2.4.0-1.2.4.255")) From 3186d8243dea736e5f40385c50c48386aa91a23b Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Sun, 18 Jul 2021 00:26:21 -0700 Subject: [PATCH 54/73] netaddr: fix fuzz build (#204) The appendMarshaller type is declared in both fuzz.go and netaddr_test.go, which causes the fuzz tests to fail to build. Use the one in netaddr_test.go. While we're at it, rename it to appendMarshaler. Signed-off-by: Joe Tsai --- fuzz.go | 9 ++------- netaddr_test.go | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/fuzz.go b/fuzz.go index 8066f7e..d2d721c 100644 --- a/fuzz.go +++ b/fuzz.go @@ -129,13 +129,8 @@ func checkBinaryMarshaller(x encoding.BinaryMarshaler) { } } -type appendMarshaller interface { - encoding.TextMarshaler - AppendTo([]byte) []byte -} - // checkTextMarshalMatchesAppendTo checks that x's MarshalText matches x's AppendTo. -func checkTextMarshalMatchesAppendTo(x appendMarshaller) { +func checkTextMarshalMatchesAppendTo(x appendMarshaler) { buf, err := x.MarshalText() if err != nil { panic(err) @@ -189,7 +184,7 @@ func checkEncoding(x interface{}) { if bm, ok := x.(encoding.BinaryMarshaler); ok { checkBinaryMarshaller(bm) } - if am, ok := x.(appendMarshaller); ok { + if am, ok := x.(appendMarshaler); ok { checkTextMarshalMatchesAppendTo(am) } } diff --git a/netaddr_test.go b/netaddr_test.go index 51613fc..7701976 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -1799,14 +1799,14 @@ func TestIPPortMarshalUnmarshal(t *testing.T) { } } -type appendMarshaller interface { +type appendMarshaler interface { encoding.TextMarshaler AppendTo([]byte) []byte } // testAppendToMarshal tests that x's AppendTo and MarshalText methods yield the same results. // x's MarshalText method must not return an error. -func testAppendToMarshal(t *testing.T, x appendMarshaller) { +func testAppendToMarshal(t *testing.T, x appendMarshaler) { t.Helper() m, err := x.MarshalText() if err != nil { From d328a730c896a941afeb066c855861b1701503ad Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Sun, 18 Jul 2021 00:27:33 -0700 Subject: [PATCH 55/73] netaddr: make UnmarshalText methods more consistent with each other (#203) Signed-off-by: Joe Tsai --- netaddr.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/netaddr.go b/netaddr.go index 501f420..9a7c156 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1209,7 +1209,7 @@ func (p IPPort) MarshalText() ([]byte, error) { // value. func (p *IPPort) UnmarshalText(text []byte) error { if p.ip.z != z0 || p.port != 0 { - return errors.New("netaddr: refusing to UnmarshalText into non-zero IP") + return errors.New("netaddr: refusing to Unmarshal into non-zero IPPort") } if len(text) == 0 { return nil @@ -1530,11 +1530,9 @@ func (p *IPPrefix) UnmarshalText(text []byte) error { if *p != (IPPrefix{}) { return errors.New("netaddr: refusing to Unmarshal into non-zero IPPrefix") } - if len(text) == 0 { return nil } - var err error *p, err = ParseIPPrefix(string(text)) return err From 06ca8145d7228671bf9732984d9065b62abfb61a Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Sun, 18 Jul 2021 00:45:54 -0700 Subject: [PATCH 56/73] netaddr: add functionality to IPRange for feature parity (#202) Add MustParseIPRange, IPRange.{IsZero,AppendTo,MarshalText,UnmarshalText} to bring it to feature parity with the other common types. Similar to the MarshalText methods of the other types, it never returns an error for invalid values. This behavior should probably change, but should be consistently done for all other types (see #169). Fixes #201 Signed-off-by: Joe Tsai --- netaddr.go | 60 ++++++++++++++++++++++++++++++++++++++++ netaddr_test.go | 73 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 122 insertions(+), 11 deletions(-) diff --git a/netaddr.go b/netaddr.go index 9a7c156..727227d 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1629,6 +1629,16 @@ func ParseIPRange(s string) (IPRange, error) { return r, nil } +// MustParseIPRange calls ParseIPRange(s) and panics on error. +// It is intended for use in tests with hard-coded strings. +func MustParseIPRange(s string) IPRange { + r, err := ParseIPRange(s) + if err != nil { + panic(err) + } + return r +} + // String returns a string representation of the range. // // For a valid range, the form is "From-To" with a single hyphen @@ -1644,6 +1654,56 @@ func (r IPRange) String() string { return "invalid IPRange" } +// AppendTo appends a text encoding of r, +// as generated by MarshalText, +// to b and returns the extended buffer. +func (r IPRange) AppendTo(b []byte) []byte { + if r.IsZero() { + return b + } + b = r.from.AppendTo(b) + b = append(b, '-') + b = r.to.AppendTo(b) + return b +} + +// MarshalText implements the encoding.TextMarshaler interface, +// The encoding is the same as returned by String, with one exception: +// If ip is the zero value, the encoding is the empty string. +func (r IPRange) MarshalText() ([]byte, error) { + if r.IsZero() { + return []byte(""), nil + } + var max int + if r.from.z == z4 { + max = len("255.255.255.255-255.255.255.255") + } else { + max = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + } + b := make([]byte, 0, max) + return r.AppendTo(b), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// The IP range is expected in a form accepted by ParseIPRange. +// It returns an error if *r is not the IPRange zero value. +func (r *IPRange) UnmarshalText(text []byte) error { + if *r != (IPRange{}) { + return errors.New("netaddr: refusing to Unmarshal into non-zero IPRange") + } + if len(text) == 0 { + return nil + } + var err error + *r, err = ParseIPRange(string(text)) + return err +} + +// IsZero reports whether r is the zero value of the IPRange type. +func (r IPRange) IsZero() bool { + return r == IPRange{} +} + // IsValid reports whether r.From() and r.To() are both non-zero and // obey the documented requirements: address families match, and From // is less than or equal to To. diff --git a/netaddr_test.go b/netaddr_test.go index 7701976..dd69a53 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -2395,6 +2395,26 @@ func TestParseIPRange(t *testing.T) { t.Errorf("input %q stringifies back as %q", tt.in, back) } } + + var r2 IPRange + err = r2.UnmarshalText([]byte(tt.in)) + if err != nil { + got = err.Error() + } else { + got = r2 + } + if got != tt.want && tt.in != "" { + t.Errorf("UnmarshalText(%q) = %v; want %v", tt.in, got, tt.want) + } + + testAppendToMarshal(t, r) + } +} + +func TestIPRangeUnmarshalTextNonZero(t *testing.T) { + r := MustParseIPRange("1.2.3.4-5.6.7.8") + if err := r.UnmarshalText([]byte("1.2.3.4-5.6.7.8")); err == nil { + t.Fatal("unmarshaled into non-empty IPPrefix") } } @@ -2776,12 +2796,6 @@ func TestNoAllocs(t *testing.T) { } return ipp } - panicIPR := func(ipr IPRange, err error) IPRange { - if err != nil { - panic(err) - } - return ipr - } test := func(name string, f func()) { t.Run(name, func(t *testing.T) { @@ -2879,17 +2893,18 @@ func TestNoAllocs(t *testing.T) { // IPRange constructors test("IPRangeFrom", func() { sinkIPRange = IPRangeFrom(IPv4(1, 2, 3, 4), IPv4(4, 3, 2, 1)) }) - test("ParseIPRange", func() { sinkIPRange = panicIPR(ParseIPRange("1.2.3.0-1.2.4.150")) }) + test("ParseIPRange", func() { sinkIPRange = MustParseIPRange("1.2.3.0-1.2.4.150") }) // IPRange methods - test("IPRange.Valid", func() { sinkBool = panicIPR(ParseIPRange("1.2.3.0-1.2.4.150")).IsValid() }) + test("IPRange.IsZero", func() { sinkBool = MustParseIPRange("1.2.3.0-1.2.4.150").IsZero() }) + test("IPRange.IsValid", func() { sinkBool = MustParseIPRange("1.2.3.0-1.2.4.150").IsValid() }) test("IPRange.Overlaps", func() { - a := panicIPR(ParseIPRange("1.2.3.0-1.2.3.150")) - b := panicIPR(ParseIPRange("1.2.4.0-1.2.4.255")) + a := MustParseIPRange("1.2.3.0-1.2.3.150") + b := MustParseIPRange("1.2.4.0-1.2.4.255") sinkBool = a.Overlaps(b) }) test("IPRange.Prefix", func() { - a := panicIPR(ParseIPRange("1.2.3.0-1.2.3.255")) + a := MustParseIPRange("1.2.3.0-1.2.3.255") sinkIPPrefix = panicPfxOK(a.Prefix()) }) } @@ -2926,3 +2941,39 @@ func TestInvalidIPPortString(t *testing.T) { } } } + +func TestMethodParity(t *testing.T) { + // Collect all method names for each type. + methods := make(map[string][]reflect.Type) + allTypes := []reflect.Type{ + reflect.TypeOf((*IP)(nil)), + reflect.TypeOf((*IPPort)(nil)), + reflect.TypeOf((*IPPrefix)(nil)), + reflect.TypeOf((*IPRange)(nil)), + } + for _, typ := range allTypes { + for i := 0; i < typ.NumMethod(); i++ { + name := typ.Method(i).Name + methods[name] = append(methods[name], typ) + } + } + + // Check whether sufficiently common methods exist on all types. + ignoreList := map[string]string{ + "Valid": "method is deprecated", + } + for name, types := range methods { + if _, ignore := ignoreList[name]; ignore { + continue // method is ignored for parity check + } + if !(len(allTypes)/2 < len(types) && len(types) < len(allTypes)) { + continue // either too unique or all types already have that method + } + for _, typ := range allTypes { + if _, ok := typ.MethodByName(name); ok { + continue // this type already has this method + } + t.Errorf("%v.%v is missing", typ.Elem().Name(), name) + } + } +} From f6a6f43734d14ccc7fdd7e0d65ef52ecd43cde34 Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Mon, 19 Jul 2021 13:43:48 -0400 Subject: [PATCH 57/73] netaddr: add test for IP.UnmarshalBinary with bad byte length (#207) Signed-off-by: Matt Layher --- netaddr_test.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/netaddr_test.go b/netaddr_test.go index dd69a53..aa72b93 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -363,10 +363,18 @@ func TestIPMarshalUnmarshalBinary(t *testing.T) { } // Cannot unmarshal into a non-zero IP - ip := MustParseIP("1.2.3.4") - if err := ip.UnmarshalBinary([]byte{1, 1, 1, 1}); err == nil { + ip1 := MustParseIP("1.2.3.4") + if err := ip1.UnmarshalBinary([]byte{1, 1, 1, 1}); err == nil { t.Fatal("unmarshaled into non-empty IP") } + + // Cannot unmarshal from unexpected IP length. + for _, l := range []int{3, 5} { + var ip2 IP + if err := ip2.UnmarshalBinary(bytes.Repeat([]byte{1}, l)); err == nil { + t.Fatalf("unmarshaled from unexpected IP length %d", l) + } + } } func TestIPMarshalUnmarshal(t *testing.T) { From e4560de9d34c289711270eecd9b910af05be1b4e Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 2 Jun 2021 11:44:26 -0700 Subject: [PATCH 58/73] distinguish zero from invalid in IPPrefix.String We do this in IPRange, and it is useful. Signed-off-by: Josh Bleecher Snyder --- netaddr.go | 3 +++ netaddr_test.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/netaddr.go b/netaddr.go index 727227d..7af28e5 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1540,6 +1540,9 @@ func (p *IPPrefix) UnmarshalText(text []byte) error { // String returns the CIDR notation of p: "/". func (p IPPrefix) String() string { + if p.IsZero() { + return "zero IPPrefix" + } if !p.IsValid() { return "invalid IPPrefix" } diff --git a/netaddr_test.go b/netaddr_test.go index aa72b93..553178b 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -2922,7 +2922,7 @@ func TestIPPrefixString(t *testing.T) { ipp IPPrefix want string }{ - {IPPrefix{}, "invalid IPPrefix"}, + {IPPrefix{}, "zero IPPrefix"}, {IPPrefixFrom(IP{}, 8), "invalid IPPrefix"}, {IPPrefixFrom(MustParseIP("1.2.3.4"), 88), "invalid IPPrefix"}, } From 73d24586a36ea4da7b984e8d52d35f70b572ef40 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 2 Jun 2021 11:46:04 -0700 Subject: [PATCH 59/73] add stack traces to IPSetBuilder errors Sample error string: AddPrefix of invalid prefix "zero IPPrefix" @ ipp.go:1 /Users/josh/go/1.16/src/testing/testing.go:1193; AddRange of invalid range "zero IPRange" @ r.go:2 /Users/josh/go/1.16/src/testing/testing.go:1193 Signed-off-by: Josh Bleecher Snyder --- ipset.go | 52 +++++++++++++++++++++++++++++++++++++++++------- stackerr_test.go | 31 +++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 stackerr_test.go diff --git a/ipset.go b/ipset.go index fb5587b..9c7f680 100644 --- a/ipset.go +++ b/ipset.go @@ -5,8 +5,8 @@ package netaddr import ( - "errors" "fmt" + "runtime" "sort" "strings" ) @@ -168,6 +168,18 @@ func (s *IPSetBuilder) Clone() *IPSetBuilder { } } +func (s *IPSetBuilder) addError(msg string, args ...interface{}) { + se := new(stacktraceErr) + // Skip three frames: runtime.Callers, addError, and the IPSetBuilder + // method that called addError (such as IPSetBuilder.Add). + // The resulting stack trace ends at the line in the user's + // code where they called into netaddr. + n := runtime.Callers(3, se.pcs[:]) + se.at = se.pcs[:n] + se.err = fmt.Errorf(msg, args...) + s.errs = append(s.errs, se) +} + // Add adds ip to s. func (s *IPSetBuilder) Add(ip IP) { if ip.IsZero() { @@ -181,7 +193,7 @@ func (s *IPSetBuilder) AddPrefix(p IPPrefix) { if r := p.Range(); r.IsValid() { s.AddRange(r) } else { - s.errs = append(s.errs, fmt.Errorf("AddPrefix of invalid prefix %q", p)) + s.addError("AddPrefix of invalid prefix %q", p) } } @@ -189,7 +201,7 @@ func (s *IPSetBuilder) AddPrefix(p IPPrefix) { // If r is not Valid, AddRange does nothing. func (s *IPSetBuilder) AddRange(r IPRange) { if !r.IsValid() { - s.errs = append(s.errs, fmt.Errorf("AddRange of invalid range %q", r)) + s.addError("AddRange of invalid range %q", r) return } // If there are any removals (s.out), then we need to compact the set @@ -213,7 +225,7 @@ func (s *IPSetBuilder) AddSet(b *IPSet) { // Remove removes ip from s. func (s *IPSetBuilder) Remove(ip IP) { if ip.IsZero() { - s.errs = append(s.errs, errors.New("ignored Remove of zero IP")) + s.addError("ignored Remove of zero IP") } else { s.RemoveRange(IPRangeFrom(ip, ip)) } @@ -224,7 +236,7 @@ func (s *IPSetBuilder) RemovePrefix(p IPPrefix) { if r := p.Range(); r.IsValid() { s.RemoveRange(r) } else { - s.errs = append(s.errs, fmt.Errorf("RemovePrefix of invalid prefix %q", p)) + s.addError("RemovePrefix of invalid prefix %q", p) } } @@ -233,7 +245,7 @@ func (s *IPSetBuilder) RemoveRange(r IPRange) { if r.IsValid() { s.out = append(s.out, r) } else { - s.errs = append(s.errs, fmt.Errorf("RemoveRange of invalid range %q", r)) + s.addError("RemoveRange of invalid range %q", r) } } @@ -454,5 +466,31 @@ func (e multiErr) Error() string { for _, err := range e { ret = append(ret, err.Error()) } - return strings.Join(ret, ", ") + return strings.Join(ret, "; ") +} + +// A stacktraceErr combines an error with a stack trace. +type stacktraceErr struct { + pcs [16]uintptr // preallocated array of PCs + at []uintptr // stack trace whence the error + err error // underlying error +} + +func (e *stacktraceErr) Error() string { + frames := runtime.CallersFrames(e.at) + buf := new(strings.Builder) + buf.WriteString(e.err.Error()) + buf.WriteString(" @ ") + for { + frame, more := frames.Next() + if !more { + break + } + fmt.Fprintf(buf, "%s:%d ", frame.File, frame.Line) + } + return strings.TrimSpace(buf.String()) +} + +func (e *stacktraceErr) Unwrap() error { + return e.err } diff --git a/stackerr_test.go b/stackerr_test.go new file mode 100644 index 0000000..c5335ec --- /dev/null +++ b/stackerr_test.go @@ -0,0 +1,31 @@ +// Copyright 2021 The Inet.Af 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 netaddr_test + +import ( + "strings" + "testing" + + "inet.af/netaddr" +) + +// The tests for stacktrace errors is in its own file, +// so that the line number munging that we do doesn't +// break line numbers for other tests. + +func TestStacktraceErr(t *testing.T) { + b := new(netaddr.IPSetBuilder) +//line ipp.go:1 + b.AddPrefix(netaddr.IPPrefix{}) +//line r.go:2 + b.AddRange(netaddr.IPRange{}) + _, err := b.IPSet() + got := err.Error() + for _, want := range []string{"ipp.go:1", "r.go:2"} { + if !strings.Contains(got, want) { + t.Errorf("error should contain %q, got %q", want, got) + } + } +} From c63926427f5fde823350c87fdc4b852d5f4cf29d Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 2 Jun 2021 11:46:28 -0700 Subject: [PATCH 60/73] make IPSetBuilder.Add(IP{}) record an error Signed-off-by: Josh Bleecher Snyder --- ipset.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ipset.go b/ipset.go index 9c7f680..c5f15d7 100644 --- a/ipset.go +++ b/ipset.go @@ -183,6 +183,7 @@ func (s *IPSetBuilder) addError(msg string, args ...interface{}) { // Add adds ip to s. func (s *IPSetBuilder) Add(ip IP) { if ip.IsZero() { + s.addError("Add of invalid IP %q", ip) return } s.AddRange(IPRangeFrom(ip, ip)) From d94309895f12ae94458c9ef9a4b29e4d0045ddc3 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 2 Jun 2021 11:51:30 -0700 Subject: [PATCH 61/73] print IP{} as "zero IP" instead of "invalid IP" It's a bit more precise. Signed-off-by: Josh Bleecher Snyder --- example_test.go | 2 +- netaddr.go | 2 +- netaddr_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example_test.go b/example_test.go index 23f2e0d..1c3028c 100644 --- a/example_test.go +++ b/example_test.go @@ -45,7 +45,7 @@ func ExampleIP_properties() { w.Flush() // Output: // String() Zone() IsZero() Is4() Is6() Is4in6() - // invalid IP true false false false + // zero IP true false false false // 192.0.2.3 false true false false // 2001:db8::68 false false true false // 2001:db8::68%eth0 eth0 false false true false diff --git a/netaddr.go b/netaddr.go index 7af28e5..88c3ef2 100644 --- a/netaddr.go +++ b/netaddr.go @@ -821,7 +821,7 @@ func (ip IP) Prior() IP { func (ip IP) String() string { switch ip.z { case z0: - return "invalid IP" + return "zero IP" case z4: return ip.string4() default: diff --git a/netaddr_test.go b/netaddr_test.go index 553178b..f01a95d 100644 --- a/netaddr_test.go +++ b/netaddr_test.go @@ -861,7 +861,7 @@ func TestLessCompare(t *testing.T) { } sort.Slice(values, func(i, j int) bool { return values[i].Less(values[j]) }) got := fmt.Sprintf("%s", values) - want := `[invalid IP 1.2.3.4 8.8.8.8 ::1 ::1%foo ::2]` + want := `[zero IP 1.2.3.4 8.8.8.8 ::1 ::1%foo ::2]` if got != want { t.Errorf("unexpected sort\n got: %s\nwant: %s\n", got, want) } @@ -874,7 +874,7 @@ func TestIPStringExpanded(t *testing.T) { }{ { ip: IP{}, - s: "invalid IP", + s: "zero IP", }, { ip: mustIP("192.0.2.1"), From 629ef9202af8b99509ef28e070215909ae31297d Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 2 Jun 2021 11:56:17 -0700 Subject: [PATCH 62/73] make IPSetBuilder errors more concise, more detailed When something goes wrong, printing "invalid IPPrefix" is not as useful as printing "1.2.3.4/33", which the user can use to see what exactly when wrong. New sample error, from the test: AddPrefix(1.2.3.4/33) @ ipp.go:1 /Users/josh/go/1.16/src/testing/testing.go:1193; AddRange(zero IP-zero IP) @ r.go:2 /Users/josh/go/1.16/src/testing/testing.go:1193 Signed-off-by: Josh Bleecher Snyder --- ipset.go | 12 ++++++------ stackerr_test.go | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ipset.go b/ipset.go index c5f15d7..b448e25 100644 --- a/ipset.go +++ b/ipset.go @@ -183,7 +183,7 @@ func (s *IPSetBuilder) addError(msg string, args ...interface{}) { // Add adds ip to s. func (s *IPSetBuilder) Add(ip IP) { if ip.IsZero() { - s.addError("Add of invalid IP %q", ip) + s.addError("Add(IP{})") return } s.AddRange(IPRangeFrom(ip, ip)) @@ -194,7 +194,7 @@ func (s *IPSetBuilder) AddPrefix(p IPPrefix) { if r := p.Range(); r.IsValid() { s.AddRange(r) } else { - s.addError("AddPrefix of invalid prefix %q", p) + s.addError("AddPrefix(%v/%v)", p.IP(), p.Bits()) } } @@ -202,7 +202,7 @@ func (s *IPSetBuilder) AddPrefix(p IPPrefix) { // If r is not Valid, AddRange does nothing. func (s *IPSetBuilder) AddRange(r IPRange) { if !r.IsValid() { - s.addError("AddRange of invalid range %q", r) + s.addError("AddRange(%v-%v)", r.From(), r.To()) return } // If there are any removals (s.out), then we need to compact the set @@ -226,7 +226,7 @@ func (s *IPSetBuilder) AddSet(b *IPSet) { // Remove removes ip from s. func (s *IPSetBuilder) Remove(ip IP) { if ip.IsZero() { - s.addError("ignored Remove of zero IP") + s.addError("Remove(IP{})") } else { s.RemoveRange(IPRangeFrom(ip, ip)) } @@ -237,7 +237,7 @@ func (s *IPSetBuilder) RemovePrefix(p IPPrefix) { if r := p.Range(); r.IsValid() { s.RemoveRange(r) } else { - s.addError("RemovePrefix of invalid prefix %q", p) + s.addError("RemovePrefix(%v/%v)", p.IP(), p.Bits()) } } @@ -246,7 +246,7 @@ func (s *IPSetBuilder) RemoveRange(r IPRange) { if r.IsValid() { s.out = append(s.out, r) } else { - s.addError("RemoveRange of invalid range %q", r) + s.addError("RemoveRange(%v-%v)", r.From(), r.To()) } } diff --git a/stackerr_test.go b/stackerr_test.go index c5335ec..d64962b 100644 --- a/stackerr_test.go +++ b/stackerr_test.go @@ -18,12 +18,12 @@ import ( func TestStacktraceErr(t *testing.T) { b := new(netaddr.IPSetBuilder) //line ipp.go:1 - b.AddPrefix(netaddr.IPPrefix{}) + b.AddPrefix(netaddr.IPPrefixFrom(netaddr.IPv4(1,2,3,4), 33)) //line r.go:2 b.AddRange(netaddr.IPRange{}) _, err := b.IPSet() got := err.Error() - for _, want := range []string{"ipp.go:1", "r.go:2"} { + for _, want := range []string{"ipp.go:1", "r.go:2", "33"} { if !strings.Contains(got, want) { t.Errorf("error should contain %q, got %q", want, got) } From ce7a8ad02cc17888a2713b182e3b7161f35d974e Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 21 Jul 2021 14:29:46 -0700 Subject: [PATCH 63/73] fix fuzz build Fixes #208 Updates #204 Signed-off-by: Josh Bleecher Snyder --- fuzz.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fuzz.go b/fuzz.go index d2d721c..ccf7837 100644 --- a/fuzz.go +++ b/fuzz.go @@ -129,8 +129,18 @@ func checkBinaryMarshaller(x encoding.BinaryMarshaler) { } } +// fuzzAppendMarshaler is identical to appendMarshaler, defined in netaddr_test.go. +// We have two because the two go-fuzz implementations differ +// in whether they include _test.go files when typechecking. +// We need this fuzz file to compile with and without netaddr_test.go, +// which means defining the interface twice. +type fuzzAppendMarshaler interface { + encoding.TextMarshaler + AppendTo([]byte) []byte +} + // checkTextMarshalMatchesAppendTo checks that x's MarshalText matches x's AppendTo. -func checkTextMarshalMatchesAppendTo(x appendMarshaler) { +func checkTextMarshalMatchesAppendTo(x fuzzAppendMarshaler) { buf, err := x.MarshalText() if err != nil { panic(err) @@ -184,7 +194,7 @@ func checkEncoding(x interface{}) { if bm, ok := x.(encoding.BinaryMarshaler); ok { checkBinaryMarshaller(bm) } - if am, ok := x.(appendMarshaler); ok { + if am, ok := x.(fuzzAppendMarshaler); ok { checkTextMarshalMatchesAppendTo(am) } } From 31d5ee66059c37216d443e3a4063144965a5b9b3 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Thu, 29 Jul 2021 13:02:52 -0700 Subject: [PATCH 64/73] remove "netaddr: " prefix from errors Fixes #211 Signed-off-by: Josh Bleecher Snyder --- netaddr.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/netaddr.go b/netaddr.go index 88c3ef2..1ad5f41 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1004,7 +1004,7 @@ func (ip IP) MarshalText() ([]byte, error) { // It returns an error if *ip is not the IP zero value. func (ip *IP) UnmarshalText(text []byte) error { if ip.z != z0 { - return errors.New("netaddr: refusing to Unmarshal into non-zero IP") + return errors.New("refusing to Unmarshal into non-zero IP") } if len(text) == 0 { return nil @@ -1035,7 +1035,7 @@ func (ip IP) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. func (ip *IP) UnmarshalBinary(b []byte) error { if ip.z != z0 { - return errors.New("netaddr: refusing to Unmarshal into non-zero IP") + return errors.New("refusing to Unmarshal into non-zero IP") } n := len(b) switch { @@ -1051,7 +1051,7 @@ func (ip *IP) UnmarshalBinary(b []byte) error { *ip = ipv6Slice(b[:16]).WithZone(string(b[16:])) return nil } - return fmt.Errorf("netaddr: unexpected ip size: %v", len(b)) + return fmt.Errorf("unexpected ip size: %v", len(b)) } // IPPort is an IP and a port number. @@ -1209,7 +1209,7 @@ func (p IPPort) MarshalText() ([]byte, error) { // value. func (p *IPPort) UnmarshalText(text []byte) error { if p.ip.z != z0 || p.port != 0 { - return errors.New("netaddr: refusing to Unmarshal into non-zero IPPort") + return errors.New("refusing to Unmarshal into non-zero IPPort") } if len(text) == 0 { return nil @@ -1528,7 +1528,7 @@ func (p IPPrefix) MarshalText() ([]byte, error) { // It returns an error if *p is not the IPPrefix zero value. func (p *IPPrefix) UnmarshalText(text []byte) error { if *p != (IPPrefix{}) { - return errors.New("netaddr: refusing to Unmarshal into non-zero IPPrefix") + return errors.New("refusing to Unmarshal into non-zero IPPrefix") } if len(text) == 0 { return nil @@ -1692,7 +1692,7 @@ func (r IPRange) MarshalText() ([]byte, error) { // It returns an error if *r is not the IPRange zero value. func (r *IPRange) UnmarshalText(text []byte) error { if *r != (IPRange{}) { - return errors.New("netaddr: refusing to Unmarshal into non-zero IPRange") + return errors.New("refusing to Unmarshal into non-zero IPRange") } if len(text) == 0 { return nil From 6bd96879f21b92cc8395a5032ef82024fc76b3c9 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Fri, 3 Sep 2021 09:22:17 +0200 Subject: [PATCH 65/73] all: gofmt with Go 1.17 to add //go:build tags Also fixes formatting in TestStacktraceErr. Signed-off-by: Tobias Klauser --- fuzz.go | 1 + stackerr_test.go | 2 +- tools/tools.go | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/fuzz.go b/fuzz.go index ccf7837..cf1836d 100644 --- a/fuzz.go +++ b/fuzz.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build gofuzz // +build gofuzz package netaddr diff --git a/stackerr_test.go b/stackerr_test.go index d64962b..f45a746 100644 --- a/stackerr_test.go +++ b/stackerr_test.go @@ -18,7 +18,7 @@ import ( func TestStacktraceErr(t *testing.T) { b := new(netaddr.IPSetBuilder) //line ipp.go:1 - b.AddPrefix(netaddr.IPPrefixFrom(netaddr.IPv4(1,2,3,4), 33)) + b.AddPrefix(netaddr.IPPrefixFrom(netaddr.IPv4(1, 2, 3, 4), 33)) //line r.go:2 b.AddRange(netaddr.IPRange{}) _, err := b.IPSet() diff --git a/tools/tools.go b/tools/tools.go index 924fb4f..3a95c7f 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -1,3 +1,4 @@ +//go:build tools // +build tools package tools From 193f8ffea0e95945392de370f9877fe202568673 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Fri, 3 Sep 2021 09:22:19 +0200 Subject: [PATCH 66/73] .github/workflows: add Go 1.17 to test matrix Drop 1.15 and keep 1.12, 1.16. Signed-off-by: Tobias Klauser --- .github/workflows/linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4864ae7..698550b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: # Oldest we support (1.12) and a latest couple: - go-version: [1.12, 1.15, 1.16] + go-version: [1.12, 1.16, 1.17] runs-on: ubuntu-latest steps: @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v1 - name: Check Go modules - if: matrix.go-version == '1.16' + if: matrix.go-version == '1.17' run: | go mod tidy git diff --exit-code From 85fa6c94624ec959e7e248c26f75d8f6ae53f313 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Fri, 3 Sep 2021 09:22:20 +0200 Subject: [PATCH 67/73] .github/workflows: check formatting Check gofmt formatting as part of GitHub actions tests. Signed-off-by: Tobias Klauser --- .github/workflows/linux.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 698550b..9d2bb19 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -32,5 +32,9 @@ jobs: go mod tidy git diff --exit-code + - name: Check formatting + if: matrix.go-version == '1.17' + run: diff -u <(echo -n) <(gofmt -d .) + - name: Run tests on linux run: go test ./... From 2e4260506ddd00756d58fc41c6409797a2046d89 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 22 Oct 2021 15:26:05 -0700 Subject: [PATCH 68/73] fix IPPrefix docs Thanks to @hknutzen --- netaddr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netaddr.go b/netaddr.go index 1ad5f41..9768406 100644 --- a/netaddr.go +++ b/netaddr.go @@ -1282,7 +1282,7 @@ type IPPrefix struct { bits uint8 } -// IPPrefixFrom returns an IPPrefix with IP ip and port port. +// IPPrefixFrom returns an IPPrefix with IP ip and provided bits prefix length. // It does not allocate. func IPPrefixFrom(ip IP, bits uint8) IPPrefix { return IPPrefix{ From c74959edd3b6ab3e864c55fd889ca8c13a835a81 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 27 Oct 2021 15:00:19 -0700 Subject: [PATCH 69/73] update intern module for 1.18 The only change is to the assume-no-moving-gc module. Signed-off-by: Josh Bleecher Snyder --- go.mod | 3 +-- go.sum | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 0097802..f449dd6 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.12 require ( github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 - go4.org/intern v0.0.0-20210108033219-3eb7198706b2 - go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect + go4.org/intern v0.0.0-20211027215823-ae77deb06f29 golang.org/x/tools v0.1.0 // indirect ) diff --git a/go.sum b/go.sum index 2995d08..e336ffb 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,10 @@ github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 h1:q1oJaUPdmpDm/VyXosjgPgr6wS7c5iV2p0PwJD73bUI= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg= -go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= From 377050f8e01de48af5bf56e3f714d7f210f5f648 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 16 Jun 2022 20:17:27 -0700 Subject: [PATCH 70/73] go.mod: bump go4.org/unsafe/assume-no-moving-gc for Go 1.19 --- go.mod | 1 + go.sum | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f449dd6..8f2347e 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,6 @@ go 1.12 require ( github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 go4.org/intern v0.0.0-20211027215823-ae77deb06f29 + go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect golang.org/x/tools v0.1.0 // indirect ) diff --git a/go.sum b/go.sum index e336ffb..e035557 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,9 @@ github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA= go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= From 09700637632167e91519bf5930d7ad004b796156 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 16 Jun 2022 20:18:23 -0700 Subject: [PATCH 71/73] fix inling test Signed-off-by: Brad Fitzpatrick --- inlining_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/inlining_test.go b/inlining_test.go index 07ef7f5..e4dc951 100644 --- a/inlining_test.go +++ b/inlining_test.go @@ -84,7 +84,6 @@ func TestInlining(t *testing.T) { "IPPrefix.Bits", "IPRange.Prefixes", "IPRange.prefixFrom128AndBits", - "IPRange.prefixFrom128AndBits-fm", "IPRange.entirelyBefore", "IPRangeFrom", "IPRange.To", From 502d2d6903173747e2c269bf9534f1c8d0f5a2f5 Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Thu, 11 Aug 2022 16:20:34 -0400 Subject: [PATCH 72/73] README: deprecation notice Signed-off-by: Matt Layher --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 65dd708..1fdaee5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # netaddr [![Test Status](https://github.com/inetaf/netaddr/workflows/Linux/badge.svg)](https://github.com/inetaf/netaddr/actions) [![Go Reference](https://pkg.go.dev/badge/inet.af/netaddr.svg)](https://pkg.go.dev/inet.af/netaddr) +## Deprecated + +Please see https://pkg.go.dev/go4.org/netipx and the standard library's +[`net/netip`](https://pkg.go.dev/net/netip). + ## What This is a package containing a new IP address type for Go. From b8eac61e914ae50552e9783d406537a85f7f6d1d Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 25 May 2023 11:43:11 -0700 Subject: [PATCH 73/73] go.mod: bump go4.org/unsafe/assume-no-moving-gc Signed-off-by: Brad Fitzpatrick --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8f2347e..747288f 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,6 @@ go 1.12 require ( github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 go4.org/intern v0.0.0-20211027215823-ae77deb06f29 - go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect + go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect golang.org/x/tools v0.1.0 // indirect ) diff --git a/go.sum b/go.sum index e035557..8abe346 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 h1:WJhcL4p+YeDxmZWg141nRm7XC8IDmhz7lk5GpadO1Sg= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=