add gvisor-mwe and add binary sizes to README

This commit is contained in:
Patricio Whittingslow
2026-04-17 15:41:17 -03:00
parent f87761f777
commit c020795303
6 changed files with 131 additions and 0 deletions
+1
View File
@@ -15,6 +15,7 @@ vendor/
*.so
*.dylib
*.hex
*.wasm
# Profiling
*.pprof
+8
View File
@@ -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.
+8
View File
@@ -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
@@ -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
)
@@ -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=
@@ -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!"))
}