Skip to content
This repository has been archived by the owner on May 25, 2023. It is now read-only.

Commit

Permalink
Add IPv6Raw, IPFrom16, IP.As16, FromStdIPRaw, modify FromStdIP
Browse files Browse the repository at this point in the history
Fixes #27
Fixes #34
  • Loading branch information
bradfitz committed May 12, 2020
1 parent 73e6749 commit 19ebe88
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 3 deletions.
60 changes: 60 additions & 0 deletions netaddr.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,25 @@ func IPv4(a, b, c, d uint8) IP {
return IP{v4Addr{a, b, c, d}}
}

// IPv6Raw returns the IPv6 address given by the bytes in addr,
// without an implicit Unmap call to unmap any v6-mapped IPv4
// address.
func IPv6Raw(addr [16]byte) IP {
return IP{v6Addr(addr)}
}

// IPFrom16 returns the IP address given by the bytes in addr,
// unmapping any v6-mapped IPv4 address.
//
// It is equivalent to calling IPv6Raw(addr).Unmap() but slightly more
// efficient.
func IPFrom16(addr [16]byte) IP {
if string(addr[:len(mapped4Prefix)]) == mapped4Prefix {
return IPv4(addr[12], addr[13], addr[14], addr[15])
}
return IP{v6Addr(addr)}
}

// ParseIP parses s as an IP address, returning the result. The string
// s can be in dotted decimal ("192.0.2.1"), IPv6 ("2001:db8::68"),
// or IPv6 with a scoped addressing zone ("fe80::1cc0:3e8c:119f:c2e1%ens18").
Expand Down Expand Up @@ -145,8 +164,37 @@ func ParseIP(s string) (IP, error) {
}

// FromStdIP returns an IP from the standard library's IP type.
//
// If std is invalid, ok is false.
//
// FromStdIP implicitly unmaps IPv6-mapped IPv4 addresses. That is, if
// len(std) == 16 and contains an IPv4 address, only the IPv4 part is
// returned, without the IPv6 wrapper. This is the common form returned by
// the standard library's ParseIP: https://play.golang.org/p/qdjylUkKWxl.
// To convert a standard library IP without the implicit unmapping, use
// FromStdIPRaw.
func FromStdIP(std net.IP) (ip IP, ok bool) {
if len(std) == 16 && string(std[:len(mapped4Prefix)]) == mapped4Prefix {
std = std[len(mapped4Prefix):]
}
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
}

// FromStdIPRaw returns an IP from the standard library's IP type.
// If std is invalid, ok is false.
// Unlike FromStdIP, FromStdIPRaw does not do an implicit Unmap if
// len(std) == 16 and contains an IPv6-mapped IPv4 address.
func FromStdIPRaw(std net.IP) (ip IP, ok bool) {
switch len(std) {
case 4:
var a v4Addr
Expand Down Expand Up @@ -410,6 +458,18 @@ func (ip IP) Prefix(bits uint8) (IPPrefix, error) {
}, nil
}

// As16 returns the IP address in its 16 byte representation.
// IPv4 addresses are returned in their v6-mapped form.
// IPv6 addresses with zones are returned without their zone (use the
// Zone method to get it).
// The ip zero value returns all zeroes.
func (ip IP) As16() [16]byte {
if ip.ipImpl == nil {
return [16]byte{}
}
return ip.ipImpl.as16()
}

// String returns the string form of the IP address ip.
// It returns one of 4 forms:
//
Expand Down
90 changes: 87 additions & 3 deletions netaddr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,90 @@ func TestIPIPAddr(t *testing.T) {
}
}

func TestFromStdIP(t *testing.T) {
tests := []struct {
name string
fn func(net.IP) (IP, bool)
std net.IP
want IP
}{
{
name: "v4",
fn: FromStdIP,
std: []byte{1, 2, 3, 4},
want: IPv4(1, 2, 3, 4),
},
{
name: "v6",
fn: FromStdIP,
std: net.ParseIP("::1"),
want: IPv6Raw([...]byte{15: 1}),
},
{
name: "4in6-unmap",
fn: FromStdIP,
std: net.ParseIP("1.2.3.4"),
want: IPv4(1, 2, 3, 4),
},
{
name: "4in6-raw",
fn: FromStdIPRaw,
std: net.ParseIP("1.2.3.4"),
want: IPv6Raw([...]byte{10: 0xff, 11: 0xff, 12: 1, 13: 2, 14: 3, 15: 4}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := tt.fn(tt.std)
if got != tt.want {
t.Errorf("got (%#v, %v); want %#v", got, ok, tt.want)
}
})
}
}

func TestIPFrom16AndIPv6Raw(t *testing.T) {
tests := []struct {
name string
fn func([16]byte) IP
in [16]byte
want IP
}{
{
name: "v6-raw",
fn: IPv6Raw,
in: [...]byte{15: 1},
want: IP{v6Addr{15: 1}},
},
{
name: "v6-from16",
fn: IPFrom16,
in: [...]byte{15: 1},
want: IP{v6Addr{15: 1}},
},
{
name: "v4-raw",
fn: IPv6Raw,
in: [...]byte{10: 0xff, 11: 0xff, 12: 1, 13: 2, 14: 3, 15: 4},
want: IP{v6Addr{10: 0xff, 11: 0xff, 12: 1, 13: 2, 14: 3, 15: 4}},
},
{
name: "v4-from16",
fn: IPFrom16,
in: [...]byte{10: 0xff, 11: 0xff, 12: 1, 13: 2, 14: 3, 15: 4},
want: IPv4(1, 2, 3, 4),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.fn(tt.in)
if got != tt.want {
t.Errorf("got %#v; want %#v", got, tt.want)
}
})
}
}

func TestFromStdAddr(t *testing.T) {
tests := []struct {
name string
Expand All @@ -153,15 +237,15 @@ func TestFromStdAddr(t *testing.T) {
IP: net.ParseIP("1.2.3.4"),
Port: 567,
},
want: IPPort{mustIP("1.2.3.4").Unmap(), 567},
want: IPPort{mustIP("1.2.3.4"), 567},
},
{
name: "v6",
ua: &net.UDPAddr{
IP: net.ParseIP("::1"),
Port: 567,
},
want: IPPort{mustIP("::1").Unmap(), 567},
want: IPPort{mustIP("::1"), 567},
},
{
name: "v6zone",
Expand All @@ -170,7 +254,7 @@ func TestFromStdAddr(t *testing.T) {
Port: 567,
Zone: "foo",
},
want: IPPort{mustIP("::1").Unmap().WithZone("foo"), 567},
want: IPPort{mustIP("::1").WithZone("foo"), 567},
},
{
name: "v4zone_bad",
Expand Down

0 comments on commit 19ebe88

Please sign in to comment.