ADDED: * netx: ** docs updated ** added NOTES.adoc for more extensive info that doesn't need to be in the library docs ** Addr4in6Compat() ** Addr4in6Mapped() ** Addr4in6Mapped() ** HasSubnet() ** IP4MapPfx4to6() ** IP4MapPfx6to4() ** IPSetCombined() ** IPSetFrom() ** IPSetFromNative() ** IsAddr4Compat() ** IsPublic() ** MustIPSetCombined() ** MustIPSetFrom() ** MustIPSetFromNative() ** Pfx4in6Compat() (untested) ** Pfx4in6Mapped() (untested) ** UnmapAddr() ** UnmapPfx() ** Better support in other functions for IPv4-Compatible/IPv4-Mapped addressing
6.2 KiB
netx Notes
1. IPv4 Addresses as IPv6
There are, effectively, two RFC-specified ways of representing an IPv4 address in IPv6 notation/address space/context.
Per RFC 4291 § 2.5.5:
-
IPv4-Compatible IPv6 Address (RFC 4291 § 2.5.5.1) —
::/96-
e.g.
::192.0.2.10
-
-
IPv4-Mapped IPv6 Address (RFC 4291 § 2.5.5.2) —
::ffff:0:0/96-
e.g.
::ffff:192.0.2.10
-
The former is considered deprecated, whereas the latter is considered the modern proper representation. Some naïve or legacy software, however, still use the former.
An IPv6 address is 128 bits (hence a prefix length/mask of /128 representing a complete address), or 16 bytes (128 / 8).
An IPv4 address is 32 bits (hence a prefix length/mask of /32 representing a complete address), or 4 bytes (32 / 8).
This is why both the "IPv4-compatible" and "IPv4-mapped" IPv6 addresses are within a reserved prefix length of 96 bits (/96) - it leaves a remaining
32 bits (128 - 96) for a complete IPv4 address.
|
Important
|
The IPv4-Compatible and IPv4-Mapped prefixes are not intended for routing! They are used for syntactic/contextual use. This library may add supporting functions for these translation prefixes at a later time. |
1.1. Addresses
While net/netip.ParseAddr (or net/netip.MustParseAddr) will handle
parsing the "IPv4-Compatible" format just fine, it will treat it as an IPv6 address — not an "IPv6-wrapped/IPv6-mapped IPv4" address.
For example…
package main
import (
"fmt"
"net/netip"
)
func main() {
fmt.Printf(
"::192.0.2.10 -> %s\n",
netip.MustParseAddr("::192.0.2.10").String(),
)
}
will print:
::192.0.2.10 -> ::c000:20a
For addresses, this is an easy enough fix:
-
Take the IPv6 as a byte slice,
b(b = ip4in6.AsSlice()) -
Slice out the last 4 bytes as
b2(b2 = b[12:16]) -
Parse
b2as an IPv4 address (ip4 = netip.FromSlice(b2))
For the visual learners, the original address (::192.0.2.10 or, more properly, ::c000:20a) can be represented in multiple ways.
For instance…
-
A "canonical"/shorted IPv6 address:
::c000:20a -
A fully "exploded" IPv6 address:
0000:0000:0000:0000:0000:0000:c000:020a -
Hex bytes:
-
00:00:00:00:00:00:00:00:00:00:00:00:C0:00:02:0A -
0x000000000000000000000000C000020A -
Via
xxd(1):# xxd -c4 -g 1 00000000: 00 00 00 00 .... 00000004: 00 00 00 00 .... 00000008: 00 00 00 00 .... 0000000c: c0 00 02 0a .... -
Via
hexdump(1):# hexdump -v -e '"%08_ax: " 4/1 "0x%02X ""\n"' 00000000: 0x00 0x00 0x00 0x00 00000004: 0x00 0x00 0x00 0x00 00000008: 0x00 0x00 0x00 0x00 0000000c: 0xC0 0x00 0x02 0x0A
-
-
A Go byte slice:
-
Either as explicit
byte:[]byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x02, 0x0A, } -
Or as
uint8(abyteis, quite literally, just a type alias foruint8):[]byte{ uint8(0), uint8(0), uint8(0), uint8(0), uint8(0), uint8(0), uint8(0), uint8(0), uint8(0), uint8(0), uint8(0), uint8(0), uint8(192), uint8(0), uint8(2), uint8(10), }
-
When seeing it represented as a slice of uint8, some things should start to click mentally looking at the last
four bytes.
An IPv4 address can then be derived by taking the last four bytes ([]byte{…}[12:16]):
-
[]byte{0xC0, 0x00, 0x02, 0x0A} -
[]byte(uint8(192), uint8(0), uint8(2), uint8(10))
As you may have guessed by now, this is the byte format of the IPv4 address 192.0.2.10.
The netx.UnmapAddr() function handles this cleanly.
If you instead prefer to think in prefixing/masking, the last four bytes are 32 bits (8 * 4) — the size of an IPv4 address.
The "compatible" mapping is '::/96', and an IPv6 address is 128 bits (16 bytes, 128 / 8) total — 128 - 96 == 32 bits, or 4 bytes.
1.2. Prefixes
Prefixes are a little more tricky.
Prefixing for IPv4-Compatible/IPv4-Mapped IPv6 addresses works a bit oddly because generally the "4-in-6" addresses are meant to be purely representative - a way to represent IPv4 addresses in IPv6 implementations.
But there are cases where one needs a prefix represented.
This leads to the awkward case where an IPv4 prefix length (e.g. /24) needs to be represented as an IPv6 prefix length (e.g. /120).
While this module (netx) does offer helper functions to calculate this (netx.IP4MapPfx6to4(), netx.IP4MapPfx4to6()),
the netx.UnmapPfx() function is offered as a convenience function that handles the translation from an IPv6 representation
of an IPv4 prefix into a fully "native" IPv4 prefix.
|
Note
|
|
This is achieved by:
-
Taking the address from the prefix (
ip4in6 = pfx4in6.Addr())-
And converting to native IPv4 (
ip4 = netx.UnmapAddr(ip4in6))
-
-
Taking the IPv6 prefix length (
len6 = pfx4in6.Bits())-
And converting it to an IPv4 prefix length (
len4 = netx.IP4MapPfx6to4(uint8(len6)))
-
-
Then re-assembling (
pfx4 = netip.PrefixFrom(ip4, int(len4)))
2. Miscellaneous
2.1. Unspecified Address
There are two "unspecified" addresses for each family:
-
IPv4
-
0.0.0.0/0 -
0.0.0.0/32
-
-
IPv6
-
::/0 -
::/128
-
For each family, the /0 prefix length