diff --git a/pkg/addr/BUILD.bazel b/pkg/addr/BUILD.bazel index 5bf33ba2da..9ee5fd06f1 100644 --- a/pkg/addr/BUILD.bazel +++ b/pkg/addr/BUILD.bazel @@ -17,7 +17,15 @@ go_library( go_test( name = "go_default_test", - srcs = ["isdas_test.go"], + srcs = [ + "addr_test.go", + "host_test.go", + "isdas_test.go", + "svc_test.go", + ], embed = [":go_default_library"], - deps = ["@com_github_stretchr_testify//assert:go_default_library"], + deps = [ + "@com_github_stretchr_testify//assert:go_default_library", + "@com_github_stretchr_testify//require:go_default_library", + ], ) diff --git a/pkg/addr/addr_test.go b/pkg/addr/addr_test.go index 0490919bdc..586a8efd98 100644 --- a/pkg/addr/addr_test.go +++ b/pkg/addr/addr_test.go @@ -16,11 +16,170 @@ package addr_test import ( "fmt" + "net/netip" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/scionproto/scion/pkg/addr" ) func ExampleParseAddr() { a, err := addr.ParseAddr("6-ffaa:0:123,198.51.100.1") - fmt.Printf("a: %+v, err: %v\n", a, err) + fmt.Printf("ia: %v, host: %v, err: %v\n", a.IA, a.Host, err) + // Output: ia: 6-ffaa:0:123, host: 198.51.100.1, err: +} + +func ExampleParseAddr_svc() { + a, err := addr.ParseAddr("6-ffaa:0:123,CS") + fmt.Printf("host type: %v, err: %v\n", a.Host.Type(), err) + // Output: host type: SVC, err: +} + +func TestParseAddr(t *testing.T) { + invalid := []string{ + "", + ",", + "a", + "0-0::", + "0-0,::,", + "1,ffaa:0:1101::", + "65536-1,ff00::1", + "[1-ffaa:0:1101,127.0.0.1]", + } + for _, s := range invalid { + t.Run(s, func(t *testing.T) { + _, err := addr.ParseAddr(s) + assert.Error(t, err) + }) + } + + valid := map[string]addr.Addr{ + "0-0,::": { + IA: addr.MustIAFrom(0, 0), + Host: addr.HostIP(netip.AddrFrom16([16]byte{})), + }, + "0-0,0.0.0.0": { + IA: addr.MustIAFrom(0, 0), + Host: addr.HostIP(netip.AddrFrom4([4]byte{})), + }, + "1-ffaa:0:1101,::1": { + IA: addr.MustIAFrom(1, 0xffaa_0000_1101), + Host: addr.HostIP(netip.AddrFrom16( + [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + )), + }, + "1-ffaa:0:1101,127.0.0.1": { + IA: addr.MustIAFrom(1, 0xffaa_0000_1101), + Host: addr.HostIP(netip.AddrFrom4([4]byte{127, 0, 0, 1})), + }, + "1-ffaa:0:1101,CS": { + IA: addr.MustIAFrom(1, 0xffaa_0000_1101), + Host: addr.HostSVC(addr.SvcCS), + }, + "65535-1,ff00::1": { + IA: addr.MustIAFrom(65535, 1), + Host: addr.HostIP(netip.AddrFrom16( + [16]byte{0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + )), + }, + "1-1:fcd1:1,::ffff:192.0.2.128": { + IA: addr.MustIAFrom(1, 0x0001_fcd1_0001), + Host: addr.HostIP(netip.AddrFrom16( + [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 0, 2, 128}, + )), + }, + } + for s, expected := range valid { + t.Run(s, func(t *testing.T) { + a, err := addr.ParseAddr(s) + require.NoError(t, err) + assert.Equal(t, expected, a) + }) + } +} + +func TestParseAddrPort(t *testing.T) { + invalid := []string{ + "", + "[]", + "[]:", + "[0-0,::]:65536", + "[0-0,::]:http", + "[0-0,::]:a", + "[1-ffaa:0:1101,127.0.0.1]", + "[1-ffaa:0:1101,127.0.0.1]:0xff", + "[1-ffaa:0:1101,127.0.0.1]:ff", + "[1-ffaa:0:1101,127.0.0.1]:-1", + "[1-ffaa:0:1101,127.0.0.1]:666666", + } + for _, s := range invalid { + t.Run(s, func(t *testing.T) { + _, _, err := addr.ParseAddrPort(s) + assert.Error(t, err) + }) + } + + valid := map[string]struct { + IA addr.IA + Host addr.Host + Port uint16 + }{ + "[0-0,::]:0": { + IA: addr.MustIAFrom(0, 0), + Host: addr.HostIP(netip.AddrFrom16([16]byte{})), + Port: 0, + }, + "[0-0,::]:65535": { + IA: addr.MustIAFrom(0, 0), + Host: addr.HostIP(netip.AddrFrom16([16]byte{})), + Port: 65535, + }, + "[0-0,0.0.0.0]:1234": { + IA: addr.MustIAFrom(0, 0), + Host: addr.HostIP(netip.AddrFrom4([4]byte{})), + Port: 1234, + }, + "[1-ffaa:0:1101,::1]:54321": { + IA: addr.MustIAFrom(1, 0xffaa_0000_1101), + Host: addr.HostIP(netip.AddrFrom16( + [16]byte{15: 1}, + )), + Port: 54321, + }, + "[1-ffaa:0:1101,127.0.0.1]:010": { + IA: addr.MustIAFrom(1, 0xffaa_0000_1101), + Host: addr.HostIP(netip.AddrFrom4([4]byte{127, 0, 0, 1})), + Port: 10, + }, + "[1-ffaa:0:1101,CS]:42": { + IA: addr.MustIAFrom(1, 0xffaa_0000_1101), + Host: addr.HostSVC(addr.SvcCS), + Port: 42, + }, + "[65535-1,ff00::1]:8888": { + IA: addr.MustIAFrom(65535, 1), + Host: addr.HostIP(netip.AddrFrom16( + [16]byte{0: 0xff, 15: 1}, + )), + Port: 8888, + }, + "[1-1:fcd1:1,::ffff:192.0.2.128]:0000000000000000000080": { + IA: addr.MustIAFrom(1, 0x0001_fcd1_0001), + Host: addr.HostIP(netip.AddrFrom16( + [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 0, 2, 128}, + )), + Port: 80, + }, + } + + for s, expected := range valid { + t.Run(s, func(t *testing.T) { + a, port, err := addr.ParseAddrPort(s) + require.NoError(t, err) + assert.Equal(t, addr.Addr{IA: expected.IA, Host: expected.Host}, a) + assert.Equal(t, expected.Port, port) + }) + } } diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 16659472a1..3adbf73e86 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -60,8 +60,8 @@ type Host struct { // s can either be a SVC address, in the format supported by ParseSVC(s), // or an IP address in dotted decimal or IPv6 format. func ParseHost(s string) (Host, error) { - svc := ParseSVC(s) - if svc != SvcNone { + svc, err := ParseSVC(s) + if err == nil { return HostSVC(svc), nil } ip, err := netip.ParseAddr(s) diff --git a/pkg/addr/host_test.go b/pkg/addr/host_test.go new file mode 100644 index 0000000000..f7b8095261 --- /dev/null +++ b/pkg/addr/host_test.go @@ -0,0 +1,143 @@ +// Copyright 2023 SCION Association +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package addr_test + +import ( + "fmt" + "net/netip" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/scionproto/scion/pkg/addr" +) + +func ExampleHost() { + hs := []addr.Host{ + {}, + addr.HostIP(netip.MustParseAddr("::1")), + addr.HostIP(netip.AddrFrom4([4]byte{198, 51, 100, 1})), + addr.HostSVC(addr.SvcCS), + } + for _, h := range hs { + fmt.Printf("h: %q, h.Type(): %q", h, h.Type()) + switch h.Type() { + case addr.HostTypeIP: + fmt.Printf(", h.IP().Is4(): %v", h.IP().Is4()) + case addr.HostTypeSVC: + fmt.Printf(", h.SVC().IsMulticast(): %v", h.SVC().IsMulticast()) + default: + fmt.Printf(", h == addr.Host{}: %v", h == addr.Host{}) + } + fmt.Println() + } + + // Use Host as map key: + stuff := make(map[addr.Host]struct{}) + for _, h := range hs { + stuff[h] = struct{}{} + } + _, hasSvcCS := stuff[addr.HostSVC(addr.SvcCS)] + _, hasSvcDS := stuff[addr.HostSVC(addr.SvcDS)] + fmt.Printf("has SvcCS: %v, has SvcDS: %v", hasSvcCS, hasSvcDS) + + // Output: + // h: "", h.Type(): "None", h == addr.Host{}: true + // h: "::1", h.Type(): "IP", h.IP().Is4(): false + // h: "198.51.100.1", h.Type(): "IP", h.IP().Is4(): true + // h: "CS A (0x0002)", h.Type(): "SVC", h.SVC().IsMulticast(): false + // has SvcCS: true, has SvcDS: false +} + +func TestParseHost(t *testing.T) { + invalid := []string{ + "", + "x", + "512.0.0.1", + "10.1234567", + "::ffff1", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334:1", // too long + " ::1", + "::1 ", + "localhost", + "CS_X", // almost a service addr + } + for _, s := range invalid { + t.Run(s, func(t *testing.T) { + _, err := addr.ParseHost(s) + assert.Error(t, err) + }) + } + + ipv6 := []string{ + "::", + "::1", + "::ff02:1", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "fe80::1ff:fe23:4567:890a%eth2", + "::ffff:192.0.2.128", + "ff00::", + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + } + for _, s := range ipv6 { + t.Run(s, func(t *testing.T) { + h, err := addr.ParseHost(s) + require.NoError(t, err) + require.Equal(t, addr.HostTypeIP, h.Type()) + assert.True(t, h.IP().Is6()) + assert.Equal(t, netip.MustParseAddr(s), h.IP()) + }) + } + + ipv4 := []string{ + "0.0.0.0", + "127.0.0.1", + "198.51.100.0", + "198.51.100.1", + "198.51.100.254", + "198.51.100.255", + "255.255.255.255", + } + for _, s := range ipv4 { + t.Run(s, func(t *testing.T) { + h, err := addr.ParseHost(s) + require.NoError(t, err) + require.Equal(t, addr.HostTypeIP, h.Type()) + assert.True(t, h.IP().Is4()) + assert.Equal(t, netip.MustParseAddr(s), h.IP()) + }) + } + + svcs := map[string]addr.SVC{ + "CS": addr.SvcCS, + "DS": addr.SvcDS, + "Wildcard": addr.SvcWildcard, + "CS_A": addr.SvcCS, + "DS_A": addr.SvcDS, + "Wildcard_A": addr.SvcWildcard, + "CS_M": addr.SvcCS.Multicast(), + "DS_M": addr.SvcDS.Multicast(), + "Wildcard_M": addr.SvcWildcard.Multicast(), + } + for src, svc := range svcs { + t.Run(src, func(t *testing.T) { + h, err := addr.ParseHost(src) + require.NoError(t, err) + require.Equal(t, addr.HostTypeSVC, h.Type()) + assert.Equal(t, svc, h.SVC()) + }) + } +} diff --git a/pkg/addr/svc.go b/pkg/addr/svc.go index 84128f566b..0187b5e509 100644 --- a/pkg/addr/svc.go +++ b/pkg/addr/svc.go @@ -36,13 +36,17 @@ var ( ) // SVC is a SCION service address. +// A service address is a short identifier for the service type, and a +// flag-bit to for multicast. +// The package's SVC constant values are defined without multicast. +// The Multicast and Base methods set/unset the multicast flag. type SVC uint16 // ParseSVC returns the SVC address corresponding to str. For anycast // SVC addresses, use CS_A and DS_A; shorthand versions without // the _A suffix (e.g., CS) also return anycast SVC addresses. For multicast, // use CS_M, and DS_M. -func ParseSVC(str string) SVC { +func ParseSVC(str string) (SVC, error) { var m SVC switch { case strings.HasSuffix(str, "_A"): @@ -53,28 +57,32 @@ func ParseSVC(str string) SVC { } switch str { case "DS": - return SvcDS | m + return SvcDS | m, nil case "CS": - return SvcCS | m + return SvcCS | m, nil case "Wildcard": - return SvcWildcard | m + return SvcWildcard | m, nil default: - return SvcNone + return SvcNone, serrors.New("invalid service address", "value", str) } } +// IsMulticast returns the value of the multicast flag. func (h SVC) IsMulticast() bool { return (h & SVCMcast) != 0 } +// Base returns the SVC identifier with the multicast flag unset. func (h SVC) Base() SVC { return h & ^SVCMcast } +// Multicast returns the SVC identifier with the multicast flag set. func (h SVC) Multicast() SVC { return h | SVCMcast } +// XXX(matzf): change this to the format accepted by ParseSVC? func (h SVC) String() string { name := h.BaseString() cast := 'A' diff --git a/pkg/addr/svc_test.go b/pkg/addr/svc_test.go new file mode 100644 index 0000000000..8a7db0fe41 --- /dev/null +++ b/pkg/addr/svc_test.go @@ -0,0 +1,60 @@ +// Copyright 2023 SCION Association +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package addr_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/scionproto/scion/pkg/addr" +) + +func TestParseSVC(t *testing.T) { + invalid := []string{ + "", + "garbage", + "CS_Y", + "cs_a", + "cs_m", + "CS ", + " CS", + } + for _, src := range invalid { + t.Run(src, func(t *testing.T) { + _, err := addr.ParseSVC(src) + assert.Error(t, err) + }) + } + + valid := map[string]addr.SVC{ + "CS": addr.SvcCS, + "DS": addr.SvcDS, + "Wildcard": addr.SvcWildcard, + "CS_A": addr.SvcCS, + "DS_A": addr.SvcDS, + "Wildcard_A": addr.SvcWildcard, + "CS_M": addr.SvcCS.Multicast(), + "DS_M": addr.SvcDS.Multicast(), + "Wildcard_M": addr.SvcWildcard.Multicast(), + } + for src, svc := range valid { + t.Run(src, func(t *testing.T) { + v, err := addr.ParseSVC(src) + assert.NoError(t, err) + assert.Equal(t, svc, v) + }) + } +}