From c0207953036f638859dd1b7d4e30e4b06e8d8d13 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Fri, 17 Apr 2026 15:41:17 -0300 Subject: [PATCH] add gvisor-mwe and add binary sizes to README --- .gitignore | 1 + README.md | 8 ++ examples/_import_examples/README.md | 8 ++ examples/_import_examples/gvisor-mwe/go.mod | 11 +++ examples/_import_examples/gvisor-mwe/go.sum | 10 +++ examples/_import_examples/gvisor-mwe/main.go | 93 ++++++++++++++++++++ 6 files changed, 131 insertions(+) create mode 100644 examples/_import_examples/README.md create mode 100644 examples/_import_examples/gvisor-mwe/go.mod create mode 100644 examples/_import_examples/gvisor-mwe/go.sum create mode 100644 examples/_import_examples/gvisor-mwe/main.go diff --git a/.gitignore b/.gitignore index 230204e..8354845 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ vendor/ *.so *.dylib *.hex +*.wasm # Profiling *.pprof diff --git a/README.md b/README.md index 76baea2..cf14146 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,14 @@ Userspace networking primitives. ## [`min-working-example`](./examples/min-working-example/) - Quick lneto showcase Get a quick showcase of how lneto can be configured and how to get a TCP listening server up and running. +### Binary size comparisons +All examples include IPv4, ARP, ICMP, TCP and UDP functionality. Go and TinyGo default build flags used. **DNC**= Does Not Compile. + + +| Program | Extra Protocols | amd64 Go | WASM Go | amd64 TinyGo | WASM TinyGo | Pico TinyGo | +|---|---|---|---|---|---|---| +| [Lneto MWE](./examples/min-working-example/) | DNS,NTP,DHCP | 3.8MB | 4.3MB | 1.3MB | 934kB | 181kB | +| [Gvisor MWE w/ go-net](./examples/_import_examples/gvisor-mwe/)| None | 6.6MB | 7.5MB | DNC | DNC | DNC | ## `xcurl` example You may try lneto out on linux with the [xcurl example](./examples/xcurl/) which gets an HTTP page by doing all the low-level networking part using absolutely no standard library. diff --git a/examples/_import_examples/README.md b/examples/_import_examples/README.md new file mode 100644 index 0000000..dd76984 --- /dev/null +++ b/examples/_import_examples/README.md @@ -0,0 +1,8 @@ +# import-examples +This folder contains examples that need external imports to work. + +This is done to avoid adding dependencies to lneto's go.mod file. +- Trivial Auditing + - No dependency analysis needed for vulnerability scanning +- Eliminate a whole set of attack vectors for lneto importers +- Ensures nothing outside Lneto gets compiled maintaining binary sizes small diff --git a/examples/_import_examples/gvisor-mwe/go.mod b/examples/_import_examples/gvisor-mwe/go.mod new file mode 100644 index 0000000..17fed52 --- /dev/null +++ b/examples/_import_examples/gvisor-mwe/go.mod @@ -0,0 +1,11 @@ +module gvisormwe + +go 1.25.7 + +require ( + github.com/google/btree v1.1.2 // indirect + github.com/usbarmory/go-net v0.0.0-20260416163630-1078311e0956 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/time v0.7.0 // indirect + gvisor.dev/gvisor v0.0.0-20250911055229-61a46406f068 // indirect +) diff --git a/examples/_import_examples/gvisor-mwe/go.sum b/examples/_import_examples/gvisor-mwe/go.sum new file mode 100644 index 0000000..4d2d713 --- /dev/null +++ b/examples/_import_examples/gvisor-mwe/go.sum @@ -0,0 +1,10 @@ +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/usbarmory/go-net v0.0.0-20260416163630-1078311e0956 h1:ZjhVXLMT/Ogxs1GIy1g8UJk7yi9G8kt90D0ElAPbaCc= +github.com/usbarmory/go-net v0.0.0-20260416163630-1078311e0956/go.mod h1:+6WiKCFJtJQZdNM2VpwQsYGo/aBJ39pN7nWx6Td3Z8s= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gvisor.dev/gvisor v0.0.0-20250911055229-61a46406f068 h1:95kdltF/maTDk/Wulj7V81cSLgjB/Mg/6eJmOKsey4U= +gvisor.dev/gvisor v0.0.0-20250911055229-61a46406f068/go.mod h1:K16uJjZ+hSqDVsXhU2Rg2FpMN7kBvjZp/Ibt5BYZJjw= diff --git a/examples/_import_examples/gvisor-mwe/main.go b/examples/_import_examples/gvisor-mwe/main.go new file mode 100644 index 0000000..a1e11fb --- /dev/null +++ b/examples/_import_examples/gvisor-mwe/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "context" + "fmt" + "net" + "net/netip" + "os" + "syscall" + "time" + + gnet "github.com/usbarmory/go-net" +) + +const pollTime = 5 * time.Millisecond + +var networkDevice NetworkDevice + +// NetworkDevice implements gnet.NetworkDevice for bridging with raw Ethernet I/O. +type NetworkDevice struct { + send func([]byte) error + recv func([]byte) (int, error) +} + +func (d *NetworkDevice) Transmit(buf []byte) error { return d.send(buf) } +func (d *NetworkDevice) Receive(buf []byte) (int, error) { return d.recv(buf) } + +func main() { + ctx := context.Background() + if err := run(ctx); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func run(ctx context.Context) error { + // Create gVisor-based networking stack. + stack := gnet.NewGVisorStack(1) + + // Configure stack with MAC, IP prefix, and gateway. + err := stack.Configure( + "aa:bb:cc:dd:ee:ff", + netip.MustParsePrefix("192.168.1.10/24"), + netip.MustParseAddr("192.168.1.1"), + ) + if err != nil { + return fmt.Errorf("configuring stack: %w", err) + } + + err = stack.EnableICMP() + if err != nil { + return fmt.Errorf("enabling ICMP: %w", err) + } + + // Bridge the stack with a network device for packet I/O. + iface := &gnet.Interface{ + Stack: stack, + NetworkDevice: &networkDevice, + HandleStackErr: func(err error, tx bool) { + dir := "rx" + if tx { + dir = "tx" + } + fmt.Printf("stack %s err: %v\n", dir, err) + }, + } + // Start the packet processing loop in a goroutine. + go iface.Start() + + // Create a TCP listener on port 80 using the gVisor stack. + const sockStream = 0x1 + laddr := net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.MustParseAddr("192.168.1.10"), 80)) + c, err := stack.Socket(ctx, "tcp", syscall.AF_INET, sockStream, laddr, nil) + if err != nil { + return fmt.Errorf("creating AF_INET stream socket: %w", err) + } + listener := c.(net.Listener) + for ctx.Err() == nil { + time.Sleep(pollTime) + conn, err := listener.Accept() + if err != nil { + fmt.Println("conn failed:", err) + continue + } + go handleConn(conn) + } + return nil +} + +func handleConn(conn net.Conn) { + defer conn.Close() + conn.Write([]byte("Hello from gVisor stack!")) +}