diff --git a/README.md b/README.md index fb18426..e46721e 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/ipv4/frame.go b/ipv4/frame.go index 7e56a59..3b35bda 100644 --- a/ipv4/frame.go +++ b/ipv4/frame.go @@ -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) } } diff --git a/x/xnet/testdata/fuzz/FuzzStackAsyncHTTP/01a1cd7bc3a9efe3 b/x/xnet/testdata/fuzz/FuzzStackAsyncHTTP/01a1cd7bc3a9efe3 new file mode 100644 index 0000000..66afb3e --- /dev/null +++ b/x/xnet/testdata/fuzz/FuzzStackAsyncHTTP/01a1cd7bc3a9efe3 @@ -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") diff --git a/x/xnet/testdata/fuzz/FuzzStackAsyncHTTP/0361497e9c28b960 b/x/xnet/testdata/fuzz/FuzzStackAsyncHTTP/0361497e9c28b960 new file mode 100644 index 0000000..0e282d0 --- /dev/null +++ b/x/xnet/testdata/fuzz/FuzzStackAsyncHTTP/0361497e9c28b960 @@ -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") diff --git a/x/xnet/xnet_fuzz_test.go b/x/xnet/xnet_fuzz_test.go new file mode 100644 index 0000000..d7cecb5 --- /dev/null +++ b/x/xnet/xnet_fuzz_test.go @@ -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") + } + } + }) +} diff --git a/x/xnet/xnet_test.go b/x/xnet/xnet_test.go index 104f863..85f97c8 100644 --- a/x/xnet/xnet_test.go +++ b/x/xnet/xnet_test.go @@ -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