diff --git a/pkg/snet/udpaddr.go b/pkg/snet/udpaddr.go index ea88ff4246..ea7ad91ee5 100644 --- a/pkg/snet/udpaddr.go +++ b/pkg/snet/udpaddr.go @@ -17,7 +17,9 @@ package snet import ( "fmt" "net" + "net/netip" "regexp" + "strconv" "strings" "inet.af/netaddr" @@ -26,7 +28,7 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" ) -var addrRegexp = regexp.MustCompile(`^(?P\d+-[\d:A-Fa-f]+),(?P.+)$`) +var addrRegexpLegacy = regexp.MustCompile(`^(?P\d+-[\d:A-Fa-f]+),(?P.+)$`) // UDPAddr to be used when UDP host. type UDPAddr struct { @@ -37,7 +39,53 @@ type UDPAddr struct { } // ParseUDPAddr converts an address string to a SCION address. -// The supported formats are: +func ParseUDPAddr(s string) (*UDPAddr, error) { + addr, err := parseUDPAddr(s) + if err != nil { + return parseUDPAddrLegacy(s) + } + return addr, nil +} + +// The supported formats are based on the extensions of RFC 3986: +// https://scion.docs.anapaya.net/en/latest/uri.html#scion-udp +// +// Examples: +// - [isd-as,ipv4]:port (e.g., [1-ff00:0:110,192.0.2.1]:80) +// - [isd-as,ipv6%zone]:port (e.g., [1-ff00:0:110,2001:DB8::1%zone]:80) +func parseUDPAddr(s string) (*UDPAddr, error) { + host, port, err := net.SplitHostPort(s) + if err != nil { + return nil, serrors.WrapStr("invalid address: split host:port", err, "addr", s) + } + parts := strings.Split(host, ",") + if len(parts) != 2 { + return nil, serrors.New("invalid address: host parts invalid", + "expected", 2, "actual", len(parts)) + } + ia, err := addr.ParseIA(parts[0]) + if err != nil { + return nil, serrors.WrapStr("invalid address: IA not parsable", err, "ia", ia) + } + ip, err := netip.ParseAddr(parts[1]) + if err != nil { + return nil, serrors.WrapStr("invalid address: ip not parsable", err, "ip", parts[1]) + } + p, err := strconv.Atoi(port) + if err != nil { + return nil, serrors.WrapStr("invalid address: port invalid", err, "port", port) + } + udp := &net.UDPAddr{ + IP: ip.AsSlice(), + Zone: ip.Zone(), + Port: p, + } + + return &UDPAddr{IA: ia, Host: udp}, nil +} + +// The legacy format of the SCION address URI encoding allows multiple different encodings. +// The supported legacy formats are: // // Recommended: // - isd-as,ipv4:port (e.g., 1-ff00:0:300,192.168.1.1:8080) @@ -56,7 +104,7 @@ type UDPAddr struct { // Not supported: // - isd-as,ipv6:port (caveat if ipv6:port builds a valid ipv6 address, // it will successfully parse as ipv6 without error) -func ParseUDPAddr(s string) (*UDPAddr, error) { +func parseUDPAddrLegacy(s string) (*UDPAddr, error) { rawIA, rawHost, err := parseAddr(s) if err != nil { return nil, err @@ -128,7 +176,7 @@ func CopyUDPAddr(a *net.UDPAddr) *net.UDPAddr { } func parseAddr(s string) (string, string, error) { - match := addrRegexp.FindStringSubmatch(s) + match := addrRegexpLegacy.FindStringSubmatch(s) if len(match) != 3 { return "", "", serrors.New("invalid address: regex match failed", "addr", s) } diff --git a/pkg/snet/udpaddr_test.go b/pkg/snet/udpaddr_test.go index 2e0abf033d..c03ace511e 100644 --- a/pkg/snet/udpaddr_test.go +++ b/pkg/snet/udpaddr_test.go @@ -156,6 +156,12 @@ func TestParseUDPAddr(t *testing.T) { {address: "1-ff00:0:300,[1.2.3.4]:70000", isError: true}, {address: "1-ff00:0:300,[1.2.3.4]]", isError: true}, {address: "1-ff00:0:300,::1:60000", isError: true}, + {address: "[1-ff00:0:110,1.2.3.4]:70:300", isError: true}, + {address: "[1-ff00:0:110,1.2.3.4,80]:80", isError: true}, + {address: "[1-ff00:0:110,1.2.3.4]", isError: true}, + {address: "[1-,127.0.0.1]:80", isError: true}, + {address: "[1-ff00:0:110,1.2.3.4]", isError: true}, + {address: "[1-ff00:0:110,::1%some-zone]", isError: true}, {address: "", isError: true}, {address: "1-ff00:0:300,[1.2.3.4]:80", ia: "1-ff00:0:300", @@ -220,6 +226,26 @@ func TestParseUDPAddr(t *testing.T) { port: 0, zone: "some-zone", }, + {address: "[1-ff00:0:110,192.0.2.1]:80", + ia: "1-ff00:0:110", + host: "192.0.2.1", + port: 80, + }, + {address: "[1-ff00:0:110,2001:DB8::1]:80", + ia: "1-ff00:0:110", + host: "2001:DB8::1", + port: 80, + }, + {address: "[1-64496,2001:DB8::1]:80", + ia: "1-64496", + host: "2001:DB8::1", + port: 80, + }, + {address: "[1-64496,2001:DB8::1]:60000", + ia: "1-64496", + host: "2001:DB8::1", + port: 60000, + }, } for _, test := range tests { t.Logf("given address %q", test.address) @@ -229,7 +255,8 @@ func TestParseUDPAddr(t *testing.T) { } else { assert.NoError(t, err) assert.Equal(t, test.ia, a.IA.String()) - assert.Equal(t, net.ParseIP(test.host), a.Host.IP) + ip := net.ParseIP(test.host) + assert.True(t, ip.Equal(a.Host.IP)) assert.Equal(t, test.port, a.Host.Port) assert.Equal(t, test.zone, a.Host.Zone) }