Repository: inetaf/netaddr
Pull request #18 (2020-04-17 16:03:22)

netaddr: add IP.IsLinkLocalUnicast method
Signed-off-by: Matt Layher <mdlayher@gmail.com>

With this and #16, all of my CoreRAD needs have been met by netaddr. I also slightly adjusted the unicast fixtures to be more correct and not overlap with link-local.


Patch:
diff --git a/netaddr.go b/netaddr.go
index da844cb..eb58af8 100644
--- a/netaddr.go
+++ b/netaddr.go
@@ -123,22 +123,6 @@ func ParseIP(s string) (IP, error) {
 	return IP{v6}, nil
 }
 
-// FromStdIP returns an IP from the standard library's IP type.
-// If std is invalid, ok is false.
-func FromStdIP(std net.IP) (ip IP, ok bool) {
-	switch len(std) {
-	case 4:
-		var a v4Addr
-		copy(a[:], std)
-		return IP{a}, true
-	case 16:
-		var a v6Addr
-		copy(a[:], std)
-		return IP{a}, true
-	}
-	return IP{}, false
-}
-
 // Zone returns ip's IPv6 scoped addressing zone, if any.
 func (ip IP) Zone() string {
 	if v6z, ok := ip.ipImpl.(v6AddrZone); ok {
@@ -250,6 +234,24 @@ func (ip IP) Unmap() IP {
 	return IP{v4Addr{a[12], a[13], a[14], a[15]}}
 }
 
+// IsLinkLocalUnicast reports whether ip is a link-local unicast address.
+// If ip is the zero value, it will return false.
+func (ip IP) IsLinkLocalUnicast() bool {
+	// See: https://en.wikipedia.org/wiki/Link-local_address.
+	switch ip := ip.ipImpl.(type) {
+	case nil:
+		return false
+	case v4Addr:
+		return ip[0] == 169 && ip[1] == 254
+	case v6Addr:
+		return ip[0] == 0xfe && ip[1] == 0x80
+	case v6AddrZone:
+		return ip.v6Addr[0] == 0xfe && ip.v6Addr[1] == 0x80
+	default:
+		panic("netaddr: unhandled ipImpl representation")
+	}
+}
+
 // IsMulticast reports whether ip is a multicast address. If ip is the zero
 // value, it will return false.
 func (ip IP) IsMulticast() bool {


Test patch:
diff --git a/netaddr_test.go b/netaddr_test.go
index b539757..1c8df47 100644
--- a/netaddr_test.go
+++ b/netaddr_test.go
@@ -128,38 +128,32 @@ func TestIPIPAddr(t *testing.T) {
 			if !reflect.DeepEqual(tt.ipa, got) {
 				t.Errorf("IPAddr = %+v; want %+v", got, tt.ipa)
 			}
-
-			if got.Zone == "" && tt.ip != (IP{}) {
-				back, ok := FromStdIP(got.IP)
-				if !ok {
-					t.Errorf("FromStdIP failed")
-				} else if back != tt.ip {
-					t.Errorf("FromStdIP = %v; want %v", back, tt.ip)
-				}
-			}
 		})
 	}
 }
 
 func TestIPProperties(t *testing.T) {
-	// TODO: expand test with more Is* property checks.
-
 	var (
 		nilIP IP
 
 		unicast4     = mustIP("192.0.2.1")
-		unicast6     = mustIP("2001:db::1")
-		unicastZone6 = mustIP("fe80::1%eth0")
+		unicast6     = mustIP("2001:db8::1")
+		unicastZone6 = mustIP("2001:db8::1%eth0")
 
 		multicast4     = mustIP("224.0.0.1")
 		multicast6     = mustIP("ff02::1")
 		multicastZone6 = mustIP("ff02::1%eth0")
+
+		llu4     = mustIP("169.254.0.1")
+		llu6     = mustIP("fe80::1")
+		lluZone6 = mustIP("fe80::1%eth0")
 	)
 
 	tests := []struct {
-		name      string
-		ip        IP
-		multicast bool
+		name             string
+		ip               IP
+		multicast        bool
+		linkLocalUnicast bool
 	}{
 		{
 			name: "nil",
@@ -192,6 +186,21 @@ func TestIPProperties(t *testing.T) {
 			ip:        multicastZone6,
 			multicast: true,
 		},
+		{
+			name:             "link-local unicast v4Addr",
+			ip:               llu4,
+			linkLocalUnicast: true,
+		},
+		{
+			name:             "link-local unicast v6Addr",
+			ip:               llu6,
+			linkLocalUnicast: true,
+		},
+		{
+			name:             "link-local unicast v6AddrZone",
+			ip:               lluZone6,
+			linkLocalUnicast: true,
+		},
 	}
 
 	for _, tt := range tests {
@@ -200,6 +209,11 @@ func TestIPProperties(t *testing.T) {
 			if multicast != tt.multicast {
 				t.Errorf("IsMulticast = %v; want %v", multicast, tt.multicast)
 			}
+
+			llu := tt.ip.IsLinkLocalUnicast()
+			if llu != tt.linkLocalUnicast {
+				t.Errorf("IsLinkLocalUnicast = %v; want %v", llu, tt.linkLocalUnicast)
+			}
 		})
 	}
 }