Repository: inetaf/netaddr
Pull request #25 (2020-04-25 17:22:00)

netaddr: add initial well-known IP addresses
Signed-off-by: Matt Layher <mdlayher@gmail.com>

These are the only two "constants" I need at the moment. The standard library exposes a few more.

It'd be nice if these could be immutable but I think this is probably reasonable.

bradfitz: It'd be ideal if we could do this without init work. And something the linker could discard. 
mdlayher: Yeah. I am curious if there is something more clever that could be done, but I also know that being able to check equality via `==` was an explicit goal with this type.

Here's a thought that comes to mind:

```go
type WellKnown int

const (
    Unspecified WellKnown = iota
    IPv6LinkLocalAllNodes
)

func (ip IP) Is(known WellKnown) bool {}

// Usage:
ip, _ := netaddr.ParseIP("::")
fmt.Println(ip.Is(netaddr.Unspecified))
// true
```
bradfitz: No, I was thinking just laying out the value in memory to help the linker (see https://github.com/golang/go/issues/19533 and https://github.com/golang/go/issues/38450#issuecomment-614248472). Not a new type or API.

Something like:

```go
var ip6Unspecified = IP{v6Addr{}}
func IPv6Unspecified() IP { return ip6Unspecified }
```

Then:

* linker can discard it all (confirm that the unexported var can be discard to and doesn't make an init func?)
* it doesn't allocate at runtuime
* no new types
* people can't modify exported package vars

mdlayher: I see! I'm good with that.
mdlayher: Updated.

Patch:
diff --git a/netaddr.go b/netaddr.go
index dccd128..3906a38 100644
--- a/netaddr.go
+++ b/netaddr.go
@@ -97,6 +97,22 @@ func (ip v6AddrZone) String() string {
 	return (&net.IPAddr{IP: net.IP(ip.v6Addr[:]), Zone: ip.zone}).String()
 }
 
+// Well known IP addresses which are accessed only through exported functions,
+// so the linker can eliminate them if they are unused.
+var (
+	// ff02::1
+	ipv6LinkLocalAllNodes = IP{v6Addr{0: 0xff, 1: 0x02, 15: 0x01}}
+	// ::
+	ipv6Unspecified = IP{v6Addr{}}
+)
+
+// IPv6LinkLocalAllNodes returns the IPv6 link-local all nodes multicast
+// address ff02::1.
+func IPv6LinkLocalAllNodes() IP { return ipv6LinkLocalAllNodes }
+
+// IPv6Unspecified returns the IPv6 unspecified address ::.
+func IPv6Unspecified() IP { return ipv6Unspecified }
+
 // IPv4 returns the IP of the IPv4 address a.b.c.d.
 func IPv4(a, b, c, d uint8) IP {
 	return IP{v4Addr{a, b, c, d}}
@@ -499,31 +515,6 @@ type IPPrefix struct {
 	Bits uint8
 }
 
-// FromStdIPNet returns an IPPrefix from the standard library's IPNet type.
-// If std is invalid, ok is false.
-func FromStdIPNet(std *net.IPNet) (prefix IPPrefix, ok bool) {
-	ip, ok := FromStdIP(std.IP)
-	if !ok {
-		return IPPrefix{}, false
-	}
-
-	if l := len(std.Mask); l != net.IPv4len && l != net.IPv6len {
-		// Invalid mask.
-		return IPPrefix{}, false
-	}
-
-	ones, bits := std.Mask.Size()
-	if ones == 0 && bits == 0 {
-		// IPPrefix does not support non-contiguous masks.
-		return IPPrefix{}, false
-	}
-
-	return IPPrefix{
-		IP:   ip,
-		Bits: uint8(ones),
-	}, true
-}
-
 // ParseIPPrefix parses s as an IP address prefix.
 // The string can be in the form "192.168.1.0/24" or "2001::db8::/32",
 // the CIDR notation defined in RFC 4632 and RFC 4291.


Test patch:
diff --git a/netaddr_test.go b/netaddr_test.go
index d0ddbd9..bfdbcb3 100644
--- a/netaddr_test.go
+++ b/netaddr_test.go
@@ -302,6 +302,36 @@ func TestIPProperties(t *testing.T) {
 	}
 }
 
+func TestIPWellKnown(t *testing.T) {
+	tests := []struct {
+		name string
+		ip   IP
+		std  net.IP
+	}{
+		{
+			name: "IPv6 link-local all nodes",
+			ip:   IPv6LinkLocalAllNodes(),
+			std:  net.IPv6linklocalallnodes,
+		},
+		{
+			name: "IPv6 unspecified",
+			ip:   IPv6Unspecified(),
+			std:  net.IPv6unspecified,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			want := tt.std.String()
+			got := tt.ip.String()
+
+			if got != want {
+				t.Fatalf("got %s, want %s", got, want)
+			}
+		})
+	}
+}
+
 func TestLess(t *testing.T) {
 	tests := []struct {
 		a, b IP
@@ -618,63 +648,6 @@ func TestIPPrefix(t *testing.T) {
 	}
 }
 
-func TestFromStdIPNet(t *testing.T) {
-	tests := []struct {
-		name string
-		std  *net.IPNet
-		want IPPrefix
-	}{
-		{
-			name: "invalid IP",
-			std: &net.IPNet{
-				IP: net.IP{0xff},
-			},
-		},
-		{
-			name: "invalid mask",
-			std: &net.IPNet{
-				IP:   net.IPv6loopback,
-				Mask: nil,
-			},
-		},
-		{
-			name: "non-contiguous mask",
-			std: &net.IPNet{
-				IP:   net.IPv4(192, 0, 2, 0).To4(),
-				Mask: net.IPv4Mask(255, 0, 255, 0),
-			},
-		},
-		{
-			name: "IPv4",
-			std: &net.IPNet{
-				IP:   net.IPv4(192, 0, 2, 0).To4(),
-				Mask: net.CIDRMask(24, 32),
-			},
-			want: mustIPPrefix("192.0.2.0/24"),
-		},
-		{
-			name: "IPv6",
-			std: &net.IPNet{
-				IP:   net.ParseIP("2001:db8::"),
-				Mask: net.CIDRMask(64, 128),
-			},
-			want: mustIPPrefix("2001:db8::/64"),
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got, ok := FromStdIPNet(tt.std)
-			if !ok && got != (IPPrefix{}) {
-				t.Fatalf("!ok but non-zero result")
-			}
-
-			if got != tt.want {
-				t.Errorf("FromStdIPNet(%q) = %+v; want %+v", tt.std, got, tt.want)
-			}
-		})
-	}
-}
-
 func TestParseIPPrefixAllocs(t *testing.T) {
 	tests := []struct {
 		ip    string