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:
Pat Whittingslow
2026-05-13 15:31:18 -03:00
committed by GitHub
parent 7d5830d7ab
commit a2970b923d
44 changed files with 1938 additions and 395 deletions
@@ -137,7 +137,7 @@ func run() error {
pfbuf, err = pf.FormatFrames(pfbuf, frames, pkt)
addr := stack.Addr4()
pfbuf = bytes.ReplaceAll(pfbuf, ipv4.AppendFormatAddr(nil, addr), []byte("us"))
pfbuf = bytes.ReplaceAll(pfbuf, ethernet.AppendAddr(nil, stack.HardwareAddress()), []byte("us"))
pfbuf = bytes.ReplaceAll(pfbuf, ethernet.AppendAddr(nil, stack.HardwareAddr()), []byte("us"))
pfbuf = append(pfbuf, ']', '\n')
if err != nil {
return err
@@ -232,7 +232,7 @@ func run() error {
return fmt.Errorf("ARP resolution of router failed: %w", err)
}
// Set gateway on the async stack (exported API).
stack.SetGateway6(routerHw)
stack.SetGatewayHardwareAddr(routerHw)
// Create Berkeley listener via SocketNetip
laddr := netip.AddrPortFrom(netip.IPv4Unspecified(), uint16(flagPort))
@@ -332,7 +332,7 @@ func mockClient(stack *xnet.StackAsync, port uint16, subnet netip.Prefix) {
err := mockStack.Reset(xnet.StackConfig{
StaticAddress4: subnet.Addr().Next().As4(),
MaxActiveTCPPorts: 1,
HardwareAddress: stack.Gateway6(),
HardwareAddress: stack.GatewayHardwareAddr(),
Hostname: "the-other",
MTU: uint16(stack.MTU()),
RandSeed: int64(stack.Prand32()),
+189
View File
@@ -0,0 +1,189 @@
//go:build !tinygo && linux
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
type program struct {
Name string
Link string // relative path for README link
Dir string // directory to build from, relative to repo root
ExtraProtocols string
PacketCapture bool
}
type buildTarget struct {
Name string
ext string // output file extension (determines format for tinygo)
build func(dir, outFile string) error
}
type result struct {
BinarySize int64
CompileTime time.Duration
DNC bool // Does Not Compile
Err error
}
func (r result) sizeString() string {
if r.DNC {
return "DNC"
}
if r.Err != nil {
return "ERR"
}
return formatSize(r.BinarySize)
}
func formatSize(n int64) string {
const mb = 1024 * 1024
if n >= mb {
return fmt.Sprintf("%.1fMB", float64(n)/mb)
}
return fmt.Sprintf("%dkB", (n+512)/1024)
}
func goBuild(goos, goarch string) func(dir, out string) error {
return func(dir, out string) error {
cmd := exec.Command("go", "build", "-o", out, ".")
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("%w: %s", err, out)
}
return nil
}
}
func tinygoBuild(target string) func(dir, out string) error {
return func(dir, out string) error {
args := []string{"build"}
if target != "" {
args = append(args, "-target="+target)
}
args = append(args, "-o", out, ".")
cmd := exec.Command("tinygo", args...)
cmd.Dir = dir
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("%w: %s", err, out)
}
return nil
}
}
var buildTargets = []buildTarget{
{Name: "amd64 Go", ext: ".elf", build: goBuild("linux", "amd64")},
{Name: "WASM Go", ext: ".wasm", build: goBuild("wasip1", "wasm")},
{Name: "amd64 TinyGo", ext: ".elf", build: tinygoBuild("")},
{Name: "WASM TinyGo", ext: ".wasm", build: tinygoBuild("wasm")},
{Name: "Pico TinyGo", ext: ".bin", build: tinygoBuild("pico")},
}
var programs = []program{
{
Name: "Lneto MWE",
Link: "./examples/min-working-example/",
Dir: "examples/min-working-example",
ExtraProtocols: "DNS,NTP,DHCP",
PacketCapture: true,
},
{
Name: "Gvisor MWE w/ go-net",
Link: "./examples/_import_examples/gvisor-mwe/",
Dir: "examples/_import_examples/gvisor-mwe",
ExtraProtocols: "None",
PacketCapture: false,
},
}
func measure(dir, outFile string, fn func(dir, out string) error) result {
start := time.Now()
err := fn(dir, outFile)
elapsed := time.Since(start)
if err != nil {
return result{DNC: true, CompileTime: elapsed, Err: err}
}
fi, err := os.Stat(outFile)
if err != nil {
return result{Err: err, CompileTime: elapsed}
}
size := fi.Size()
os.Remove(outFile)
return result{BinarySize: size, CompileTime: elapsed}
}
func main() {
root := flag.String("root", ".", "path to repository root")
flag.Parse()
repoRoot, err := filepath.Abs(*root)
if err != nil {
panic(err)
}
tmpDir, err := os.MkdirTemp("", "binbench-*")
if err != nil {
panic(err)
}
defer os.RemoveAll(tmpDir)
type row struct {
prog program
results []result
}
rows := make([]row, len(programs))
for i, prog := range programs {
dir := filepath.Join(repoRoot, prog.Dir)
results := make([]result, len(buildTargets))
for j, bt := range buildTargets {
outFile := filepath.Join(tmpDir, fmt.Sprintf("p%d_t%d%s", i, j, bt.ext))
fmt.Fprintf(os.Stderr, "building %s for %s...\n", prog.Name, bt.Name)
r := measure(dir, outFile, bt.build)
if r.DNC {
fmt.Fprintf(os.Stderr, " DNC: %v\n", r.Err)
}
results[j] = r
}
rows[i] = row{prog: prog, results: results}
}
// Print markdown table.
headers := []string{"Program", "Extra Protocols", "Packet capture printing"}
for _, bt := range buildTargets {
headers = append(headers, bt.Name)
}
fmt.Printf("| %s |\n", strings.Join(headers, " | "))
aligns := make([]string, len(headers))
aligns[0] = "---"
aligns[1] = ":---:"
aligns[2] = ":---:"
for i := 3; i < len(aligns); i++ {
aligns[i] = "---"
}
fmt.Printf("|%s|\n", strings.Join(aligns, "|"))
for _, r := range rows {
pcap := "❌"
if r.prog.PacketCapture {
pcap = "✅"
}
cols := []string{
fmt.Sprintf("[%s](%s)", r.prog.Name, r.prog.Link),
r.prog.ExtraProtocols,
pcap,
}
for _, res := range r.results {
cols = append(cols, res.sizeString())
}
fmt.Printf("| %s |\n", strings.Join(cols, " | "))
}
}
+3 -3
View File
@@ -137,7 +137,7 @@ func run() (err error) {
pfbuf, err = pf.FormatFrames(pfbuf, frames, pkt)
pfbuf = bytes.ReplaceAll(pfbuf, ipv4.AppendFormatAddr(nil, stack.Addr4()), []byte("us"))
pfbuf = bytes.ReplaceAll(pfbuf, ethernet.AppendAddr(nil, stack.HardwareAddress()), []byte("us"))
pfbuf = bytes.ReplaceAll(pfbuf, ethernet.AppendAddr(nil, stack.HardwareAddr()), []byte("us"))
pfbuf = append(pfbuf, ']', '\n')
if err != nil {
return err
@@ -220,7 +220,7 @@ func run() (err error) {
return fmt.Errorf("ARP resolution of router failed: %w", err)
}
timeResolveRouterHW()
stack.SetGateway6(routerHw)
stack.SetGatewayHardwareAddr(routerHw)
svPort := uint16(flagPort)
fmt.Printf("Listening on %s:%d\n", ipv4.AppendFormatAddr(nil, stack.Addr4()), svPort)
@@ -233,7 +233,7 @@ func run() (err error) {
TxBuf: make([]byte, mtu),
TxPacketQueueSize: 3,
})
err = stack.ListenTCP(&conn, svPort)
err = stack.ListenTCP4(&conn, svPort)
if err != nil {
return fmt.Errorf("listen TCP: %w", err)
}
+1 -1
View File
@@ -55,7 +55,7 @@ type arpEntry struct {
// newDHCPInterceptor creates a dhcpInterceptor that wraps iface and serves
// DHCP from the given server address and subnet.
func newDHCPInterceptor(iface ltesto.Interface, svIP [4]byte, svMAC [6]byte, subnet netip.Prefix) (*dhcpInterceptor, error) {
func newDHCPInterceptor(iface ltesto.Interface, svIP [4]byte, svMAC [6]byte, subnet ipv4.Prefix) (*dhcpInterceptor, error) {
d := &dhcpInterceptor{
inner: iface,
svMAC: svMAC,
+4 -1
View File
@@ -18,6 +18,7 @@ import (
"github.com/soypat/lneto/internal"
"github.com/soypat/lneto/internal/ltesto"
"github.com/soypat/lneto/internet/pcap"
"github.com/soypat/lneto/ipv4"
)
func main() {
@@ -73,8 +74,10 @@ func run() error {
if err != nil {
return err
}
svIP := ipMask.Addr().As4()
iface, err = newDHCPInterceptor(iface, svIP, hwaddr, ipMask.Masked())
subnet := ipv4.PrefixFromNetip(ipMask)
iface, err = newDHCPInterceptor(iface, svIP, hwaddr, subnet.Masked())
if err != nil {
return fmt.Errorf("DHCP interceptor: %w", err)
}
+1 -1
View File
@@ -92,7 +92,7 @@ func run(ctx context.Context, stack *xnet.StackAsync) error {
if err != nil {
return fmt.Errorf("resolving router MAC: %w", err)
}
stack.SetGateway6(gateway)
stack.SetGatewayHardwareAddr(gateway)
berkstack := stack.StackBlocking(stackBackoff).StackGo(xnet.StackGoConfig{
ListenerPoolConfig: xnet.TCPPoolConfig{
PoolSize: tcpConnPoolSize,
+2 -2
View File
@@ -159,7 +159,7 @@ func run() (err error) {
pfbuf, err = pf.FormatFrames(pfbuf, frames, pkt)
addr := stack.Addr4()
pfbuf = bytes.ReplaceAll(pfbuf, addr[:], []byte("us"))
pfbuf = bytes.ReplaceAll(pfbuf, ethernet.AppendAddr(nil, stack.HardwareAddress()), []byte("us"))
pfbuf = bytes.ReplaceAll(pfbuf, ethernet.AppendAddr(nil, stack.HardwareAddr()), []byte("us"))
pfbuf = append(pfbuf, ']', '\n')
if err != nil {
return err
@@ -248,7 +248,7 @@ func run() (err error) {
return fmt.Errorf("ARP resolution of router failed: %w", err)
}
timeResolveRouterHW()
stack.SetGateway6(routerHw)
stack.SetGatewayHardwareAddr(routerHw)
if flagDoNTP {
timeLookupNTP := timer("NTP IP lookup")
const ntpHost = "pool.ntp.org"