mirror of
https://github.com/autc04/Retro68.git
synced 2024-07-11 11:29:05 +00:00
1975 lines
48 KiB
Go
1975 lines
48 KiB
Go
// Copyright 2020 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package netip_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"internal/intern"
|
|
"net"
|
|
. "net/netip"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
var long = flag.Bool("long", false, "run long tests")
|
|
|
|
type uint128 = Uint128
|
|
|
|
var (
|
|
mustPrefix = MustParsePrefix
|
|
mustIP = MustParseAddr
|
|
mustIPPort = MustParseAddrPort
|
|
)
|
|
|
|
func TestParseAddr(t *testing.T) {
|
|
var validIPs = []struct {
|
|
in string
|
|
ip Addr // output of ParseAddr()
|
|
str string // output of String(). If "", use in.
|
|
wantErr string
|
|
}{
|
|
// Basic zero IPv4 address.
|
|
{
|
|
in: "0.0.0.0",
|
|
ip: MkAddr(Mk128(0, 0xffff00000000), Z4),
|
|
},
|
|
// Basic non-zero IPv4 address.
|
|
{
|
|
in: "192.168.140.255",
|
|
ip: MkAddr(Mk128(0, 0xffffc0a88cff), Z4),
|
|
},
|
|
// IPv4 address in windows-style "print all the digits" form.
|
|
{
|
|
in: "010.000.015.001",
|
|
wantErr: `ParseAddr("010.000.015.001"): IPv4 field has octet with leading zero`,
|
|
},
|
|
// IPv4 address with a silly amount of leading zeros.
|
|
{
|
|
in: "000001.00000002.00000003.000000004",
|
|
wantErr: `ParseAddr("000001.00000002.00000003.000000004"): IPv4 field has octet with leading zero`,
|
|
},
|
|
// 4-in-6 with octet with leading zero
|
|
{
|
|
in: "::ffff:1.2.03.4",
|
|
wantErr: `ParseAddr("::ffff:1.2.03.4"): ParseAddr("1.2.03.4"): IPv4 field has octet with leading zero (at "1.2.03.4")`,
|
|
},
|
|
// Basic zero IPv6 address.
|
|
{
|
|
in: "::",
|
|
ip: MkAddr(Mk128(0, 0), Z6noz),
|
|
},
|
|
// Localhost IPv6.
|
|
{
|
|
in: "::1",
|
|
ip: MkAddr(Mk128(0, 1), Z6noz),
|
|
},
|
|
// Fully expanded IPv6 address.
|
|
{
|
|
in: "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b",
|
|
ip: MkAddr(Mk128(0xfd7a115ca1e0ab12, 0x4843cd96626b430b), Z6noz),
|
|
},
|
|
// IPv6 with elided fields in the middle.
|
|
{
|
|
in: "fd7a:115c::626b:430b",
|
|
ip: MkAddr(Mk128(0xfd7a115c00000000, 0x00000000626b430b), Z6noz),
|
|
},
|
|
// IPv6 with elided fields at the end.
|
|
{
|
|
in: "fd7a:115c:a1e0:ab12:4843:cd96::",
|
|
ip: MkAddr(Mk128(0xfd7a115ca1e0ab12, 0x4843cd9600000000), Z6noz),
|
|
},
|
|
// IPv6 with single elided field at the end.
|
|
{
|
|
in: "fd7a:115c:a1e0:ab12:4843:cd96:626b::",
|
|
ip: MkAddr(Mk128(0xfd7a115ca1e0ab12, 0x4843cd96626b0000), Z6noz),
|
|
str: "fd7a:115c:a1e0:ab12:4843:cd96:626b:0",
|
|
},
|
|
// IPv6 with single elided field in the middle.
|
|
{
|
|
in: "fd7a:115c:a1e0::4843:cd96:626b:430b",
|
|
ip: MkAddr(Mk128(0xfd7a115ca1e00000, 0x4843cd96626b430b), Z6noz),
|
|
str: "fd7a:115c:a1e0:0:4843:cd96:626b:430b",
|
|
},
|
|
// IPv6 with the trailing 32 bits written as IPv4 dotted decimal. (4in6)
|
|
{
|
|
in: "::ffff:192.168.140.255",
|
|
ip: MkAddr(Mk128(0, 0x0000ffffc0a88cff), Z6noz),
|
|
str: "::ffff:192.168.140.255",
|
|
},
|
|
// IPv6 with a zone specifier.
|
|
{
|
|
in: "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b%eth0",
|
|
ip: MkAddr(Mk128(0xfd7a115ca1e0ab12, 0x4843cd96626b430b), intern.Get("eth0")),
|
|
},
|
|
// IPv6 with dotted decimal and zone specifier.
|
|
{
|
|
in: "1:2::ffff:192.168.140.255%eth1",
|
|
ip: MkAddr(Mk128(0x0001000200000000, 0x0000ffffc0a88cff), intern.Get("eth1")),
|
|
str: "1:2::ffff:c0a8:8cff%eth1",
|
|
},
|
|
// 4-in-6 with zone
|
|
{
|
|
in: "::ffff:192.168.140.255%eth1",
|
|
ip: MkAddr(Mk128(0, 0x0000ffffc0a88cff), intern.Get("eth1")),
|
|
str: "::ffff:192.168.140.255%eth1",
|
|
},
|
|
// IPv6 with capital letters.
|
|
{
|
|
in: "FD9E:1A04:F01D::1",
|
|
ip: MkAddr(Mk128(0xfd9e1a04f01d0000, 0x1), Z6noz),
|
|
str: "fd9e:1a04:f01d::1",
|
|
},
|
|
}
|
|
|
|
for _, test := range validIPs {
|
|
t.Run(test.in, func(t *testing.T) {
|
|
got, err := ParseAddr(test.in)
|
|
if err != nil {
|
|
if err.Error() == test.wantErr {
|
|
return
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
if test.wantErr != "" {
|
|
t.Fatalf("wanted error %q; got none", test.wantErr)
|
|
}
|
|
if got != test.ip {
|
|
t.Errorf("got %#v, want %#v", got, test.ip)
|
|
}
|
|
|
|
// Check that ParseAddr is a pure function.
|
|
got2, err := ParseAddr(test.in)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != got2 {
|
|
t.Errorf("ParseAddr(%q) got 2 different results: %#v, %#v", test.in, got, got2)
|
|
}
|
|
|
|
// Check that ParseAddr(ip.String()) is the identity function.
|
|
s := got.String()
|
|
got3, err := ParseAddr(s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != got3 {
|
|
t.Errorf("ParseAddr(%q) != ParseAddr(ParseIP(%q).String()). Got %#v, want %#v", test.in, test.in, got3, got)
|
|
}
|
|
|
|
// Check that the slow-but-readable parser produces the same result.
|
|
slow, err := parseIPSlow(test.in)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != slow {
|
|
t.Errorf("ParseAddr(%q) = %#v, parseIPSlow(%q) = %#v", test.in, got, test.in, slow)
|
|
}
|
|
|
|
// Check that the parsed IP formats as expected.
|
|
s = got.String()
|
|
wants := test.str
|
|
if wants == "" {
|
|
wants = test.in
|
|
}
|
|
if s != wants {
|
|
t.Errorf("ParseAddr(%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
|
|
// ParseAddr/String (see TestIPMarshalUnmarshal for
|
|
// marshal-specific behavior that's not common with
|
|
// ParseAddr/String).
|
|
js := `"` + test.in + `"`
|
|
var jsgot Addr
|
|
if err := json.Unmarshal([]byte(js), &jsgot); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if jsgot != got {
|
|
t.Errorf("json.Unmarshal(%q) = %#v, want %#v", test.in, jsgot, got)
|
|
}
|
|
jsb, err := json.Marshal(jsgot)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
jswant := `"` + wants + `"`
|
|
jsback := string(jsb)
|
|
if jsback != jswant {
|
|
t.Errorf("Marshal(Unmarshal(%q)) = %s, want %s", test.in, jsback, jswant)
|
|
}
|
|
})
|
|
}
|
|
|
|
var invalidIPs = []string{
|
|
// Empty string
|
|
"",
|
|
// Garbage non-IP
|
|
"bad",
|
|
// Single number. Some parsers accept this as an IPv4 address in
|
|
// big-endian uint32 form, but we don't.
|
|
"1234",
|
|
// IPv4 with a zone specifier
|
|
"1.2.3.4%eth0",
|
|
// IPv4 field must have at least one digit
|
|
".1.2.3",
|
|
"1.2.3.",
|
|
"1..2.3",
|
|
// IPv4 address too long
|
|
"1.2.3.4.5",
|
|
// IPv4 in dotted octal form
|
|
"0300.0250.0214.0377",
|
|
// IPv4 in dotted hex form
|
|
"0xc0.0xa8.0x8c.0xff",
|
|
// IPv4 in class B form
|
|
"192.168.12345",
|
|
// IPv4 in class B form, with a small enough number to be
|
|
// parseable as a regular dotted decimal field.
|
|
"127.0.1",
|
|
// IPv4 in class A form
|
|
"192.1234567",
|
|
// IPv4 in class A form, with a small enough number to be
|
|
// parseable as a regular dotted decimal field.
|
|
"127.1",
|
|
// IPv4 field has value >255
|
|
"192.168.300.1",
|
|
// IPv4 with too many fields
|
|
"192.168.0.1.5.6",
|
|
// IPv6 with not enough fields
|
|
"1:2:3:4:5:6:7",
|
|
// IPv6 with too many fields
|
|
"1:2:3:4:5:6:7:8:9",
|
|
// IPv6 with 8 fields and a :: expander
|
|
"1:2:3:4::5:6:7:8",
|
|
// IPv6 with a field bigger than 2b
|
|
"fe801::1",
|
|
// IPv6 with non-hex values in field
|
|
"fe80:tail:scal:e::",
|
|
// IPv6 with a zone delimiter but no zone.
|
|
"fe80::1%",
|
|
// IPv6 (without ellipsis) with too many fields for trailing embedded IPv4.
|
|
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:192.168.140.255",
|
|
// IPv6 (with ellipsis) with too many fields for trailing embedded IPv4.
|
|
"ffff::ffff:ffff:ffff:ffff:ffff:ffff:192.168.140.255",
|
|
// IPv6 with invalid embedded IPv4.
|
|
"::ffff:192.168.140.bad",
|
|
// IPv6 with multiple ellipsis ::.
|
|
"fe80::1::1",
|
|
// IPv6 with invalid non hex/colon character.
|
|
"fe80:1?:1",
|
|
// IPv6 with truncated bytes after single colon.
|
|
"fe80:",
|
|
}
|
|
|
|
for _, s := range invalidIPs {
|
|
t.Run(s, func(t *testing.T) {
|
|
got, err := ParseAddr(s)
|
|
if err == nil {
|
|
t.Errorf("ParseAddr(%q) = %#v, want error", s, got)
|
|
}
|
|
|
|
slow, err := parseIPSlow(s)
|
|
if err == nil {
|
|
t.Errorf("parseIPSlow(%q) = %#v, want error", s, slow)
|
|
}
|
|
|
|
std := net.ParseIP(s)
|
|
if std != nil {
|
|
t.Errorf("net.ParseIP(%q) = %#v, want error", s, std)
|
|
}
|
|
|
|
if s == "" {
|
|
// Don't test unmarshaling of "" here, do it in
|
|
// IPMarshalUnmarshal.
|
|
return
|
|
}
|
|
var jsgot Addr
|
|
js := []byte(`"` + s + `"`)
|
|
if err := json.Unmarshal(js, &jsgot); err == nil {
|
|
t.Errorf("json.Unmarshal(%q) = %#v, want error", s, jsgot)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIPv4Constructors(t *testing.T) {
|
|
if AddrFrom4([4]byte{1, 2, 3, 4}) != MustParseAddr("1.2.3.4") {
|
|
t.Errorf("don't match")
|
|
}
|
|
}
|
|
|
|
func TestAddrMarshalUnmarshalBinary(t *testing.T) {
|
|
tests := []struct {
|
|
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 {
|
|
var ip Addr
|
|
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("%q encoded to size %d; want %d", tc.ip, len(b), tc.wantSize)
|
|
}
|
|
var ip2 Addr
|
|
if err := ip2.UnmarshalBinary(b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ip != ip2 {
|
|
t.Fatalf("got %v; want %v", ip2, ip)
|
|
}
|
|
}
|
|
|
|
// Cannot unmarshal from unexpected IP length.
|
|
for _, n := range []int{3, 5} {
|
|
var ip2 Addr
|
|
if err := ip2.UnmarshalBinary(bytes.Repeat([]byte{1}, n)); err == nil {
|
|
t.Fatalf("unmarshaled from unexpected IP length %d", n)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAddrPortMarshalTextString(t *testing.T) {
|
|
tests := []struct {
|
|
in AddrPort
|
|
want string
|
|
}{
|
|
{mustIPPort("1.2.3.4:80"), "1.2.3.4:80"},
|
|
{mustIPPort("[1::CAFE]:80"), "[1::cafe]:80"},
|
|
{mustIPPort("[1::CAFE%en0]:80"), "[1::cafe%en0]:80"},
|
|
{mustIPPort("[::FFFF:192.168.140.255]:80"), "[::ffff:192.168.140.255]:80"},
|
|
{mustIPPort("[::FFFF:192.168.140.255%en0]:80"), "[::ffff:192.168.140.255%en0]:80"},
|
|
}
|
|
for i, tt := range tests {
|
|
if got := tt.in.String(); got != tt.want {
|
|
t.Errorf("%d. for (%v, %v) String = %q; want %q", i, tt.in.Addr(), tt.in.Port(), got, tt.want)
|
|
}
|
|
mt, err := tt.in.MarshalText()
|
|
if err != nil {
|
|
t.Errorf("%d. for (%v, %v) MarshalText error: %v", i, tt.in.Addr(), tt.in.Port(), err)
|
|
continue
|
|
}
|
|
if string(mt) != tt.want {
|
|
t.Errorf("%d. for (%v, %v) MarshalText = %q; want %q", i, tt.in.Addr(), tt.in.Port(), mt, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAddrPortMarshalUnmarshalBinary(t *testing.T) {
|
|
tests := []struct {
|
|
ipport string
|
|
wantSize int
|
|
}{
|
|
{"1.2.3.4:51820", 4 + 2},
|
|
{"[fd7a:115c:a1e0:ab12:4843:cd96:626b:430b]:80", 16 + 2},
|
|
{"[::ffff:c000:0280]:65535", 16 + 2},
|
|
{"[::ffff:c000:0280%eth0]:1", 20 + 2},
|
|
}
|
|
for _, tc := range tests {
|
|
var ipport AddrPort
|
|
if len(tc.ipport) > 0 {
|
|
ipport = mustIPPort(tc.ipport)
|
|
}
|
|
b, err := ipport.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(b) != tc.wantSize {
|
|
t.Fatalf("%q encoded to size %d; want %d", tc.ipport, len(b), tc.wantSize)
|
|
}
|
|
var ipport2 AddrPort
|
|
if err := ipport2.UnmarshalBinary(b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ipport != ipport2 {
|
|
t.Fatalf("got %v; want %v", ipport2, ipport)
|
|
}
|
|
}
|
|
|
|
// Cannot unmarshal from unexpected lengths.
|
|
for _, n := range []int{3, 7} {
|
|
var ipport2 AddrPort
|
|
if err := ipport2.UnmarshalBinary(bytes.Repeat([]byte{1}, n)); err == nil {
|
|
t.Fatalf("unmarshaled from unexpected length %d", n)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPrefixMarshalTextString(t *testing.T) {
|
|
tests := []struct {
|
|
in Prefix
|
|
want string
|
|
}{
|
|
{mustPrefix("1.2.3.4/24"), "1.2.3.4/24"},
|
|
{mustPrefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118"), "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118"},
|
|
{mustPrefix("::ffff:c000:0280/96"), "::ffff:192.0.2.128/96"},
|
|
{mustPrefix("::ffff:c000:0280%eth0/37"), "::ffff:192.0.2.128/37"}, // Zone should be stripped
|
|
{mustPrefix("::ffff:192.168.140.255/8"), "::ffff:192.168.140.255/8"},
|
|
}
|
|
for i, tt := range tests {
|
|
if got := tt.in.String(); got != tt.want {
|
|
t.Errorf("%d. for %v String = %q; want %q", i, tt.in, got, tt.want)
|
|
}
|
|
mt, err := tt.in.MarshalText()
|
|
if err != nil {
|
|
t.Errorf("%d. for %v MarshalText error: %v", i, tt.in, err)
|
|
continue
|
|
}
|
|
if string(mt) != tt.want {
|
|
t.Errorf("%d. for %v MarshalText = %q; want %q", i, tt.in, mt, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPrefixMarshalUnmarshalBinary(t *testing.T) {
|
|
type testCase struct {
|
|
prefix Prefix
|
|
wantSize int
|
|
}
|
|
tests := []testCase{
|
|
{mustPrefix("1.2.3.4/24"), 4 + 1},
|
|
{mustPrefix("fd7a:115c:a1e0:ab12:4843:cd96:626b:430b/118"), 16 + 1},
|
|
{mustPrefix("::ffff:c000:0280/96"), 16 + 1},
|
|
{mustPrefix("::ffff:c000:0280%eth0/37"), 16 + 1}, // Zone should be stripped
|
|
}
|
|
tests = append(tests,
|
|
testCase{PrefixFrom(tests[0].prefix.Addr(), 33), tests[0].wantSize},
|
|
testCase{PrefixFrom(tests[1].prefix.Addr(), 129), tests[1].wantSize})
|
|
for _, tc := range tests {
|
|
prefix := tc.prefix
|
|
b, err := prefix.MarshalBinary()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(b) != tc.wantSize {
|
|
t.Fatalf("%q encoded to size %d; want %d", tc.prefix, len(b), tc.wantSize)
|
|
}
|
|
var prefix2 Prefix
|
|
if err := prefix2.UnmarshalBinary(b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if prefix != prefix2 {
|
|
t.Fatalf("got %v; want %v", prefix2, prefix)
|
|
}
|
|
}
|
|
|
|
// Cannot unmarshal from unexpected lengths.
|
|
for _, n := range []int{3, 6} {
|
|
var prefix2 Prefix
|
|
if err := prefix2.UnmarshalBinary(bytes.Repeat([]byte{1}, n)); err == nil {
|
|
t.Fatalf("unmarshaled from unexpected length %d", n)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAddrMarshalUnmarshal(t *testing.T) {
|
|
// This only tests the cases where Marshal/Unmarshal diverges from
|
|
// the behavior of ParseAddr/String. For the rest of the test cases,
|
|
// see TestParseAddr above.
|
|
orig := `""`
|
|
var ip Addr
|
|
if err := json.Unmarshal([]byte(orig), &ip); err != nil {
|
|
t.Fatalf("Unmarshal(%q) got error %v", orig, err)
|
|
}
|
|
if ip != (Addr{}) {
|
|
t.Errorf("Unmarshal(%q) is not the zero Addr", orig)
|
|
}
|
|
|
|
jsb, err := json.Marshal(ip)
|
|
if err != nil {
|
|
t.Fatalf("Marshal(%v) got error %v", ip, err)
|
|
}
|
|
back := string(jsb)
|
|
if back != orig {
|
|
t.Errorf("Marshal(Unmarshal(%q)) got %q, want %q", orig, back, orig)
|
|
}
|
|
}
|
|
|
|
func TestAddrFrom16(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in [16]byte
|
|
want Addr
|
|
}{
|
|
{
|
|
name: "v6-raw",
|
|
in: [...]byte{15: 1},
|
|
want: MkAddr(Mk128(0, 1), Z6noz),
|
|
},
|
|
{
|
|
name: "v4-raw",
|
|
in: [...]byte{10: 0xff, 11: 0xff, 12: 1, 13: 2, 14: 3, 15: 4},
|
|
want: MkAddr(Mk128(0, 0xffff01020304), Z6noz),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := AddrFrom16(tt.in)
|
|
if got != tt.want {
|
|
t.Errorf("got %#v; want %#v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIPProperties(t *testing.T) {
|
|
var (
|
|
nilIP Addr
|
|
|
|
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")
|
|
multicastZone6 = mustIP("ff02::1%eth0")
|
|
|
|
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")
|
|
loopback6 = mustIP("::1")
|
|
|
|
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 = AddrFrom4([4]byte{})
|
|
unspecified6 = IPv6Unspecified()
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
ip Addr
|
|
globalUnicast bool
|
|
interfaceLocalMulticast bool
|
|
linkLocalMulticast bool
|
|
linkLocalUnicast bool
|
|
loopback bool
|
|
multicast bool
|
|
private bool
|
|
unspecified bool
|
|
}{
|
|
{
|
|
name: "nil",
|
|
ip: nilIP,
|
|
},
|
|
{
|
|
name: "unicast v4Addr",
|
|
ip: unicast4,
|
|
globalUnicast: true,
|
|
},
|
|
{
|
|
name: "unicast v6Addr",
|
|
ip: unicast6,
|
|
globalUnicast: true,
|
|
},
|
|
{
|
|
name: "unicast v6AddrZone",
|
|
ip: unicastZone6,
|
|
globalUnicast: true,
|
|
},
|
|
{
|
|
name: "unicast v6Addr unassigned",
|
|
ip: unicast6Unassigned,
|
|
globalUnicast: true,
|
|
},
|
|
{
|
|
name: "multicast v4Addr",
|
|
ip: multicast4,
|
|
linkLocalMulticast: true,
|
|
multicast: true,
|
|
},
|
|
{
|
|
name: "multicast v6Addr",
|
|
ip: multicast6,
|
|
linkLocalMulticast: true,
|
|
multicast: true,
|
|
},
|
|
{
|
|
name: "multicast v6AddrZone",
|
|
ip: multicastZone6,
|
|
linkLocalMulticast: true,
|
|
multicast: true,
|
|
},
|
|
{
|
|
name: "link-local unicast v4Addr",
|
|
ip: llu4,
|
|
linkLocalUnicast: true,
|
|
},
|
|
{
|
|
name: "link-local unicast v6Addr",
|
|
ip: llu6,
|
|
linkLocalUnicast: true,
|
|
},
|
|
{
|
|
name: "link-local unicast v6Addr upper bound",
|
|
ip: llu6Last,
|
|
linkLocalUnicast: true,
|
|
},
|
|
{
|
|
name: "link-local unicast v6AddrZone",
|
|
ip: lluZone6,
|
|
linkLocalUnicast: true,
|
|
},
|
|
{
|
|
name: "loopback v4Addr",
|
|
ip: loopback4,
|
|
loopback: true,
|
|
},
|
|
{
|
|
name: "loopback v6Addr",
|
|
ip: loopback6,
|
|
loopback: true,
|
|
},
|
|
{
|
|
name: "interface-local multicast v6Addr",
|
|
ip: ilm6,
|
|
interfaceLocalMulticast: true,
|
|
multicast: true,
|
|
},
|
|
{
|
|
name: "interface-local multicast v6AddrZone",
|
|
ip: ilmZone6,
|
|
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,
|
|
unspecified: true,
|
|
},
|
|
{
|
|
name: "unspecified v6Addr",
|
|
ip: unspecified6,
|
|
unspecified: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
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()
|
|
if llu != tt.linkLocalUnicast {
|
|
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)
|
|
}
|
|
|
|
multicast := tt.ip.IsMulticast()
|
|
if multicast != tt.multicast {
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddrWellKnown(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ip Addr
|
|
std net.IP
|
|
}{
|
|
{
|
|
name: "IPv6 link-local all nodes",
|
|
ip: IPv6LinkLocalAllNodes(),
|
|
std: net.IPv6linklocalallnodes,
|
|
},
|
|
{
|
|
name: "IPv6 unspecified",
|
|
ip: IPv6Unspecified(),
|
|
std: net.IPv6unspecified,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
want := tt.std.String()
|
|
got := tt.ip.String()
|
|
|
|
if got != want {
|
|
t.Fatalf("got %s, want %s", got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLessCompare(t *testing.T) {
|
|
tests := []struct {
|
|
a, b Addr
|
|
want bool
|
|
}{
|
|
{Addr{}, Addr{}, false},
|
|
{Addr{}, mustIP("1.2.3.4"), true},
|
|
{mustIP("1.2.3.4"), Addr{}, false},
|
|
|
|
{mustIP("1.2.3.4"), mustIP("0102:0304::0"), true},
|
|
{mustIP("0102:0304::0"), mustIP("1.2.3.4"), false},
|
|
{mustIP("1.2.3.4"), mustIP("1.2.3.4"), false},
|
|
|
|
{mustIP("::1"), mustIP("::2"), true},
|
|
{mustIP("::1"), mustIP("::1%foo"), true},
|
|
{mustIP("::1%foo"), mustIP("::2"), true},
|
|
{mustIP("::2"), mustIP("::3"), true},
|
|
|
|
{mustIP("::"), mustIP("0.0.0.0"), false},
|
|
{mustIP("0.0.0.0"), mustIP("::"), true},
|
|
|
|
{mustIP("::1%a"), mustIP("::1%b"), true},
|
|
{mustIP("::1%a"), mustIP("::1%a"), false},
|
|
{mustIP("::1%b"), mustIP("::1%a"), false},
|
|
}
|
|
for _, tt := range tests {
|
|
got := tt.a.Less(tt.b)
|
|
if got != tt.want {
|
|
t.Errorf("Less(%q, %q) = %v; want %v", tt.a, tt.b, got, tt.want)
|
|
}
|
|
cmp := tt.a.Compare(tt.b)
|
|
if got && cmp != -1 {
|
|
t.Errorf("Less(%q, %q) = true, but Compare = %v (not -1)", tt.a, tt.b, cmp)
|
|
}
|
|
if cmp < -1 || cmp > 1 {
|
|
t.Errorf("bogus Compare return value %v", cmp)
|
|
}
|
|
if cmp == 0 && tt.a != tt.b {
|
|
t.Errorf("Compare(%q, %q) = 0; but not equal", tt.a, tt.b)
|
|
}
|
|
if cmp == 1 && !tt.b.Less(tt.a) {
|
|
t.Errorf("Compare(%q, %q) = 1; but b.Less(a) isn't true", tt.a, tt.b)
|
|
}
|
|
|
|
// Also check inverse.
|
|
if got == tt.want && got {
|
|
got2 := tt.b.Less(tt.a)
|
|
if got2 {
|
|
t.Errorf("Less(%q, %q) was correctly %v, but so was Less(%q, %q)", tt.a, tt.b, got, tt.b, tt.a)
|
|
}
|
|
}
|
|
}
|
|
|
|
// And just sort.
|
|
values := []Addr{
|
|
mustIP("::1"),
|
|
mustIP("::2"),
|
|
Addr{},
|
|
mustIP("1.2.3.4"),
|
|
mustIP("8.8.8.8"),
|
|
mustIP("::1%foo"),
|
|
}
|
|
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]`
|
|
if got != want {
|
|
t.Errorf("unexpected sort\n got: %s\nwant: %s\n", got, want)
|
|
}
|
|
}
|
|
|
|
func TestIPStringExpanded(t *testing.T) {
|
|
tests := []struct {
|
|
ip Addr
|
|
s string
|
|
}{
|
|
{
|
|
ip: Addr{},
|
|
s: "invalid IP",
|
|
},
|
|
{
|
|
ip: mustIP("192.0.2.1"),
|
|
s: "192.0.2.1",
|
|
},
|
|
{
|
|
ip: mustIP("::ffff:192.0.2.1"),
|
|
s: "0000:0000:0000:0000:0000:ffff:c000:0201",
|
|
},
|
|
{
|
|
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 TestPrefixMasking(t *testing.T) {
|
|
type subtest struct {
|
|
ip Addr
|
|
bits uint8
|
|
p Prefix
|
|
ok bool
|
|
}
|
|
|
|
// makeIPv6 produces a set of IPv6 subtests with an optional zone identifier.
|
|
makeIPv6 := func(zone string) []subtest {
|
|
if zone != "" {
|
|
zone = "%" + zone
|
|
}
|
|
|
|
return []subtest{
|
|
{
|
|
ip: mustIP(fmt.Sprintf("2001:db8::1%s", zone)),
|
|
bits: 255,
|
|
},
|
|
{
|
|
ip: mustIP(fmt.Sprintf("2001:db8::1%s", zone)),
|
|
bits: 32,
|
|
p: mustPrefix(fmt.Sprintf("2001:db8::%s/32", zone)),
|
|
ok: true,
|
|
},
|
|
{
|
|
ip: mustIP(fmt.Sprintf("fe80::dead:beef:dead:beef%s", zone)),
|
|
bits: 96,
|
|
p: mustPrefix(fmt.Sprintf("fe80::dead:beef:0:0%s/96", zone)),
|
|
ok: true,
|
|
},
|
|
{
|
|
ip: mustIP(fmt.Sprintf("aaaa::%s", zone)),
|
|
bits: 4,
|
|
p: mustPrefix(fmt.Sprintf("a000::%s/4", zone)),
|
|
ok: true,
|
|
},
|
|
{
|
|
ip: mustIP(fmt.Sprintf("::%s", zone)),
|
|
bits: 63,
|
|
p: mustPrefix(fmt.Sprintf("::%s/63", zone)),
|
|
ok: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
tests := []struct {
|
|
family string
|
|
subtests []subtest
|
|
}{
|
|
{
|
|
family: "nil",
|
|
subtests: []subtest{
|
|
{
|
|
bits: 255,
|
|
ok: true,
|
|
},
|
|
{
|
|
bits: 16,
|
|
ok: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
family: "IPv4",
|
|
subtests: []subtest{
|
|
{
|
|
ip: mustIP("192.0.2.0"),
|
|
bits: 255,
|
|
},
|
|
{
|
|
ip: mustIP("192.0.2.0"),
|
|
bits: 16,
|
|
p: mustPrefix("192.0.0.0/16"),
|
|
ok: true,
|
|
},
|
|
{
|
|
ip: mustIP("255.255.255.255"),
|
|
bits: 20,
|
|
p: mustPrefix("255.255.240.0/20"),
|
|
ok: true,
|
|
},
|
|
{
|
|
// Partially masking one byte that contains both
|
|
// 1s and 0s on either side of the mask limit.
|
|
ip: mustIP("100.98.156.66"),
|
|
bits: 10,
|
|
p: mustPrefix("100.64.0.0/10"),
|
|
ok: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
family: "IPv6",
|
|
subtests: makeIPv6(""),
|
|
},
|
|
{
|
|
family: "IPv6 zone",
|
|
subtests: makeIPv6("eth0"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.family, func(t *testing.T) {
|
|
for _, st := range tt.subtests {
|
|
t.Run(st.p.String(), func(t *testing.T) {
|
|
// Ensure st.ip is not mutated.
|
|
orig := st.ip.String()
|
|
|
|
p, err := st.ip.Prefix(int(st.bits))
|
|
if st.ok && err != nil {
|
|
t.Fatalf("failed to produce prefix: %v", err)
|
|
}
|
|
if !st.ok && err == nil {
|
|
t.Fatal("expected an error, but none occurred")
|
|
}
|
|
if err != nil {
|
|
t.Logf("err: %v", err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(p, st.p) {
|
|
t.Errorf("prefix = %q, want %q", p, st.p)
|
|
}
|
|
|
|
if got := st.ip.String(); got != orig {
|
|
t.Errorf("IP was mutated: %q, want %q", got, orig)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrefixMarshalUnmarshal(t *testing.T) {
|
|
tests := []string{
|
|
"",
|
|
"1.2.3.4/32",
|
|
"0.0.0.0/0",
|
|
"::/0",
|
|
"::1/128",
|
|
"2001:db8::/32",
|
|
}
|
|
|
|
for _, s := range tests {
|
|
t.Run(s, func(t *testing.T) {
|
|
// Ensure that JSON (and by extension, text) marshaling is
|
|
// sane by entering quoted input.
|
|
orig := `"` + s + `"`
|
|
|
|
var p Prefix
|
|
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 orig != back {
|
|
t.Errorf("Marshal = %q; want %q", back, orig)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrefixMarshalUnmarshalZone(t *testing.T) {
|
|
orig := `"fe80::1cc0:3e8c:119f:c2e1%ens18/128"`
|
|
unzoned := `"fe80::1cc0:3e8c:119f:c2e1/128"`
|
|
|
|
var p Prefix
|
|
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 TestPrefixUnmarshalTextNonZero(t *testing.T) {
|
|
ip := mustPrefix("fe80::/64")
|
|
if err := ip.UnmarshalText([]byte("xxx")); err == nil {
|
|
t.Fatal("unmarshaled into non-empty Prefix")
|
|
}
|
|
}
|
|
|
|
func TestIs4AndIs6(t *testing.T) {
|
|
tests := []struct {
|
|
ip Addr
|
|
is4 bool
|
|
is6 bool
|
|
}{
|
|
{Addr{}, false, false},
|
|
{mustIP("1.2.3.4"), true, false},
|
|
{mustIP("127.0.0.2"), true, false},
|
|
{mustIP("::1"), false, true},
|
|
{mustIP("::ffff:192.0.2.128"), false, true},
|
|
{mustIP("::fffe:c000:0280"), false, true},
|
|
{mustIP("::1%eth0"), false, true},
|
|
}
|
|
for _, tt := range tests {
|
|
got4 := tt.ip.Is4()
|
|
if got4 != tt.is4 {
|
|
t.Errorf("Is4(%q) = %v; want %v", tt.ip, got4, tt.is4)
|
|
}
|
|
|
|
got6 := tt.ip.Is6()
|
|
if got6 != tt.is6 {
|
|
t.Errorf("Is6(%q) = %v; want %v", tt.ip, got6, tt.is6)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIs4In6(t *testing.T) {
|
|
tests := []struct {
|
|
ip Addr
|
|
want bool
|
|
wantUnmap Addr
|
|
}{
|
|
{Addr{}, false, Addr{}},
|
|
{mustIP("::ffff:c000:0280"), true, mustIP("192.0.2.128")},
|
|
{mustIP("::ffff:192.0.2.128"), true, mustIP("192.0.2.128")},
|
|
{mustIP("::ffff:192.0.2.128%eth0"), true, mustIP("192.0.2.128")},
|
|
{mustIP("::fffe:c000:0280"), false, mustIP("::fffe:c000:0280")},
|
|
{mustIP("::ffff:127.1.2.3"), true, mustIP("127.1.2.3")},
|
|
{mustIP("::ffff:7f01:0203"), true, mustIP("127.1.2.3")},
|
|
{mustIP("0:0:0:0:0000:ffff:127.1.2.3"), true, mustIP("127.1.2.3")},
|
|
{mustIP("0:0:0:0:000000:ffff:127.1.2.3"), true, mustIP("127.1.2.3")},
|
|
{mustIP("0:0:0:0::ffff:127.1.2.3"), true, mustIP("127.1.2.3")},
|
|
{mustIP("::1"), false, mustIP("::1")},
|
|
{mustIP("1.2.3.4"), false, mustIP("1.2.3.4")},
|
|
}
|
|
for _, tt := range tests {
|
|
got := tt.ip.Is4In6()
|
|
if got != tt.want {
|
|
t.Errorf("Is4In6(%q) = %v; want %v", tt.ip, got, tt.want)
|
|
}
|
|
u := tt.ip.Unmap()
|
|
if u != tt.wantUnmap {
|
|
t.Errorf("Unmap(%q) = %v; want %v", tt.ip, u, tt.wantUnmap)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPrefixMasked(t *testing.T) {
|
|
tests := []struct {
|
|
prefix Prefix
|
|
masked Prefix
|
|
}{
|
|
{
|
|
prefix: mustPrefix("192.168.0.255/24"),
|
|
masked: mustPrefix("192.168.0.0/24"),
|
|
},
|
|
{
|
|
prefix: mustPrefix("2100::/3"),
|
|
masked: mustPrefix("2000::/3"),
|
|
},
|
|
{
|
|
prefix: PrefixFrom(mustIP("2000::"), 129),
|
|
masked: Prefix{},
|
|
},
|
|
{
|
|
prefix: PrefixFrom(mustIP("1.2.3.4"), 33),
|
|
masked: Prefix{},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.prefix.String(), func(t *testing.T) {
|
|
got := test.prefix.Masked()
|
|
if got != test.masked {
|
|
t.Errorf("Masked=%s, want %s", got, test.masked)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrefix(t *testing.T) {
|
|
tests := []struct {
|
|
prefix string
|
|
ip Addr
|
|
bits int
|
|
str string
|
|
contains []Addr
|
|
notContains []Addr
|
|
}{
|
|
{
|
|
prefix: "192.168.0.0/24",
|
|
ip: mustIP("192.168.0.0"),
|
|
bits: 24,
|
|
contains: mustIPs("192.168.0.1", "192.168.0.55"),
|
|
notContains: mustIPs("192.168.1.1", "1.1.1.1"),
|
|
},
|
|
{
|
|
prefix: "192.168.1.1/32",
|
|
ip: mustIP("192.168.1.1"),
|
|
bits: 32,
|
|
contains: mustIPs("192.168.1.1"),
|
|
notContains: mustIPs("192.168.1.2"),
|
|
},
|
|
{
|
|
prefix: "100.64.0.0/10", // CGNAT range; prefix not multiple of 8
|
|
ip: mustIP("100.64.0.0"),
|
|
bits: 10,
|
|
contains: mustIPs("100.64.0.0", "100.64.0.1", "100.81.251.94", "100.100.100.100", "100.127.255.254", "100.127.255.255"),
|
|
notContains: mustIPs("100.63.255.255", "100.128.0.0"),
|
|
},
|
|
{
|
|
prefix: "2001:db8::/96",
|
|
ip: mustIP("2001:db8::"),
|
|
bits: 96,
|
|
contains: mustIPs("2001:db8::aaaa:bbbb", "2001:db8::1"),
|
|
notContains: mustIPs("2001:db8::1:aaaa:bbbb", "2001:db9::"),
|
|
},
|
|
{
|
|
prefix: "0.0.0.0/0",
|
|
ip: mustIP("0.0.0.0"),
|
|
bits: 0,
|
|
contains: mustIPs("192.168.0.1", "1.1.1.1"),
|
|
notContains: append(mustIPs("2001:db8::1"), Addr{}),
|
|
},
|
|
{
|
|
prefix: "::/0",
|
|
ip: mustIP("::"),
|
|
bits: 0,
|
|
contains: mustIPs("::1", "2001:db8::1"),
|
|
notContains: mustIPs("192.0.2.1"),
|
|
},
|
|
{
|
|
prefix: "2000::/3",
|
|
ip: mustIP("2000::"),
|
|
bits: 3,
|
|
contains: mustIPs("2001:db8::1"),
|
|
notContains: mustIPs("fe80::1"),
|
|
},
|
|
{
|
|
prefix: "::%0/00/80",
|
|
ip: mustIP("::"),
|
|
bits: 80,
|
|
str: "::/80",
|
|
contains: mustIPs("::"),
|
|
notContains: mustIPs("ff::%0/00", "ff::%1/23", "::%0/00", "::%1/23"),
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.prefix, func(t *testing.T) {
|
|
prefix, err := ParsePrefix(test.prefix)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if prefix.Addr() != test.ip {
|
|
t.Errorf("IP=%s, want %s", prefix.Addr(), test.ip)
|
|
}
|
|
if prefix.Bits() != test.bits {
|
|
t.Errorf("bits=%d, want %d", prefix.Bits(), test.bits)
|
|
}
|
|
for _, ip := range test.contains {
|
|
if !prefix.Contains(ip) {
|
|
t.Errorf("does not contain %s", ip)
|
|
}
|
|
}
|
|
for _, ip := range test.notContains {
|
|
if prefix.Contains(ip) {
|
|
t.Errorf("contains %s", ip)
|
|
}
|
|
}
|
|
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)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrefixFromInvalidBits(t *testing.T) {
|
|
v4 := MustParseAddr("1.2.3.4")
|
|
v6 := MustParseAddr("66::66")
|
|
tests := []struct {
|
|
ip Addr
|
|
in, want int
|
|
}{
|
|
{v4, 0, 0},
|
|
{v6, 0, 0},
|
|
{v4, 1, 1},
|
|
{v4, 33, -1},
|
|
{v6, 33, 33},
|
|
{v6, 127, 127},
|
|
{v6, 128, 128},
|
|
{v4, 254, -1},
|
|
{v4, 255, -1},
|
|
{v4, -1, -1},
|
|
{v6, -1, -1},
|
|
{v4, -5, -1},
|
|
{v6, -5, -1},
|
|
}
|
|
for _, tt := range tests {
|
|
p := PrefixFrom(tt.ip, tt.in)
|
|
if got := p.Bits(); got != tt.want {
|
|
t.Errorf("for (%v, %v), Bits out = %v; want %v", tt.ip, tt.in, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParsePrefixAllocs(t *testing.T) {
|
|
tests := []struct {
|
|
ip string
|
|
slash string
|
|
}{
|
|
{"192.168.1.0", "/24"},
|
|
{"aaaa:bbbb:cccc::", "/24"},
|
|
}
|
|
for _, test := range tests {
|
|
prefix := test.ip + test.slash
|
|
t.Run(prefix, func(t *testing.T) {
|
|
ipAllocs := int(testing.AllocsPerRun(5, func() {
|
|
ParseAddr(test.ip)
|
|
}))
|
|
prefixAllocs := int(testing.AllocsPerRun(5, func() {
|
|
ParsePrefix(prefix)
|
|
}))
|
|
if got := prefixAllocs - ipAllocs; got != 0 {
|
|
t.Errorf("allocs=%d, want 0", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsePrefixError(t *testing.T) {
|
|
tests := []struct {
|
|
prefix string
|
|
errstr string
|
|
}{
|
|
{
|
|
prefix: "192.168.0.0",
|
|
errstr: "no '/'",
|
|
},
|
|
{
|
|
prefix: "1.257.1.1/24",
|
|
errstr: "value >255",
|
|
},
|
|
{
|
|
prefix: "1.1.1.0/q",
|
|
errstr: "bad bits",
|
|
},
|
|
{
|
|
prefix: "1.1.1.0/-1",
|
|
errstr: "out of range",
|
|
},
|
|
{
|
|
prefix: "1.1.1.0/33",
|
|
errstr: "out of range",
|
|
},
|
|
{
|
|
prefix: "2001::/129",
|
|
errstr: "out of range",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.prefix, func(t *testing.T) {
|
|
_, err := ParsePrefix(test.prefix)
|
|
if err == nil {
|
|
t.Fatal("no error")
|
|
}
|
|
if got := err.Error(); !strings.Contains(got, test.errstr) {
|
|
t.Errorf("error is missing substring %q: %s", test.errstr, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrefixIsSingleIP(t *testing.T) {
|
|
tests := []struct {
|
|
ipp Prefix
|
|
want bool
|
|
}{
|
|
{ipp: mustPrefix("127.0.0.1/32"), want: true},
|
|
{ipp: mustPrefix("127.0.0.1/31"), want: false},
|
|
{ipp: mustPrefix("127.0.0.1/0"), want: false},
|
|
{ipp: mustPrefix("::1/128"), want: true},
|
|
{ipp: mustPrefix("::1/127"), want: false},
|
|
{ipp: mustPrefix("::1/0"), want: false},
|
|
{ipp: Prefix{}, want: false},
|
|
}
|
|
for _, tt := range tests {
|
|
got := tt.ipp.IsSingleIP()
|
|
if got != tt.want {
|
|
t.Errorf("IsSingleIP(%v) = %v want %v", tt.ipp, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func mustIPs(strs ...string) []Addr {
|
|
var res []Addr
|
|
for _, s := range strs {
|
|
res = append(res, mustIP(s))
|
|
}
|
|
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 Addr
|
|
if err := ip2.UnmarshalBinary(bt); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkStdIPv4(b *testing.B) {
|
|
b.ReportAllocs()
|
|
ips := []net.IP{}
|
|
for i := 0; i < b.N; i++ {
|
|
ip := net.IPv4(8, 8, 8, 8)
|
|
ips = ips[:0]
|
|
for i := 0; i < 100; i++ {
|
|
ips = append(ips, ip)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkIPv4(b *testing.B) {
|
|
b.ReportAllocs()
|
|
ips := []Addr{}
|
|
for i := 0; i < b.N; i++ {
|
|
ip := IPv4(8, 8, 8, 8)
|
|
ips = ips[:0]
|
|
for i := 0; i < 100; i++ {
|
|
ips = append(ips, ip)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ip4i was one of the possible representations of IP that came up in
|
|
// discussions, inlining IPv4 addresses, but having an "overflow"
|
|
// interface for IPv6 or IPv6 + zone. This is here for benchmarking.
|
|
type ip4i struct {
|
|
ip4 [4]byte
|
|
flags1 byte
|
|
flags2 byte
|
|
flags3 byte
|
|
flags4 byte
|
|
ipv6 any
|
|
}
|
|
|
|
func newip4i_v4(a, b, c, d byte) ip4i {
|
|
return ip4i{ip4: [4]byte{a, b, c, d}}
|
|
}
|
|
|
|
// BenchmarkIPv4_inline benchmarks the candidate representation, ip4i.
|
|
func BenchmarkIPv4_inline(b *testing.B) {
|
|
b.ReportAllocs()
|
|
ips := []ip4i{}
|
|
for i := 0; i < b.N; i++ {
|
|
ip := newip4i_v4(8, 8, 8, 8)
|
|
ips = ips[:0]
|
|
for i := 0; i < 100; i++ {
|
|
ips = append(ips, ip)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkStdIPv6(b *testing.B) {
|
|
b.ReportAllocs()
|
|
ips := []net.IP{}
|
|
for i := 0; i < b.N; i++ {
|
|
ip := net.ParseIP("2001:db8::1")
|
|
ips = ips[:0]
|
|
for i := 0; i < 100; i++ {
|
|
ips = append(ips, ip)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkIPv6(b *testing.B) {
|
|
b.ReportAllocs()
|
|
ips := []Addr{}
|
|
for i := 0; i < b.N; i++ {
|
|
ip := mustIP("2001:db8::1")
|
|
ips = ips[:0]
|
|
for i := 0; i < 100; i++ {
|
|
ips = append(ips, ip)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkIPv4Contains(b *testing.B) {
|
|
b.ReportAllocs()
|
|
prefix := PrefixFrom(IPv4(192, 168, 1, 0), 24)
|
|
ip := IPv4(192, 168, 1, 1)
|
|
for i := 0; i < b.N; i++ {
|
|
prefix.Contains(ip)
|
|
}
|
|
}
|
|
|
|
func BenchmarkIPv6Contains(b *testing.B) {
|
|
b.ReportAllocs()
|
|
prefix := MustParsePrefix("::1/128")
|
|
ip := MustParseAddr("::1")
|
|
for i := 0; i < b.N; i++ {
|
|
prefix.Contains(ip)
|
|
}
|
|
}
|
|
|
|
var parseBenchInputs = []struct {
|
|
name string
|
|
ip string
|
|
}{
|
|
{"v4", "192.168.1.1"},
|
|
{"v6", "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b"},
|
|
{"v6_ellipsis", "fd7a:115c::626b:430b"},
|
|
{"v6_v4", "::ffff:192.168.140.255"},
|
|
{"v6_zone", "1:2::ffff:192.168.140.255%eth1"},
|
|
}
|
|
|
|
func BenchmarkParseAddr(b *testing.B) {
|
|
sinkInternValue = intern.Get("eth1") // Pin to not benchmark the intern package
|
|
for _, test := range parseBenchInputs {
|
|
b.Run(test.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
sinkIP, _ = ParseAddr(test.ip)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkStdParseIP(b *testing.B) {
|
|
for _, test := range parseBenchInputs {
|
|
b.Run(test.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
sinkStdIP = net.ParseIP(test.ip)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkIPString(b *testing.B) {
|
|
for _, test := range parseBenchInputs {
|
|
ip := MustParseAddr(test.ip)
|
|
b.Run(test.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
sinkString = ip.String()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkIPStringExpanded(b *testing.B) {
|
|
for _, test := range parseBenchInputs {
|
|
ip := MustParseAddr(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 := MustParseAddr("66.55.44.33")
|
|
for i := 0; i < b.N; i++ {
|
|
sinkBytes, _ = ip.MarshalText()
|
|
}
|
|
}
|
|
|
|
func BenchmarkAddrPortString(b *testing.B) {
|
|
for _, test := range parseBenchInputs {
|
|
ip := MustParseAddr(test.ip)
|
|
ipp := AddrPortFrom(ip, 60000)
|
|
b.Run(test.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
sinkString = ipp.String()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkAddrPortMarshalText(b *testing.B) {
|
|
for _, test := range parseBenchInputs {
|
|
ip := MustParseAddr(test.ip)
|
|
ipp := AddrPortFrom(ip, 60000)
|
|
b.Run(test.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
sinkBytes, _ = ipp.MarshalText()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkPrefixMasking(b *testing.B) {
|
|
tests := []struct {
|
|
name string
|
|
ip Addr
|
|
bits int
|
|
}{
|
|
{
|
|
name: "IPv4 /32",
|
|
ip: IPv4(192, 0, 2, 0),
|
|
bits: 32,
|
|
},
|
|
{
|
|
name: "IPv4 /17",
|
|
ip: IPv4(192, 0, 2, 0),
|
|
bits: 17,
|
|
},
|
|
{
|
|
name: "IPv4 /0",
|
|
ip: IPv4(192, 0, 2, 0),
|
|
bits: 0,
|
|
},
|
|
{
|
|
name: "IPv6 /128",
|
|
ip: mustIP("2001:db8::1"),
|
|
bits: 128,
|
|
},
|
|
{
|
|
name: "IPv6 /65",
|
|
ip: mustIP("2001:db8::1"),
|
|
bits: 65,
|
|
},
|
|
{
|
|
name: "IPv6 /0",
|
|
ip: mustIP("2001:db8::1"),
|
|
bits: 0,
|
|
},
|
|
{
|
|
name: "IPv6 zone /128",
|
|
ip: mustIP("2001:db8::1%eth0"),
|
|
bits: 128,
|
|
},
|
|
{
|
|
name: "IPv6 zone /65",
|
|
ip: mustIP("2001:db8::1%eth0"),
|
|
bits: 65,
|
|
},
|
|
{
|
|
name: "IPv6 zone /0",
|
|
ip: mustIP("2001:db8::1%eth0"),
|
|
bits: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
b.Run(tt.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
sinkPrefix, _ = tt.ip.Prefix(tt.bits)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkPrefixMarshalText(b *testing.B) {
|
|
b.ReportAllocs()
|
|
ipp := MustParsePrefix("66.55.44.33/22")
|
|
for i := 0; i < b.N; i++ {
|
|
sinkBytes, _ = ipp.MarshalText()
|
|
}
|
|
}
|
|
|
|
func BenchmarkParseAddrPort(b *testing.B) {
|
|
for _, test := range parseBenchInputs {
|
|
var ipp string
|
|
if strings.HasPrefix(test.name, "v6") {
|
|
ipp = fmt.Sprintf("[%s]:1234", test.ip)
|
|
} else {
|
|
ipp = fmt.Sprintf("%s:1234", test.ip)
|
|
}
|
|
b.Run(test.name, func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
sinkAddrPort, _ = ParseAddrPort(ipp)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAs4(t *testing.T) {
|
|
tests := []struct {
|
|
ip Addr
|
|
want [4]byte
|
|
wantPanic bool
|
|
}{
|
|
{
|
|
ip: mustIP("1.2.3.4"),
|
|
want: [4]byte{1, 2, 3, 4},
|
|
},
|
|
{
|
|
ip: AddrFrom16(mustIP("1.2.3.4").As16()), // IPv4-in-IPv6
|
|
want: [4]byte{1, 2, 3, 4},
|
|
},
|
|
{
|
|
ip: mustIP("0.0.0.0"),
|
|
want: [4]byte{0, 0, 0, 0},
|
|
},
|
|
{
|
|
ip: Addr{},
|
|
wantPanic: true,
|
|
},
|
|
{
|
|
ip: mustIP("::1"),
|
|
wantPanic: true,
|
|
},
|
|
}
|
|
as4 := func(ip Addr) (v [4]byte, gotPanic bool) {
|
|
defer func() {
|
|
if recover() != nil {
|
|
gotPanic = true
|
|
return
|
|
}
|
|
}()
|
|
v = ip.As4()
|
|
return
|
|
}
|
|
for i, tt := range tests {
|
|
got, gotPanic := as4(tt.ip)
|
|
if gotPanic != tt.wantPanic {
|
|
t.Errorf("%d. panic on %v = %v; want %v", i, tt.ip, gotPanic, tt.wantPanic)
|
|
continue
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("%d. %v = %v; want %v", i, tt.ip, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPrefixOverlaps(t *testing.T) {
|
|
pfx := mustPrefix
|
|
tests := []struct {
|
|
a, b Prefix
|
|
want bool
|
|
}{
|
|
{Prefix{}, pfx("1.2.0.0/16"), false}, // first zero
|
|
{pfx("1.2.0.0/16"), Prefix{}, false}, // second zero
|
|
{pfx("::0/3"), pfx("0.0.0.0/3"), false}, // different families
|
|
|
|
{pfx("1.2.0.0/16"), pfx("1.2.0.0/16"), true}, // equal
|
|
|
|
{pfx("1.2.0.0/16"), pfx("1.2.3.0/24"), true},
|
|
{pfx("1.2.3.0/24"), pfx("1.2.0.0/16"), true},
|
|
|
|
{pfx("1.2.0.0/16"), pfx("1.2.3.0/32"), true},
|
|
{pfx("1.2.3.0/32"), pfx("1.2.0.0/16"), true},
|
|
|
|
// Match /0 either order
|
|
{pfx("1.2.3.0/32"), pfx("0.0.0.0/0"), true},
|
|
{pfx("0.0.0.0/0"), pfx("1.2.3.0/32"), true},
|
|
|
|
{pfx("1.2.3.0/32"), pfx("5.5.5.5/0"), true}, // normalization not required; /0 means true
|
|
|
|
// IPv6 overlapping
|
|
{pfx("5::1/128"), pfx("5::0/8"), true},
|
|
{pfx("5::0/8"), pfx("5::1/128"), true},
|
|
|
|
// IPv6 not overlapping
|
|
{pfx("1::1/128"), pfx("2::2/128"), false},
|
|
{pfx("0100::0/8"), pfx("::1/128"), false},
|
|
|
|
// v6-mapped v4 should not overlap with IPv4.
|
|
{PrefixFrom(AddrFrom16(mustIP("1.2.0.0").As16()), 16), pfx("1.2.3.0/24"), false},
|
|
|
|
// Invalid prefixes
|
|
{PrefixFrom(mustIP("1.2.3.4"), 33), pfx("1.2.3.0/24"), false},
|
|
{PrefixFrom(mustIP("2000::"), 129), pfx("2000::/64"), false},
|
|
}
|
|
for i, tt := range tests {
|
|
if got := tt.a.Overlaps(tt.b); got != tt.want {
|
|
t.Errorf("%d. (%v).Overlaps(%v) = %v; want %v", i, tt.a, tt.b, got, tt.want)
|
|
}
|
|
// Overlaps is commutative
|
|
if got := tt.b.Overlaps(tt.a); got != tt.want {
|
|
t.Errorf("%d. (%v).Overlaps(%v) = %v; want %v", i, tt.b, tt.a, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sink variables are here to force the compiler to not elide
|
|
// seemingly useless work in benchmarks and allocation tests. If you
|
|
// were to just `_ = foo()` within a test function, the compiler could
|
|
// correctly deduce that foo() does nothing and doesn't need to be
|
|
// called. By writing results to a global variable, we hide that fact
|
|
// from the compiler and force it to keep the code under test.
|
|
var (
|
|
sinkIP Addr
|
|
sinkStdIP net.IP
|
|
sinkAddrPort AddrPort
|
|
sinkPrefix Prefix
|
|
sinkPrefixSlice []Prefix
|
|
sinkInternValue *intern.Value
|
|
sinkIP16 [16]byte
|
|
sinkIP4 [4]byte
|
|
sinkBool bool
|
|
sinkString string
|
|
sinkBytes []byte
|
|
sinkUDPAddr = &net.UDPAddr{IP: make(net.IP, 0, 16)}
|
|
)
|
|
|
|
func TestNoAllocs(t *testing.T) {
|
|
// Wrappers that panic on error, to prove that our alloc-free
|
|
// methods are returning successfully.
|
|
panicIP := func(ip Addr, err error) Addr {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return ip
|
|
}
|
|
panicPfx := func(pfx Prefix, err error) Prefix {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return pfx
|
|
}
|
|
panicIPP := func(ipp AddrPort, err error) AddrPort {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return ipp
|
|
}
|
|
test := func(name string, f func()) {
|
|
t.Run(name, func(t *testing.T) {
|
|
n := testing.AllocsPerRun(1000, f)
|
|
if n != 0 {
|
|
t.Fatalf("allocs = %d; want 0", int(n))
|
|
}
|
|
})
|
|
}
|
|
|
|
// IP constructors
|
|
test("IPv4", func() { sinkIP = IPv4(1, 2, 3, 4) })
|
|
test("AddrFrom4", func() { sinkIP = AddrFrom4([4]byte{1, 2, 3, 4}) })
|
|
test("AddrFrom16", func() { sinkIP = AddrFrom16([16]byte{}) })
|
|
test("ParseAddr/4", func() { sinkIP = panicIP(ParseAddr("1.2.3.4")) })
|
|
test("ParseAddr/6", func() { sinkIP = panicIP(ParseAddr("::1")) })
|
|
test("MustParseAddr", func() { sinkIP = MustParseAddr("1.2.3.4") })
|
|
test("IPv6LinkLocalAllNodes", func() { sinkIP = IPv6LinkLocalAllNodes() })
|
|
test("IPv6Unspecified", func() { sinkIP = IPv6Unspecified() })
|
|
|
|
// IP methods
|
|
test("IP.IsZero", func() { sinkBool = MustParseAddr("1.2.3.4").IsZero() })
|
|
test("IP.BitLen", func() { sinkBool = MustParseAddr("1.2.3.4").BitLen() == 8 })
|
|
test("IP.Zone/4", func() { sinkBool = MustParseAddr("1.2.3.4").Zone() == "" })
|
|
test("IP.Zone/6", func() { sinkBool = MustParseAddr("fe80::1").Zone() == "" })
|
|
test("IP.Zone/6zone", func() { sinkBool = MustParseAddr("fe80::1%zone").Zone() == "" })
|
|
test("IP.Compare", func() {
|
|
a := MustParseAddr("1.2.3.4")
|
|
b := MustParseAddr("2.3.4.5")
|
|
sinkBool = a.Compare(b) == 0
|
|
})
|
|
test("IP.Less", func() {
|
|
a := MustParseAddr("1.2.3.4")
|
|
b := MustParseAddr("2.3.4.5")
|
|
sinkBool = a.Less(b)
|
|
})
|
|
test("IP.Is4", func() { sinkBool = MustParseAddr("1.2.3.4").Is4() })
|
|
test("IP.Is6", func() { sinkBool = MustParseAddr("fe80::1").Is6() })
|
|
test("IP.Is4In6", func() { sinkBool = MustParseAddr("fe80::1").Is4In6() })
|
|
test("IP.Unmap", func() { sinkIP = MustParseAddr("ffff::2.3.4.5").Unmap() })
|
|
test("IP.WithZone", func() { sinkIP = MustParseAddr("fe80::1").WithZone("") })
|
|
test("IP.IsGlobalUnicast", func() { sinkBool = MustParseAddr("2001:db8::1").IsGlobalUnicast() })
|
|
test("IP.IsInterfaceLocalMulticast", func() { sinkBool = MustParseAddr("fe80::1").IsInterfaceLocalMulticast() })
|
|
test("IP.IsLinkLocalMulticast", func() { sinkBool = MustParseAddr("fe80::1").IsLinkLocalMulticast() })
|
|
test("IP.IsLinkLocalUnicast", func() { sinkBool = MustParseAddr("fe80::1").IsLinkLocalUnicast() })
|
|
test("IP.IsLoopback", func() { sinkBool = MustParseAddr("fe80::1").IsLoopback() })
|
|
test("IP.IsMulticast", func() { sinkBool = MustParseAddr("fe80::1").IsMulticast() })
|
|
test("IP.IsPrivate", func() { sinkBool = MustParseAddr("fd00::1").IsPrivate() })
|
|
test("IP.IsUnspecified", func() { sinkBool = IPv6Unspecified().IsUnspecified() })
|
|
test("IP.Prefix/4", func() { sinkPrefix = panicPfx(MustParseAddr("1.2.3.4").Prefix(20)) })
|
|
test("IP.Prefix/6", func() { sinkPrefix = panicPfx(MustParseAddr("fe80::1").Prefix(64)) })
|
|
test("IP.As16", func() { sinkIP16 = MustParseAddr("1.2.3.4").As16() })
|
|
test("IP.As4", func() { sinkIP4 = MustParseAddr("1.2.3.4").As4() })
|
|
test("IP.Next", func() { sinkIP = MustParseAddr("1.2.3.4").Next() })
|
|
test("IP.Prev", func() { sinkIP = MustParseAddr("1.2.3.4").Prev() })
|
|
|
|
// AddrPort constructors
|
|
test("AddrPortFrom", func() { sinkAddrPort = AddrPortFrom(IPv4(1, 2, 3, 4), 22) })
|
|
test("ParseAddrPort", func() { sinkAddrPort = panicIPP(ParseAddrPort("[::1]:1234")) })
|
|
test("MustParseAddrPort", func() { sinkAddrPort = MustParseAddrPort("[::1]:1234") })
|
|
|
|
// Prefix constructors
|
|
test("PrefixFrom", func() { sinkPrefix = PrefixFrom(IPv4(1, 2, 3, 4), 32) })
|
|
test("ParsePrefix/4", func() { sinkPrefix = panicPfx(ParsePrefix("1.2.3.4/20")) })
|
|
test("ParsePrefix/6", func() { sinkPrefix = panicPfx(ParsePrefix("fe80::1/64")) })
|
|
test("MustParsePrefix", func() { sinkPrefix = MustParsePrefix("1.2.3.4/20") })
|
|
|
|
// Prefix methods
|
|
test("Prefix.Contains", func() { sinkBool = MustParsePrefix("1.2.3.0/24").Contains(MustParseAddr("1.2.3.4")) })
|
|
test("Prefix.Overlaps", func() {
|
|
a, b := MustParsePrefix("1.2.3.0/24"), MustParsePrefix("1.2.0.0/16")
|
|
sinkBool = a.Overlaps(b)
|
|
})
|
|
test("Prefix.IsZero", func() { sinkBool = MustParsePrefix("1.2.0.0/16").IsZero() })
|
|
test("Prefix.IsSingleIP", func() { sinkBool = MustParsePrefix("1.2.3.4/32").IsSingleIP() })
|
|
test("IPPRefix.Masked", func() { sinkPrefix = MustParsePrefix("1.2.3.4/16").Masked() })
|
|
}
|
|
|
|
func TestPrefixString(t *testing.T) {
|
|
tests := []struct {
|
|
ipp Prefix
|
|
want string
|
|
}{
|
|
{Prefix{}, "invalid Prefix"},
|
|
{PrefixFrom(Addr{}, 8), "invalid Prefix"},
|
|
{PrefixFrom(MustParseAddr("1.2.3.4"), 88), "invalid Prefix"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := tt.ipp.String(); got != tt.want {
|
|
t.Errorf("(%#v).String() = %q want %q", tt.ipp, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInvalidAddrPortString(t *testing.T) {
|
|
tests := []struct {
|
|
ipp AddrPort
|
|
want string
|
|
}{
|
|
{AddrPort{}, "invalid AddrPort"},
|
|
{AddrPortFrom(Addr{}, 80), "invalid AddrPort"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := tt.ipp.String(); got != tt.want {
|
|
t.Errorf("(%#v).String() = %q want %q", tt.ipp, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAsSlice(t *testing.T) {
|
|
tests := []struct {
|
|
in Addr
|
|
want []byte
|
|
}{
|
|
{in: Addr{}, want: nil},
|
|
{in: mustIP("1.2.3.4"), want: []byte{1, 2, 3, 4}},
|
|
{in: mustIP("ffff::1"), want: []byte{0xff, 0xff, 15: 1}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
got := test.in.AsSlice()
|
|
if !bytes.Equal(got, test.want) {
|
|
t.Errorf("%v.AsSlice() = %v want %v", test.in, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
var sink16 [16]byte
|
|
|
|
func BenchmarkAs16(b *testing.B) {
|
|
addr := MustParseAddr("1::10")
|
|
for i := 0; i < b.N; i++ {
|
|
sink16 = addr.As16()
|
|
}
|
|
}
|