= `netx` Notes Brent Saner Last rendered {localdatetime} :doctype: book :docinfo: shared :data-uri: :imagesdir: images :sectlinks: :sectnums: :sectnumlevels: 7 :toc: preamble :toc2: left :idprefix: :toclevels: 7 :source-highlighter: rouge :docinfo: shared [id="4in6"] == IPv4 Addresses as IPv6 There are, effectively, two RFC-specified ways of representing an IPv4 address in IPv6 notation/address space/context. Per https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5[RFC 4291 § 2.5.5^]: * _IPv4-Compatible IPv6 Address_ (https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1[RFC 4291 § 2.5.5.1^]) -- `::/96` ** e.g. `::192.0.2.10` * _IPv4-Mapped IPv6 Address_ (https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2[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. For routing/actual IPv4 <=> IPv6 translation, see https://datatracker.ietf.org/doc/html/rfc6052[RFC 6052^] and https://datatracker.ietf.org/doc/html/rfc8215[RFC 8215^]. This library may add supporting functions for these _translation_ prefixes at a later time. ==== [id="4in6_addr"] === Addresses While https://pkg.go.dev/net/netip#ParseAddr[`net/netip.ParseAddr`^] (or https://pkg.go.dev/net/netip#MustParseAddr[`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. [%collapsible] .For example... ==== [source,go] ---- 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: [source,text] ---- ::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 `b2` as 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. [%collapsible] .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)`: + [source,text] ---- # 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)`: + [source,text] ---- # 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`: + [source,go] ---- []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x02, 0x0A, } ---- ** Or as `uint8` (a `byte` is, quite literally, https://go.dev/ref/spec#Numeric_types[just a type alias for `uint8`^]): + [source,go] ---- []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*. [id="4in6_pfx"] === 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] ==== `netx.IP4MapPfx4to6(len4 uint8)` simply takes the prefix length `len4`, and (if `len4 <= 32`) adds `96`. `netx.IP4MapPfx6to4(len6 uint8)` simply takes the prefix length `len6`, and (if `len6 >= 96`) subtracts `96`. ==== This is achieved by: . Taking the address from the prefix (`ip4in6 = pfx4in6.Addr()`) .. And <<4in6_addr, 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))`) [id="misc"] == Miscellaneous [id="misc_unspec"] === 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