add IP/TCP/HTTP fuzzing and fixes to panics found

This commit is contained in:
Patricio Whittingslow
2026-02-12 16:25:48 -03:00
parent 1694e71e0c
commit fae4552ddb
6 changed files with 141 additions and 2 deletions
+2
View File
@@ -21,6 +21,8 @@ Userspace networking primitives.
- Can produce **very** small binaries. Ideal for embedded systems.
- Extremely simple networking stack construction. Can be used to teach basics of networking
- Only one networking interface fulfilled by all implementations. See [abstractions](#abstractions).
- Stack can be fuzz tested very fast:
- `go test ./x/xnet/ -run FuzzStackAsyncHTTP -fuzz=.` fuzzes Ethernet/IP/TCP/HTTP stack with 170k HTTP exchanges per second on 12 core machine: `fuzz: elapsed: 4m9s, execs: 42649941 (169428/sec), new interesting: 5 (total: 52)`
## `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.
+1 -1
View File
@@ -210,7 +210,7 @@ func (ifrm Frame) ValidateSize(v *lneto.Validator) {
if int(tl) > len(ifrm.RawData()) {
v.AddError(errShort)
}
if ihl < 5 {
if ihl < 5 || uint16(ihl)*4 > tl {
v.AddError(errBadIHL)
}
}
@@ -0,0 +1,3 @@
go test fuzz v1
int(0)
[]byte("\xbe\xef\x00\x00\x00\xff000000\b\x00E0\x00 00000\x11000000\n\x00\x00\xff000000000000")
@@ -0,0 +1,3 @@
go test fuzz v1
int(3)
[]byte("\xbe\xef\x00\x00\x00\xff000000\b\x00H0\x00\x1a00000\x06000000\n\x00\x00\xff000000")
+131
View File
@@ -0,0 +1,131 @@
package xnet
import (
"net/netip"
"testing"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/http/httpraw"
"github.com/soypat/lneto/tcp"
)
func FuzzStackAsyncHTTP(f *testing.F) {
const MTU = 1500
const seed = 1
var buf [MTU + ethernet.MaxOverheadSize]byte
s1, s2, c1, c2 := newTCPStacks(f, seed, MTU)
var hdr httpraw.Header
err := s1.ListenTCP(c1, 80)
if err != nil {
f.Fatal(err)
}
err = s2.DialTCP(c2, 1337, netip.AddrPortFrom(s1.Addr(), c1.LocalPort()))
if err != nil {
f.Fatal(err)
}
hdr.SetMethod("GET")
hdr.SetProtocol("HTTP/1.1")
hdr.SetRequestURI("/")
data := hdr.AppendHeaders(nil)
pktnum := 0
written := false
closed := false
for {
n1, err := s1.Encapsulate(buf[:], -1, 0)
if err != nil {
f.Fatal(err)
}
if n1 > 0 {
err = s2.Demux(buf[:n1], 0)
if err != nil {
f.Fatal(err)
}
f.Add(pktnum, buf[:n1])
pktnum++
if !written && c2.State() >= tcp.StateEstablished {
_, err = c2.Write(data)
if err != nil {
f.Fatal(err)
}
written = true
}
}
n2, err := s2.Encapsulate(buf[:], -1, 0)
if n2 > 0 {
pktnum++
err = s1.Demux(buf[:n2], 0)
if err != nil {
f.Fatal(err)
}
f.Add(pktnum, buf[:n2])
}
if n1 == 0 && n2 == 0 {
if !closed {
c2.Close()
closed = true
continue
}
break // No more data to send
}
}
f.Fuzz(func(t *testing.T, pktnum int, a []byte) {
var buf [MTU + ethernet.MaxOverheadSize]byte
s1, s2, c1, c2 := newTCPStacks(t, seed, MTU)
err := s1.ListenTCP(c1, 80)
if err != nil {
t.Fatal(err)
}
err = s2.DialTCP(c2, 1337, netip.AddrPortFrom(s1.Addr(), c1.LocalPort()))
if err != nil {
t.Fatal(err)
}
pkt := 0
written := false
closed := false
const maxpkts = 100
for {
n1, err := s1.Encapsulate(buf[:], -1, 0)
if err != nil {
t.Fatal(err)
}
if n1 > 0 {
if pkt == pktnum {
n1 = copy(buf[:], a)
}
s2.Demux(buf[:n1], 0)
pkt++
if !written && c2.State() >= tcp.StateEstablished {
c2.Write(data)
written = true
}
}
n2, err := s2.Encapsulate(buf[:], -1, 0)
if n2 > 0 {
if pkt == pktnum {
n2 = copy(buf[:], a)
}
pkt++
s1.Demux(buf[:n2], 0)
}
if n1 == 0 && n2 == 0 {
if !closed {
if c1.BufferedInput() > 0 {
var hdr httpraw.Header
n, _ := c1.Read(buf[:])
hdr.ReadFromBytes(buf[:n])
hdr.TryParse(false)
}
c2.Close()
closed = true
continue
}
break // No more data to send
}
if pkt > maxpkts {
panic("infinite retransmission loop")
}
}
})
}
+1 -1
View File
@@ -166,7 +166,7 @@ func TestStackAsyncTCP_singlepacket(t *testing.T) {
tst.TestTCPClose(client, sv, clconn, svconn)
}
func newTCPStacks(t *testing.T, randSeed int64, mtu int) (s1, s2 *StackAsync, c1, c2 *tcp.Conn) {
func newTCPStacks(t testing.TB, randSeed int64, mtu int) (s1, s2 *StackAsync, c1, c2 *tcp.Conn) {
s1, s2 = new(StackAsync), new(StackAsync)
c1, c2 = new(tcp.Conn), new(tcp.Conn)
byte1 := byte(randSeed)/4 - 1