diff --git a/util/netconv/netconv.go b/util/netconv/netconv.go new file mode 100644 index 000000000..04b5760fb --- /dev/null +++ b/util/netconv/netconv.go @@ -0,0 +1,63 @@ +// Copyright (c) 2022 Tailscale Inc & 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 netconv provides utilities to convert between netaddr and netip. +// To convert from a net.IP, use the netaddr/netip API. +package netconv + +import ( + "net/netip" + + "inet.af/netaddr" +) + +// AsIP returns a as a netaddr.IP. +func AsIP(a netip.Addr) netaddr.IP { + switch { + case a.Is4(): + return netaddr.IPFrom4(a.As4()) + case a.Is6(): + return netaddr.IPv6Raw(a.As16()).WithZone(a.Zone()) + } + return netaddr.IP{} +} + +// AsAddr returns a as a netip.IP. +func AsAddr(a netaddr.IP) netip.Addr { + switch { + case a.Is4(): + return netip.AddrFrom4(a.As4()) + case a.Is6(): + return netip.AddrFrom16(a.As16()).WithZone(a.Zone()) + } + return netip.Addr{} +} + +// AsIPPrefix returns a as a netaddr.IPPrefix. +// If a has Bits of -1, indicating an invalid bits, +// the returned IPPrefix will have Bits of 255. +// AsIPPrefix and AsPrefix do not +// round trip for invalid Bits values. +func AsIPPrefix(a netip.Prefix) netaddr.IPPrefix { + return netaddr.IPPrefixFrom(AsIP(a.Addr()), uint8(a.Bits())) +} + +// AsPrefix returns a as a netip.Prefix. +// If a has an invalid Bits value, +// the returned Prefix will have Bits of -1. +// AsIPPrefix and AsPrefix do not +// round trip for invalid Bits values. +func AsPrefix(a netaddr.IPPrefix) netip.Prefix { + return netip.PrefixFrom(AsAddr(a.IP()), int(a.Bits())) +} + +// AsIPPort returns a as a netaddr.IPPort. +func AsIPPort(a netip.AddrPort) netaddr.IPPort { + return netaddr.IPPortFrom(AsIP(a.Addr()), a.Port()) +} + +// AsAddrPort returns a as a netip.AddrPort. +func AsAddrPort(a netaddr.IPPort) netip.AddrPort { + return netip.AddrPortFrom(AsAddr(a.IP()), a.Port()) +} diff --git a/util/netconv/netconv_test.go b/util/netconv/netconv_test.go new file mode 100644 index 000000000..376cc4cd3 --- /dev/null +++ b/util/netconv/netconv_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2022 Tailscale Inc & 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 netconv + +import ( + "net/netip" + "testing" + + qt "github.com/frankban/quicktest" + "inet.af/netaddr" +) + +func TestAddr(t *testing.T) { + c := qt.New(t) + + c.Assert(netip.Addr{}, qt.Equals, AsAddr(netaddr.IP{})) + c.Assert(netaddr.IP{}, qt.Equals, AsIP(netip.Addr{})) + + // Cover IPv4, IPv6, 4in6, and zones. + addrStrs := []string{ + "0.0.0.0", + "123.45.67.89", + "::", + "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b", + "fd7a:115c:a1e0:ab12:4843:cd96:626b:430b%eth0", + "::ffff:192.0.2.128", + "::ffff:192.0.2.128%eth0", + } + for _, s := range addrStrs { + ip := netaddr.MustParseIP(s) + addr := netip.MustParseAddr(s) + c.Assert(addr, qt.Equals, AsAddr(ip)) + c.Assert(ip, qt.Equals, AsIP(addr)) + } +} + +func TestAddrPort(t *testing.T) { + c := qt.New(t) + + c.Assert(netip.AddrPort{}, qt.Equals, AsAddrPort(netaddr.IPPort{})) + c.Assert(netaddr.IPPort{}, qt.Equals, AsIPPort(netip.AddrPort{})) + + // Test just a single AddrPort conversion; + // there's almost nothing happening in the code. + portStr := "1.2.4.5:8" + ipPort := netaddr.MustParseIPPort(portStr) + ap := netip.MustParseAddrPort(portStr) + c.Assert(ipPort, qt.Equals, AsIPPort(ap)) + c.Assert(ap, qt.Equals, AsAddrPort(ipPort)) +} + +func TestPrefix(t *testing.T) { + c := qt.New(t) + + // The interesting Prefix cases are invalid bits. + addr := netip.MustParseAddr("1.2.3.4") + ip := AsIP(addr) + + tests := []struct { + ipp netaddr.IPPrefix // input IPPrefix, output from converting pfx + pfx netip.Prefix // input Prefix, output from converting ipp + out netaddr.IPPrefix // output from converting pfx + }{ + {netaddr.IPPrefix{}, netip.Prefix{}, netaddr.IPPrefix{}}, + {netaddr.IPPrefixFrom(ip, 24), netip.PrefixFrom(addr, 24), netaddr.IPPrefixFrom(ip, 24)}, + {netaddr.IPPrefixFrom(ip, 255), netip.PrefixFrom(addr, -1), netaddr.IPPrefixFrom(ip, 255)}, + {netaddr.IPPrefixFrom(ip, 204), netip.PrefixFrom(addr, -1), netaddr.IPPrefixFrom(ip, 255)}, + } + + for _, test := range tests { + c.Assert(test.out, qt.Equals, AsIPPrefix(test.pfx)) + c.Assert(test.pfx, qt.Equals, AsPrefix(test.ipp)) + } +}