v1.17.0
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
This commit is contained in:
+206
@@ -0,0 +1,206 @@
|
||||
= `netx` Notes
|
||||
Brent Saner <bts@square-r00t.net>
|
||||
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
|
||||
Reference in New Issue
Block a user