mirror of
https://github.com/soypat/lneto.git
synced 2026-09-06 14:59:05 +00:00
add ipv6 to xnet.StackAsync (#107)
* add ipv6 to xnet.StackAsync * dns improvements * improve DNS workings of StackAsync * add tentative ICMPv6 * work on prefixes and fix some small bugs, plan UDP/TCP6 * fix bugs in StackAsync and ipv4.Prefix.Contains * update arpsubtable * completely remove legacy internet.StackIP for StackIPv4/v6 * ipv4/ipv6 tcp/udp * add TCP6/UDP6 dialing APIs * add xnet.Stack6 interface * more ipv6 integration into StackAsync; various tweaks to lneto and documentation+TODOs * add stack6 tests * replace netip.Prefix with ipv4.Prefix where it makes sense
This commit is contained in:
+3
-1
@@ -1,6 +1,8 @@
|
||||
package ipv4
|
||||
|
||||
import "strconv"
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// RFC791 defines the minimum MTU for an IPv4 packet as 68, meaning a payload of 48 bytes when no IPv4 options included.
|
||||
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package ipv4
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// Prefix is a [netip.Prefix] equivalent specifically designed for IPv4.
|
||||
type Prefix struct {
|
||||
addr uint32
|
||||
bitsPlusOne uint8
|
||||
}
|
||||
|
||||
func PrefixFromNetip(pfx netip.Prefix) Prefix {
|
||||
addr := pfx.Addr()
|
||||
if addr.Is4() {
|
||||
return PrefixFrom(addr.As4(), uint8(pfx.Bits()))
|
||||
}
|
||||
return Prefix{}
|
||||
}
|
||||
|
||||
// PrefixFrom constructs a [Prefix] from an address and prefix bit length.
|
||||
//
|
||||
// It does not allocate and does not mask
|
||||
// off the host bits of ip.
|
||||
//
|
||||
// If bits is less than zero or greater than 32, [Prefix.Bits]
|
||||
// will return an invalid value 255.
|
||||
func PrefixFrom(addr [4]byte, bits uint8) Prefix {
|
||||
if bits > 32 {
|
||||
bits = 0
|
||||
}
|
||||
return Prefix{addr: addr2bits(addr), bitsPlusOne: bits + 1}
|
||||
}
|
||||
|
||||
// IsValid returns true if the [Prefix] is valid.
|
||||
func (p Prefix) IsValid() bool { return p.bitsPlusOne != 0 }
|
||||
|
||||
// Addr returns the IPv4 address.
|
||||
func (p Prefix) Addr() [4]byte { return bits2addr(p.addr) }
|
||||
|
||||
// Bits returns IPv4 prefix bits 0..32 or 255 for invalid prefixes.
|
||||
func (p Prefix) Bits() uint8 { return p.bitsPlusOne - 1 }
|
||||
|
||||
// NetipPrefix returns the equivalent [netip.Prefix].
|
||||
func (p Prefix) NetipPrefix() netip.Prefix {
|
||||
return netip.PrefixFrom(netip.AddrFrom4(p.Addr()), int(p.Bits()))
|
||||
}
|
||||
|
||||
func (p Prefix) addrBitmasked() uint32 { return p.addr & p.bitmask() }
|
||||
func (p Prefix) bitmask() uint32 { return ^uint32(0) << (32 - p.Bits()) }
|
||||
|
||||
func addr2bits(addr [4]byte) uint32 { return binary.BigEndian.Uint32(addr[:]) }
|
||||
|
||||
func bits2addr(addrbits uint32) (addr [4]byte) {
|
||||
binary.BigEndian.PutUint32(addr[:], addrbits)
|
||||
return addr
|
||||
}
|
||||
|
||||
// Contains reports whether the network p includes ip.
|
||||
//
|
||||
// A zero-value IP will not match any prefix.
|
||||
func (p Prefix) Contains(addr [4]byte) bool {
|
||||
if !p.IsValid() {
|
||||
return false
|
||||
}
|
||||
mask := p.bitmask()
|
||||
return p.addr&mask == addr2bits(addr)&mask
|
||||
}
|
||||
|
||||
// Masked returns the Prefix with address bits outside of the prefix masked to zero.
|
||||
func (p Prefix) Masked() Prefix {
|
||||
return Prefix{addr: p.addrBitmasked(), bitsPlusOne: p.bitsPlusOne}
|
||||
}
|
||||
|
||||
// IsSingleIP reports whether p contains exactly one IP address (i.e. a /32).
|
||||
func (p Prefix) IsSingleIP() bool { return p.IsValid() && p.Bits() == 32 }
|
||||
|
||||
// Overlaps reports whether p and o contain any IP addresses in common.
|
||||
func (p Prefix) Overlaps(o Prefix) bool {
|
||||
if !p.IsValid() || !o.IsValid() {
|
||||
return false
|
||||
}
|
||||
mask := ^uint32(0) << (32 - min(p.Bits(), o.Bits()))
|
||||
return p.addr&mask == o.addr&mask
|
||||
}
|
||||
|
||||
// Next returns the address following addr in the prefix mask with wrap around semantics.
|
||||
func (p Prefix) Next(addr [4]byte) (next [4]byte) {
|
||||
mask := p.bitmask()
|
||||
host := addr2bits(addr) &^ mask
|
||||
host = (host + 1) & ^mask
|
||||
return bits2addr(p.addrBitmasked() | host)
|
||||
}
|
||||
|
||||
// Compare returns an integer comparing two prefixes.
|
||||
// The result will be 0 if p == p2, -1 if p < p2, and +1 if p > p2.
|
||||
// Prefixes sort first by validity (invalid before valid), then masked
|
||||
// prefix address, then prefix length, then unmasked address.
|
||||
func (p Prefix) Compare(p2 Prefix) int {
|
||||
if p.IsValid() != p2.IsValid() {
|
||||
if !p.IsValid() {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if !p.IsValid() {
|
||||
return 0
|
||||
}
|
||||
pm, p2m := p.addrBitmasked(), p2.addrBitmasked()
|
||||
if pm != p2m {
|
||||
if pm < p2m {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if p.bitsPlusOne != p2.bitsPlusOne {
|
||||
if p.bitsPlusOne < p2.bitsPlusOne {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
pa, p2a := p.addr, p2.addr
|
||||
if pa < p2a {
|
||||
return -1
|
||||
} else if pa > p2a {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package ipv4
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPrefixFrom(t *testing.T) {
|
||||
tests := []struct {
|
||||
addr [4]byte
|
||||
bits uint8
|
||||
wantValid bool
|
||||
wantBits uint8
|
||||
wantAddr [4]byte
|
||||
}{
|
||||
{[4]byte{192, 168, 1, 0}, 24, true, 24, [4]byte{192, 168, 1, 0}},
|
||||
{[4]byte{10, 0, 0, 0}, 8, true, 8, [4]byte{10, 0, 0, 0}},
|
||||
{[4]byte{0, 0, 0, 0}, 0, true, 0, [4]byte{0, 0, 0, 0}},
|
||||
{[4]byte{1, 2, 3, 4}, 32, true, 32, [4]byte{1, 2, 3, 4}},
|
||||
{[4]byte{1, 2, 3, 4}, 33, true, 0, [4]byte{1, 2, 3, 4}}, // >32 clamped to 0
|
||||
}
|
||||
for _, tc := range tests {
|
||||
p := PrefixFrom(tc.addr, tc.bits)
|
||||
if p.IsValid() != tc.wantValid {
|
||||
t.Errorf("PrefixFrom(%v, %d).IsValid() = %v, want %v", tc.addr, tc.bits, p.IsValid(), tc.wantValid)
|
||||
}
|
||||
if p.Bits() != tc.wantBits {
|
||||
t.Errorf("PrefixFrom(%v, %d).Bits() = %d, want %d", tc.addr, tc.bits, p.Bits(), tc.wantBits)
|
||||
}
|
||||
if p.Addr() != tc.wantAddr {
|
||||
t.Errorf("PrefixFrom(%v, %d).Addr() = %v, want %v", tc.addr, tc.bits, p.Addr(), tc.wantAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixZeroValue(t *testing.T) {
|
||||
var p Prefix
|
||||
if p.IsValid() {
|
||||
t.Error("zero Prefix should be invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixFromNetip(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
wantValid bool
|
||||
}{
|
||||
{"192.168.1.0/24", true},
|
||||
{"10.0.0.0/8", true},
|
||||
{"0.0.0.0/0", true},
|
||||
{"1.2.3.4/32", true},
|
||||
{"::1/128", false}, // IPv6 should yield invalid
|
||||
}
|
||||
for _, tc := range tests {
|
||||
npfx, err := netip.ParsePrefix(tc.in)
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePrefix(%q): %v", tc.in, err)
|
||||
}
|
||||
p := PrefixFromNetip(npfx)
|
||||
if p.IsValid() != tc.wantValid {
|
||||
t.Errorf("PrefixFromNetip(%q).IsValid() = %v, want %v", tc.in, p.IsValid(), tc.wantValid)
|
||||
}
|
||||
if !tc.wantValid {
|
||||
continue
|
||||
}
|
||||
if p.NetipPrefix() != npfx {
|
||||
t.Errorf("PrefixFromNetip(%q).NetipPrefix() = %v, want %v", tc.in, p.NetipPrefix(), npfx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixNetipRoundtrip(t *testing.T) {
|
||||
inputs := []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "0.0.0.0/0", "1.2.3.4/32"}
|
||||
for _, s := range inputs {
|
||||
npfx := netip.MustParsePrefix(s)
|
||||
p := PrefixFromNetip(npfx)
|
||||
if got := p.NetipPrefix(); got != npfx {
|
||||
t.Errorf("roundtrip %q: got %v", s, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixContains(t *testing.T) {
|
||||
p := PrefixFrom([4]byte{192, 168, 1, 0}, 24)
|
||||
tests := []struct {
|
||||
addr [4]byte
|
||||
want bool
|
||||
}{
|
||||
{[4]byte{192, 168, 1, 0}, true},
|
||||
{[4]byte{192, 168, 1, 1}, true},
|
||||
{[4]byte{192, 168, 1, 255}, true},
|
||||
{[4]byte{192, 168, 2, 0}, false},
|
||||
{[4]byte{10, 0, 0, 1}, false},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
if got := p.Contains(tc.addr); got != tc.want {
|
||||
t.Errorf("%v.Contains(%v) = %v, want %v", p.NetipPrefix(), tc.addr, got, tc.want)
|
||||
}
|
||||
}
|
||||
|
||||
var invalid Prefix
|
||||
if invalid.Contains([4]byte{0, 0, 0, 0}) {
|
||||
t.Error("invalid Prefix.Contains should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixMasked(t *testing.T) {
|
||||
// Address with host bits set.
|
||||
p := PrefixFrom([4]byte{192, 168, 1, 5}, 24)
|
||||
m := p.Masked()
|
||||
want := [4]byte{192, 168, 1, 0}
|
||||
if m.Addr() != want {
|
||||
t.Errorf("Masked().Addr() = %v, want %v", m.Addr(), want)
|
||||
}
|
||||
if m.Bits() != 24 {
|
||||
t.Errorf("Masked().Bits() = %d, want 24", m.Bits())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixIsSingleIP(t *testing.T) {
|
||||
if !PrefixFrom([4]byte{1, 2, 3, 4}, 32).IsSingleIP() {
|
||||
t.Error("/32 should be single IP")
|
||||
}
|
||||
if PrefixFrom([4]byte{1, 2, 3, 4}, 31).IsSingleIP() {
|
||||
t.Error("/31 should not be single IP")
|
||||
}
|
||||
var invalid Prefix
|
||||
if invalid.IsSingleIP() {
|
||||
t.Error("invalid Prefix.IsSingleIP should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixOverlaps(t *testing.T) {
|
||||
tests := []struct {
|
||||
a, b string
|
||||
want bool
|
||||
}{
|
||||
{"192.168.0.0/16", "192.168.1.0/24", true},
|
||||
{"10.0.0.0/8", "10.1.2.0/24", true},
|
||||
{"10.0.0.0/8", "192.168.0.0/16", false},
|
||||
{"0.0.0.0/0", "1.2.3.4/32", true},
|
||||
{"1.2.3.4/32", "1.2.3.4/32", true},
|
||||
{"1.2.3.4/32", "1.2.3.5/32", false},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
a := PrefixFromNetip(netip.MustParsePrefix(tc.a))
|
||||
b := PrefixFromNetip(netip.MustParsePrefix(tc.b))
|
||||
if got := a.Overlaps(b); got != tc.want {
|
||||
t.Errorf("%s.Overlaps(%s) = %v, want %v", tc.a, tc.b, got, tc.want)
|
||||
}
|
||||
// Symmetry.
|
||||
if got := b.Overlaps(a); got != tc.want {
|
||||
t.Errorf("%s.Overlaps(%s) [symmetric] = %v, want %v", tc.b, tc.a, got, tc.want)
|
||||
}
|
||||
}
|
||||
|
||||
var invalid Prefix
|
||||
valid := PrefixFrom([4]byte{10, 0, 0, 0}, 8)
|
||||
if invalid.Overlaps(valid) || valid.Overlaps(invalid) {
|
||||
t.Error("invalid Prefix.Overlaps should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixNext(t *testing.T) {
|
||||
tests := []struct {
|
||||
prefix string
|
||||
addr [4]byte
|
||||
want [4]byte
|
||||
}{
|
||||
// Normal increment within /24.
|
||||
{"192.168.1.0/24", [4]byte{192, 168, 1, 0}, [4]byte{192, 168, 1, 1}},
|
||||
{"192.168.1.0/24", [4]byte{192, 168, 1, 1}, [4]byte{192, 168, 1, 2}},
|
||||
{"192.168.1.0/24", [4]byte{192, 168, 1, 254}, [4]byte{192, 168, 1, 255}},
|
||||
// Wrap-around: last host addr in /24 wraps to first.
|
||||
{"192.168.1.0/24", [4]byte{192, 168, 1, 255}, [4]byte{192, 168, 1, 0}},
|
||||
// /32: only one host, wraps to itself.
|
||||
{"1.2.3.4/32", [4]byte{1, 2, 3, 4}, [4]byte{1, 2, 3, 4}},
|
||||
// /31: two hosts, wraps.
|
||||
{"10.0.0.0/31", [4]byte{10, 0, 0, 0}, [4]byte{10, 0, 0, 1}},
|
||||
{"10.0.0.0/31", [4]byte{10, 0, 0, 1}, [4]byte{10, 0, 0, 0}},
|
||||
// /8: increment and wrap within the network.
|
||||
{"10.0.0.0/8", [4]byte{10, 0, 0, 255}, [4]byte{10, 0, 1, 0}},
|
||||
{"10.0.0.0/8", [4]byte{10, 255, 255, 255}, [4]byte{10, 0, 0, 0}},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
p := PrefixFromNetip(netip.MustParsePrefix(tc.prefix))
|
||||
got := p.Next(tc.addr)
|
||||
if got != tc.want {
|
||||
t.Errorf("%s.Next(%v) = %v, want %v", tc.prefix, tc.addr, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixCompare(t *testing.T) {
|
||||
var invalid Prefix
|
||||
a := PrefixFromNetip(netip.MustParsePrefix("10.0.0.0/8"))
|
||||
b := PrefixFromNetip(netip.MustParsePrefix("192.168.0.0/16"))
|
||||
|
||||
// invalid < valid
|
||||
if invalid.Compare(a) != -1 {
|
||||
t.Error("invalid.Compare(valid) should be -1")
|
||||
}
|
||||
if a.Compare(invalid) != 1 {
|
||||
t.Error("valid.Compare(invalid) should be 1")
|
||||
}
|
||||
// two invalids are equal
|
||||
if invalid.Compare(Prefix{}) != 0 {
|
||||
t.Error("invalid.Compare(invalid) should be 0")
|
||||
}
|
||||
// reflexive
|
||||
if a.Compare(a) != 0 {
|
||||
t.Error("a.Compare(a) should be 0")
|
||||
}
|
||||
// ordering
|
||||
if got := a.Compare(b); got >= 0 {
|
||||
t.Errorf("10/8.Compare(192.168/16) should be negative, got %d", got)
|
||||
}
|
||||
if got := b.Compare(a); got <= 0 {
|
||||
t.Errorf("192.168/16.Compare(10/8) should be positive, got %d", got)
|
||||
}
|
||||
|
||||
// shorter prefix < longer prefix when masked addr is equal
|
||||
a8 := PrefixFromNetip(netip.MustParsePrefix("10.0.0.0/8"))
|
||||
a16 := PrefixFromNetip(netip.MustParsePrefix("10.0.0.0/16"))
|
||||
if a8.Compare(a16) >= 0 {
|
||||
t.Error("10/8 should sort before 10/16")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user