Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pkg/snet: support URI style UDPAddr encoding #4254

Merged
merged 10 commits into from
Oct 6, 2022
56 changes: 52 additions & 4 deletions pkg/snet/udpaddr.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ package snet
import (
"fmt"
"net"
"net/netip"
"regexp"
"strconv"
"strings"

"inet.af/netaddr"
Expand All @@ -26,7 +28,7 @@ import (
"github.com/scionproto/scion/pkg/private/serrors"
)

var addrRegexp = regexp.MustCompile(`^(?P<ia>\d+-[\d:A-Fa-f]+),(?P<host>.+)$`)
var addrRegexpLegacy = regexp.MustCompile(`^(?P<ia>\d+-[\d:A-Fa-f]+),(?P<host>.+)$`)

// UDPAddr to be used when UDP host.
type UDPAddr struct {
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
29 changes: 28 additions & 1 deletion pkg/snet/udpaddr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand Down