diff --git a/pkg/apiserver/alerts_test.go b/pkg/apiserver/alerts_test.go index 7fda35ffcc3..3699f410e12 100644 --- a/pkg/apiserver/alerts_test.go +++ b/pkg/apiserver/alerts_test.go @@ -305,7 +305,7 @@ func TestAlertListFilters(t *testing.T) { w = lapi.RecordResponse(t, ctx, "GET", "/v1/alerts?ip=gruueq", emptyBody, "password") assert.Equal(t, http.StatusInternalServerError, w.Code) - assert.JSONEq(t, `{"message":"invalid ip address 'gruueq'"}`, w.Body.String()) + assert.JSONEq(t, `{"message":"ParseAddr(\"gruueq\"): unable to parse IP"}`, w.Body.String()) // test range (ok) @@ -324,7 +324,7 @@ func TestAlertListFilters(t *testing.T) { w = lapi.RecordResponse(t, ctx, "GET", "/v1/alerts?range=ratata", emptyBody, "password") assert.Equal(t, http.StatusInternalServerError, w.Code) - assert.JSONEq(t, `{"message":"invalid ip address 'ratata'"}`, w.Body.String()) + assert.JSONEq(t, `{"message":"ParseAddr(\"ratata\"): unable to parse IP"}`, w.Body.String()) // test since (ok) diff --git a/pkg/csnet/ip.go b/pkg/csnet/ip.go index c12552de780..84247d32f87 100644 --- a/pkg/csnet/ip.go +++ b/pkg/csnet/ip.go @@ -1,7 +1,11 @@ package csnet import ( - "github.com/crowdsecurity/crowdsec/pkg/types" + "encoding/binary" + "errors" + "math" + "net/netip" + "strings" ) type IPAddrSize int @@ -11,37 +15,98 @@ const ( IPv6Size IPAddrSize = 16 ) -type IP struct { +type IntIP struct { size IPAddrSize Addr int64 Sfx int64 } type Range struct { - Start IP - End IP + Start IntIP + End IntIP } func (r Range) Size() int { return int(r.Start.size) } +// NewIP converts a netip.Addr into an IntIP for storage and comparison. +func NewIP(addr netip.Addr) IntIP { + if addr.Is4() { + ipBytes := addr.As4() + + return IntIP{ + size: IPv4Size, + Addr: uint2int(uint64(binary.BigEndian.Uint32(ipBytes[:]))), + Sfx: 0, + } + } + + ipBytes := addr.As16() + + return IntIP{ + size: IPv6Size, + Addr: uint2int(binary.BigEndian.Uint64(ipBytes[0:8])), + Sfx: uint2int(binary.BigEndian.Uint64(ipBytes[8:16])), + } +} + +// NewRange parses an IP or CIDR string into a Range of IntIP addresses. +// If the input is a single IP (e.g. "1.2.3.4"), the start and end of the range are equal. +// If the input is a prefix (e.g. "1.2.3.0/24"), the function computes the first and last +// addresses covered by the prefix. func NewRange(anyIP string) (Range, error) { - size, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(anyIP) + if !strings.Contains(anyIP, "/") { + addr, err := netip.ParseAddr(anyIP) + if err != nil { + return Range{}, err + } + + ip := NewIP(addr) + + return Range{Start: ip, End: ip}, nil + } + + prefix, err := netip.ParsePrefix(anyIP) if err != nil { return Range{}, err } - return Range{ - Start: IP{ - size: IPAddrSize(size), - Addr: start_ip, - Sfx: start_sfx, - }, - End: IP{ - size: IPAddrSize(size), - Addr: end_ip, - Sfx: end_sfx, - }, - }, nil + start := prefix.Masked().Addr() + bits := prefix.Bits() + + if start.Is4In6() && bits < 96 { + return Range{}, errors.New("prefix with 4in6 address must have mask >= 96") + } + + a16 := start.As16() + + if start.Is4() { + bits += 96 + } + + // Fill host bits with 1s + for b := bits; b < 128; b++ { + a16[b/8] |= 1 << (7 - (b % 8)) + } + + end := netip.AddrFrom16(a16) + if start.Is4() { + end = end.Unmap() + } + + return Range{Start: NewIP(start), End: NewIP(end)}, nil +} + +func uint2int(u uint64) int64 { + switch { + case u == math.MaxInt64: + return 0 + case u == math.MaxUint64: + return math.MaxInt64 + case u > math.MaxInt64: + return int64(u - math.MaxInt64) + default: + return int64(u) - math.MaxInt64 + } } diff --git a/pkg/csnet/ip_test.go b/pkg/csnet/ip_test.go index 3f0c5654e8d..91b45e48f75 100644 --- a/pkg/csnet/ip_test.go +++ b/pkg/csnet/ip_test.go @@ -2,206 +2,162 @@ package csnet import ( "math" - "strings" "testing" + + "github.com/stretchr/testify/assert" + + "github.com/crowdsecurity/go-cs-lib/cstest" ) -func TestAdd2Int(t *testing.T) { - tests := []struct { - in_addr string - exp_sz int - exp_start_ip int64 - exp_start_sfx int64 - exp_end_ip int64 - exp_end_sfx int64 - exp_error string - }{ - { - in_addr: "7FFF:FFFF:FFFF:FFFF:aaaa:aaaa:aaaa:fff7", +type test struct { + input string + want Range + wantErr string +} - exp_sz: 16, - exp_start_ip: -math.MaxInt64 + 0x7FFFFFFFFFFFFFFF, - exp_start_sfx: -math.MaxInt64 + 0xaaaaaaaaaaaafff7, - exp_end_ip: -math.MaxInt64 + 0x7FFFFFFFFFFFFFFF, - exp_end_sfx: -math.MaxInt64 + 0xaaaaaaaaaaaafff7, +func TestAdd2Int(t *testing.T) { + tests := []test{ + { + input: "7FFF:FFFF:FFFF:FFFF:aaaa:aaaa:aaaa:fff7", + want: Range{ + IntIP{16, -math.MaxInt64 + 0x7FFFFFFFFFFFFFFF, -math.MaxInt64 + 0xaaaaaaaaaaaafff7}, + IntIP{16, -math.MaxInt64 + 0x7FFFFFFFFFFFFFFF, -math.MaxInt64 + 0xaaaaaaaaaaaafff7}, + }, }, { - in_addr: "aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:fff7", - - exp_sz: 16, - exp_start_ip: -math.MaxInt64 + 0xaaaaaaaaaaaaaaaa, - exp_start_sfx: -math.MaxInt64 + 0xaaaaaaaaaaaafff7, - exp_end_ip: -math.MaxInt64 + 0xaaaaaaaaaaaaaaaa, - exp_end_sfx: -math.MaxInt64 + 0xaaaaaaaaaaaafff7, + input: "aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:fff7", + want: Range{ + IntIP{16, -math.MaxInt64 + 0xaaaaaaaaaaaaaaaa, -math.MaxInt64 + 0xaaaaaaaaaaaafff7}, + IntIP{16, -math.MaxInt64 + 0xaaaaaaaaaaaaaaaa, -math.MaxInt64 + 0xaaaaaaaaaaaafff7}, + }, }, { - in_addr: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff7", + input: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff7", /*ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff*/ - exp_sz: 16, - exp_start_ip: math.MaxInt64, - exp_start_sfx: -math.MaxInt64 + 0xfffffffffffffff7, - exp_end_ip: math.MaxInt64, - exp_end_sfx: -math.MaxInt64 + 0xfffffffffffffff7, + want: Range{ + IntIP{16, math.MaxInt64, -math.MaxInt64 + 0xfffffffffffffff7}, + IntIP{16, math.MaxInt64, -math.MaxInt64 + 0xfffffffffffffff7}, + }, }, { - in_addr: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + input: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", /*ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff*/ - exp_sz: 16, - exp_start_ip: math.MaxInt64, - exp_start_sfx: math.MaxInt64, - exp_end_ip: math.MaxInt64, - exp_end_sfx: math.MaxInt64, + want: Range{ + IntIP{16, math.MaxInt64, math.MaxInt64}, + IntIP{16, math.MaxInt64, math.MaxInt64}, + }, }, { - in_addr: "::", + input: "::", /*::*/ - - exp_sz: 16, - exp_start_ip: -math.MaxInt64, - exp_start_sfx: -math.MaxInt64, - exp_end_ip: -math.MaxInt64, - exp_end_sfx: -math.MaxInt64, + want: Range{ + IntIP{16, -math.MaxInt64, -math.MaxInt64}, + IntIP{16, -math.MaxInt64, -math.MaxInt64}, + }, }, { - in_addr: "2001:db8::", + input: "2001:db8::", /*2001:db8:: -> 2001:db8::*/ - exp_sz: 16, - exp_start_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_start_sfx: -math.MaxInt64, - exp_end_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_end_sfx: -math.MaxInt64, + want: Range{ + IntIP{16, -math.MaxInt64 + 0x20010DB800000000, -math.MaxInt64}, + IntIP{16, -math.MaxInt64 + 0x20010DB800000000, -math.MaxInt64}, + }, }, { - in_addr: "2001:db8:0000:0000:0000:0000:0000:00ff", + input: "2001:db8:0000:0000:0000:0000:0000:00ff", /*2001:db8:0000:0000:0000:0000:0000:00ff*/ - exp_sz: 16, - exp_start_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_start_sfx: -math.MaxInt64 + 0xFF, - exp_end_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_end_sfx: -math.MaxInt64 + 0xFF, + want: Range{ + IntIP{16, -math.MaxInt64 + 0x20010DB800000000, -math.MaxInt64 + 0xFF}, + IntIP{16, -math.MaxInt64 + 0x20010DB800000000, -math.MaxInt64 + 0xFF}, + }, }, { - in_addr: "1.2.3.4", + input: "1.2.3.4", /*1.2.3.4*/ - exp_sz: 4, - exp_start_ip: -math.MaxInt64 + 0x01020304, - exp_start_sfx: 0, - exp_end_ip: -math.MaxInt64 + 0x01020304, - exp_end_sfx: 0, + want: Range{ + IntIP{4, -math.MaxInt64 + 0x01020304, 0}, + IntIP{4, -math.MaxInt64 + 0x01020304, 0}, + }, }, { - in_addr: "::/0", + input: "::/0", /*:: -> ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff*/ - exp_sz: 16, - exp_start_ip: -math.MaxInt64, - exp_start_sfx: -math.MaxInt64, - exp_end_ip: math.MaxInt64, - exp_end_sfx: math.MaxInt64, + want: Range{ + IntIP{16, -math.MaxInt64, -math.MaxInt64}, + IntIP{16, math.MaxInt64, math.MaxInt64}, + }, }, { - in_addr: "::/64", + input: "::/64", /*:: -> 0000:0000:0000:0000:ffff:ffff:ffff:ffff*/ - exp_sz: 16, - exp_start_ip: -math.MaxInt64, - exp_start_sfx: -math.MaxInt64, - exp_end_ip: -math.MaxInt64, - exp_end_sfx: math.MaxInt64, + want: Range{ + IntIP{16, -math.MaxInt64, -math.MaxInt64}, + IntIP{16, -math.MaxInt64, math.MaxInt64}, + }, }, { - in_addr: "2001:db8::/109", + input: "2001:db8::/109", /*2001:db8:: -> 2001:db8:0000:0000:0000:0000:0007:ffff*/ - exp_sz: 16, - exp_start_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_start_sfx: -math.MaxInt64, - exp_end_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_end_sfx: -math.MaxInt64 + 0x7FFFF, + want: Range{ + IntIP{16, -math.MaxInt64 + 0x20010DB800000000, -math.MaxInt64}, + IntIP{16, -math.MaxInt64 + 0x20010DB800000000, -math.MaxInt64 + 0x7FFFF}, + }, }, { - in_addr: "0.0.0.0/0", + input: "0.0.0.0/0", /*0.0.0.0 -> 255.255.255.255*/ - exp_sz: 4, - exp_start_ip: -math.MaxInt64, - exp_start_sfx: 0, - exp_end_ip: -math.MaxInt64 + 0xFFFFFFFF, - exp_end_sfx: 0, + want: Range{ + IntIP{4, -math.MaxInt64, 0}, + IntIP{4, -math.MaxInt64 + 0xFFFFFFFF, 0}, + }, }, { - in_addr: "0.0.0.0/16", + input: "0.0.0.0/16", /*0.0.0.0 -> 0.0.255.255*/ - exp_sz: 4, - exp_start_ip: -math.MaxInt64, - exp_start_sfx: 0, - exp_end_ip: -math.MaxInt64 + 0x0000FFFF, - exp_end_sfx: 0, + want: Range{ + IntIP{4, -math.MaxInt64, 0}, + IntIP{4, -math.MaxInt64 + 0x0000FFFF, 0}, + }, }, { - in_addr: "255.255.0.0/16", + input: "255.255.0.0/16", /*255.255.0.0 -> 255.255.255.255*/ - exp_sz: 4, - exp_start_ip: -math.MaxInt64 + 0xFFFF0000, - exp_start_sfx: 0, - exp_end_ip: -math.MaxInt64 + 0xFFFFFFFF, - exp_end_sfx: 0, + want: Range{ + IntIP{4, -math.MaxInt64 + 0xFFFF0000, 0}, + IntIP{4, -math.MaxInt64 + 0xFFFFFFFF, 0}, + }, }, { - in_addr: "1.2.3.0/24", + input: "1.2.3.0/24", /*1.2.3.0 -> 1.2.3.255*/ - exp_sz: 4, - exp_start_ip: -math.MaxInt64 + 0x01020300, - exp_start_sfx: 0, - exp_end_ip: -math.MaxInt64 + 0x010203FF, - exp_end_sfx: 0, + want: Range{ + IntIP{4, -math.MaxInt64 + 0x01020300, 0}, + IntIP{4, -math.MaxInt64 + 0x010203FF, 0}, + }, }, - /*errors*/ { - in_addr: "xxx/24", - exp_error: "invalid ip range 'xxx/24': invalid CIDR address: xxx/24", + input: "xxx/24", + wantErr: `netip.ParsePrefix("xxx/24"): ParseAddr("xxx"): unable to parse IP`, }, { - in_addr: "xxx2", - exp_error: "invalid ip address 'xxx2'", + input: "xxx2", + wantErr: `ParseAddr("xxx2"): unable to parse IP`, }, } - for idx, test := range tests { - rng, err := NewRange(test.in_addr) - if err != nil && test.exp_error == "" { - t.Fatalf("%d unexpected error : %s", idx, err) - } + for _, tc := range tests { + t.Run(tc.input, func(t *testing.T) { + got, err := NewRange(tc.input) + cstest.RequireErrorContains(t, err, tc.wantErr) - if test.exp_error != "" { - if !strings.Contains(err.Error(), test.exp_error) { - t.Fatalf("%d unmatched error : %s != %s", idx, err, test.exp_error) + if tc.wantErr != "" { + return } - continue // we can skip this one - } - - if rng.Size() != test.exp_sz { - t.Fatalf("%d unexpected size %d != %d", idx, rng.Size(), test.exp_sz) - } - - if rng.Start.Addr != test.exp_start_ip { - t.Fatalf("%d unexpected start_ip %d != %d", idx, rng.Start.Addr, test.exp_start_ip) - } - - if rng.Size() == 16 { - if rng.Start.Sfx != test.exp_start_sfx { - t.Fatalf("%d unexpected start sfx %d != %d", idx, rng.Start.Sfx, test.exp_start_sfx) - } - } - - if rng.End.Addr != test.exp_end_ip { - t.Fatalf("%d unexpected end ip %d != %d", idx, rng.End.Addr, test.exp_end_ip) - } - - if rng.Size() == 16 { - if rng.End.Sfx != test.exp_end_sfx { - t.Fatalf("%d unexpected end sfx %d != %d", idx, rng.End.Sfx, test.exp_end_sfx) - } - } + assert.Equal(t, tc.want, got) + }) } } diff --git a/pkg/database/utils.go b/pkg/database/utils.go deleted file mode 100644 index 9b8d20dcf92..00000000000 --- a/pkg/database/utils.go +++ /dev/null @@ -1,68 +0,0 @@ -package database - -import ( - "encoding/binary" - "fmt" - "net" -) - -func IP2Int(ip net.IP) uint32 { - if len(ip) == 16 { - return binary.BigEndian.Uint32(ip[12:16]) - } - - return binary.BigEndian.Uint32(ip) -} - -func Int2ip(nn uint32) net.IP { - ip := make(net.IP, 4) - binary.BigEndian.PutUint32(ip, nn) - - return ip -} - -func IsIpv4(host string) bool { - return net.ParseIP(host) != nil -} - -// Stolen from : https://github.com/llimllib/ipaddress/ -// Return the final address of a net range. Convert to IPv4 if possible, -// otherwise return an ipv6 -func LastAddress(n *net.IPNet) net.IP { - ip := n.IP.To4() - if ip == nil { - ip = n.IP - - return net.IP{ - ip[0] | ^n.Mask[0], ip[1] | ^n.Mask[1], ip[2] | ^n.Mask[2], - ip[3] | ^n.Mask[3], ip[4] | ^n.Mask[4], ip[5] | ^n.Mask[5], - ip[6] | ^n.Mask[6], ip[7] | ^n.Mask[7], ip[8] | ^n.Mask[8], - ip[9] | ^n.Mask[9], ip[10] | ^n.Mask[10], ip[11] | ^n.Mask[11], - ip[12] | ^n.Mask[12], ip[13] | ^n.Mask[13], ip[14] | ^n.Mask[14], - ip[15] | ^n.Mask[15], - } - } - - return net.IPv4( - ip[0]|^n.Mask[0], - ip[1]|^n.Mask[1], - ip[2]|^n.Mask[2], - ip[3]|^n.Mask[3]) -} - -// GetIpsFromIpRange takes a CIDR range and returns the start and end IP -func GetIpsFromIpRange(host string) (int64, int64, error) { - _, parsedRange, err := net.ParseCIDR(host) - if err != nil { - return 0, 0, fmt.Errorf("'%s' is not a valid CIDR", host) - } - - if parsedRange == nil { - return 0, 0, fmt.Errorf("unable to parse network: %w", err) - } - - ipStart := int64(IP2Int(parsedRange.IP)) - ipEnd := int64(IP2Int(LastAddress(parsedRange))) - - return ipStart, ipEnd, nil -} diff --git a/pkg/types/ip.go b/pkg/types/ip.go deleted file mode 100644 index 3f52a7ccf18..00000000000 --- a/pkg/types/ip.go +++ /dev/null @@ -1,118 +0,0 @@ -package types - -import ( - "encoding/binary" - "fmt" - "math" - "net" - "strings" -) - -// LastAddress returns the last address of a network -func LastAddress(n net.IPNet) net.IP { - // get the last address by ORing the hostmask and the IP - ip := n.IP.To4() - if ip == nil { - // IPv6 - ip = n.IP - - return net.IP{ - ip[0] | ^n.Mask[0], ip[1] | ^n.Mask[1], ip[2] | ^n.Mask[2], - ip[3] | ^n.Mask[3], ip[4] | ^n.Mask[4], ip[5] | ^n.Mask[5], - ip[6] | ^n.Mask[6], ip[7] | ^n.Mask[7], ip[8] | ^n.Mask[8], - ip[9] | ^n.Mask[9], ip[10] | ^n.Mask[10], ip[11] | ^n.Mask[11], - ip[12] | ^n.Mask[12], ip[13] | ^n.Mask[13], ip[14] | ^n.Mask[14], - ip[15] | ^n.Mask[15], - } - } - - return net.IPv4( - ip[0]|^n.Mask[0], - ip[1]|^n.Mask[1], - ip[2]|^n.Mask[2], - ip[3]|^n.Mask[3]) -} - -/*returns a range for any ip or range*/ -func Addr2Ints(anyIP string) (int, int64, int64, int64, int64, error) { - if strings.Contains(anyIP, "/") { - _, net, err := net.ParseCIDR(anyIP) - if err != nil { - return -1, 0, 0, 0, 0, fmt.Errorf("invalid ip range '%s': %w", anyIP, err) - } - - return Range2Ints(*net) - } - - ip := net.ParseIP(anyIP) - if ip == nil { - return -1, 0, 0, 0, 0, fmt.Errorf("invalid ip address '%s'", anyIP) - } - - sz, start, end, err := IP2Ints(ip) - if err != nil { - return -1, 0, 0, 0, 0, fmt.Errorf("invalid ip address '%s': %w", anyIP, err) - } - - return sz, start, end, start, end, nil -} - -/*size (16|4), nw_start, suffix_start, nw_end, suffix_end, error*/ -func Range2Ints(network net.IPNet) (int, int64, int64, int64, int64, error) { - szStart, nwStart, sfxStart, err := IP2Ints(network.IP) - if err != nil { - return -1, 0, 0, 0, 0, fmt.Errorf("converting first ip in range: %w", err) - } - - lastAddr := LastAddress(network) - - szEnd, nwEnd, sfxEnd, err := IP2Ints(lastAddr) - if err != nil { - return -1, 0, 0, 0, 0, fmt.Errorf("transforming last address of range: %w", err) - } - - if szEnd != szStart { - return -1, 0, 0, 0, 0, fmt.Errorf("inconsistent size for range first(%d) and last(%d) ip", szStart, szEnd) - } - - return szStart, nwStart, sfxStart, nwEnd, sfxEnd, nil -} - -func uint2int(u uint64) int64 { - var ret int64 - if u == math.MaxInt64 { - ret = 0 - } else if u == math.MaxUint64 { - ret = math.MaxInt64 - } else if u > math.MaxInt64 { - u -= math.MaxInt64 - ret = int64(u) - } else { - ret = int64(u) - ret -= math.MaxInt64 - } - - return ret -} - -/*size (16|4), network, suffix, error*/ -func IP2Ints(pip net.IP) (int, int64, int64, error) { - var ip_nw, ip_sfx uint64 - - pip4 := pip.To4() - pip16 := pip.To16() - - if pip4 != nil { - ip_nw32 := binary.BigEndian.Uint32(pip4) - return 4, uint2int(uint64(ip_nw32)), uint2int(ip_sfx), nil - } - - if pip16 != nil { - ip_nw = binary.BigEndian.Uint64(pip16[0:8]) - ip_sfx = binary.BigEndian.Uint64(pip16[8:16]) - - return 16, uint2int(ip_nw), uint2int(ip_sfx), nil - } - - return -1, 0, 0, fmt.Errorf("unexpected len %d for %s", len(pip), pip) -} diff --git a/pkg/types/ip_test.go b/pkg/types/ip_test.go deleted file mode 100644 index 571163761d4..00000000000 --- a/pkg/types/ip_test.go +++ /dev/null @@ -1,226 +0,0 @@ -package types - -import ( - "math" - "net" - "strings" - "testing" -) - -func TestIP2Int(t *testing.T) { - tEmpty := net.IP{} - - _, _, _, err := IP2Ints(tEmpty) - if !strings.Contains(err.Error(), "unexpected len 0 for ") { - t.Fatalf("unexpected: %s", err) - } -} - -func TestRange2Int(t *testing.T) { - tEmpty := net.IPNet{} - // empty item - _, _, _, _, _, err := Range2Ints(tEmpty) - if !strings.Contains(err.Error(), "converting first ip in range") { - t.Fatalf("unexpected: %s", err) - } -} - -func TestAdd2Int(t *testing.T) { - tests := []struct { - in_addr string - exp_sz int - exp_start_ip int64 - exp_start_sfx int64 - exp_end_ip int64 - exp_end_sfx int64 - exp_error string - }{ - { - in_addr: "7FFF:FFFF:FFFF:FFFF:aaaa:aaaa:aaaa:fff7", - - exp_sz: 16, - exp_start_ip: -math.MaxInt64 + 0x7FFFFFFFFFFFFFFF, - exp_start_sfx: -math.MaxInt64 + 0xaaaaaaaaaaaafff7, - exp_end_ip: -math.MaxInt64 + 0x7FFFFFFFFFFFFFFF, - exp_end_sfx: -math.MaxInt64 + 0xaaaaaaaaaaaafff7, - }, - { - in_addr: "aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:fff7", - - exp_sz: 16, - exp_start_ip: -math.MaxInt64 + 0xaaaaaaaaaaaaaaaa, - exp_start_sfx: -math.MaxInt64 + 0xaaaaaaaaaaaafff7, - exp_end_ip: -math.MaxInt64 + 0xaaaaaaaaaaaaaaaa, - exp_end_sfx: -math.MaxInt64 + 0xaaaaaaaaaaaafff7, - }, - { - in_addr: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff7", - /*ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff*/ - - exp_sz: 16, - exp_start_ip: math.MaxInt64, - exp_start_sfx: -math.MaxInt64 + 0xfffffffffffffff7, - exp_end_ip: math.MaxInt64, - exp_end_sfx: -math.MaxInt64 + 0xfffffffffffffff7, - }, - { - in_addr: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", - /*ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff*/ - - exp_sz: 16, - exp_start_ip: math.MaxInt64, - exp_start_sfx: math.MaxInt64, - exp_end_ip: math.MaxInt64, - exp_end_sfx: math.MaxInt64, - }, - { - in_addr: "::", - /*::*/ - - exp_sz: 16, - exp_start_ip: -math.MaxInt64, - exp_start_sfx: -math.MaxInt64, - exp_end_ip: -math.MaxInt64, - exp_end_sfx: -math.MaxInt64, - }, - { - in_addr: "2001:db8::", - /*2001:db8:: -> 2001:db8::*/ - exp_sz: 16, - exp_start_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_start_sfx: -math.MaxInt64, - exp_end_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_end_sfx: -math.MaxInt64, - }, - { - in_addr: "2001:db8:0000:0000:0000:0000:0000:00ff", - /*2001:db8:0000:0000:0000:0000:0000:00ff*/ - exp_sz: 16, - exp_start_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_start_sfx: -math.MaxInt64 + 0xFF, - exp_end_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_end_sfx: -math.MaxInt64 + 0xFF, - }, - { - in_addr: "1.2.3.4", - /*1.2.3.4*/ - exp_sz: 4, - exp_start_ip: -math.MaxInt64 + 0x01020304, - exp_start_sfx: 0, - exp_end_ip: -math.MaxInt64 + 0x01020304, - exp_end_sfx: 0, - }, - { - in_addr: "::/0", - /*:: -> ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff*/ - - exp_sz: 16, - exp_start_ip: -math.MaxInt64, - exp_start_sfx: -math.MaxInt64, - exp_end_ip: math.MaxInt64, - exp_end_sfx: math.MaxInt64, - }, - { - in_addr: "::/64", - /*:: -> 0000:0000:0000:0000:ffff:ffff:ffff:ffff*/ - exp_sz: 16, - exp_start_ip: -math.MaxInt64, - exp_start_sfx: -math.MaxInt64, - exp_end_ip: -math.MaxInt64, - exp_end_sfx: math.MaxInt64, - }, - { - in_addr: "2001:db8::/109", - /*2001:db8:: -> 2001:db8:0000:0000:0000:0000:0007:ffff*/ - exp_sz: 16, - exp_start_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_start_sfx: -math.MaxInt64, - exp_end_ip: -math.MaxInt64 + 0x20010DB800000000, - exp_end_sfx: -math.MaxInt64 + 0x7FFFF, - }, - { - in_addr: "0.0.0.0/0", - /*0.0.0.0 -> 255.255.255.255*/ - exp_sz: 4, - exp_start_ip: -math.MaxInt64, - exp_start_sfx: 0, - exp_end_ip: -math.MaxInt64 + 0xFFFFFFFF, - exp_end_sfx: 0, - }, - { - in_addr: "0.0.0.0/16", - /*0.0.0.0 -> 0.0.255.255*/ - exp_sz: 4, - exp_start_ip: -math.MaxInt64, - exp_start_sfx: 0, - exp_end_ip: -math.MaxInt64 + 0x0000FFFF, - exp_end_sfx: 0, - }, - { - in_addr: "255.255.0.0/16", - /*255.255.0.0 -> 255.255.255.255*/ - exp_sz: 4, - exp_start_ip: -math.MaxInt64 + 0xFFFF0000, - exp_start_sfx: 0, - exp_end_ip: -math.MaxInt64 + 0xFFFFFFFF, - exp_end_sfx: 0, - }, - { - in_addr: "1.2.3.0/24", - /*1.2.3.0 -> 1.2.3.255*/ - exp_sz: 4, - exp_start_ip: -math.MaxInt64 + 0x01020300, - exp_start_sfx: 0, - exp_end_ip: -math.MaxInt64 + 0x010203FF, - exp_end_sfx: 0, - }, - /*errors*/ - { - in_addr: "xxx/24", - exp_error: "invalid CIDR address", - }, - { - in_addr: "xxx2", - exp_error: "invalid ip address 'xxx2'", - }, - } - - for idx, test := range tests { - sz, start_ip, start_sfx, end_ip, end_sfx, err := Addr2Ints(test.in_addr) - if err != nil && test.exp_error == "" { - t.Fatalf("%d unexpected error : %s", idx, err) - } - - if test.exp_error != "" { - if !strings.Contains(err.Error(), test.exp_error) { - t.Fatalf("%d unmatched error : %s != %s", idx, err, test.exp_error) - } - - continue // we can skip this one - } - - if sz != test.exp_sz { - t.Fatalf("%d unexpected size %d != %d", idx, sz, test.exp_sz) - } - - if start_ip != test.exp_start_ip { - t.Fatalf("%d unexpected start_ip %d != %d", idx, start_ip, test.exp_start_ip) - } - - if sz == 16 { - if start_sfx != test.exp_start_sfx { - t.Fatalf("%d unexpected start sfx %d != %d", idx, start_sfx, test.exp_start_sfx) - } - } - - if end_ip != test.exp_end_ip { - t.Fatalf("%d unexpected end ip %d != %d", idx, end_ip, test.exp_end_ip) - } - - if sz == 16 { - if end_sfx != test.exp_end_sfx { - t.Fatalf("%d unexpected end sfx %d != %d", idx, end_sfx, test.exp_end_sfx) - } - } - } -} diff --git a/test/bats/90_decisions.bats b/test/bats/90_decisions.bats index 1d98bd26abe..5f32a4576c0 100644 --- a/test/bats/90_decisions.bats +++ b/test/bats/90_decisions.bats @@ -166,7 +166,7 @@ teardown() { rune -1 cscli decisions import -i - --format values <<-EOT whatever EOT - assert_stderr --partial "invalid ip address 'whatever'" + assert_stderr --partial 'API error: ParseAddr("whatever"): unable to parse IP' rune -0 cscli decisions list -a -o json assert_json '[]' @@ -182,7 +182,7 @@ teardown() { 1.2.3.5 EOT assert_output "Parsing values" - assert_stderr "Error: cscli decisions import: API error: invalid ip address 'bad-apple'" + assert_stderr 'Error: cscli decisions import: API error: ParseAddr("bad-apple"): unable to parse IP' rune -0 cscli decisions list -a -o json rune -0 jq -r '.[0].decisions | length' <(output) diff --git a/test/bats/cscli-allowlists.bats b/test/bats/cscli-allowlists.bats index 50350b7571f..77dbfdd617d 100644 --- a/test/bats/cscli-allowlists.bats +++ b/test/bats/cscli-allowlists.bats @@ -95,15 +95,15 @@ teardown() { rune -0 cscli allowlist add foo bar # XXX: here we should return an error? # and it's currently displayed as ERRO[0000] -- client logger has no formatter? - assert_stderr --partial "level=error msg=\"invalid ip address 'bar'\"" + assert_stderr --partial 'level=error msg="ParseAddr(\"bar\"): unable to parse IP' refute_output rune -0 cscli allowlist add foo 1.1.1.256 - assert_stderr --partial "level=error msg=\"invalid ip address '1.1.1.256'\"" + assert_stderr --partial 'level=error msg="ParseAddr(\"1.1.1.256\"): IPv4 field has value >255"' refute_output rune -0 cscli allowlist add foo 1.1.1.1/2/3 - assert_stderr --partial "level=error msg=\"invalid ip range '1.1.1.1/2/3': invalid CIDR address: 1.1.1.1/2/3\"" + assert_stderr --partial 'level=error msg="netip.ParsePrefix(\"1.1.1.1/2/3\"): ParseAddr(\"1.1.1.1/2\"): unexpected character (at \"/2\")"' refute_output rune -0 cscli allowlist add foo 1.2.3.4