diff --git a/http/httpraw/header_test.go b/http/httpraw/header_test.go index 9a27216..a5c518c 100644 --- a/http/httpraw/header_test.go +++ b/http/httpraw/header_test.go @@ -112,13 +112,19 @@ func BenchmarkParseBytes(b *testing.B) { asRequest = false ) req, _ := http.NewRequest(wantMethod, wantURI, strings.NewReader(wantMessage)) - var buf bytes.Buffer - req.Write(&buf) - data := buf.Bytes() + var rawBuf bytes.Buffer + req.Write(&rawBuf) + data := rawBuf.Bytes() + + // hdr is declared outside the loop so that ParseBytes can reuse the + // backing arrays on Reset (headers slice and data buffer) without + // allocating on every iteration. Declaring it inside the loop causes + // two allocs per iteration: one for the headers slice (make in reset) + // and one for the data buffer (append in readFromBytes). + var hdr Header b.StartTimer() for i := 0; i < b.N; i++ { - var hdr Header err := hdr.ParseBytes(asRequest, data) if err != nil { b.Fatal(err) diff --git a/http/httpraw/parse.go b/http/httpraw/parse.go index 5243764..0b0bd26 100644 --- a/http/httpraw/parse.go +++ b/http/httpraw/parse.go @@ -120,7 +120,13 @@ func (hb *headerBuf) free() int { return cap(hb.buf) - len(hb.buf) } func (hb *headerBuf) parseNextHeaders(ss *scannerState) { debuglog("http:nexthdr:loop") for kv := hb.next(ss); kv.isValid(); kv = hb.next(ss) { - hb.headers = append(hb.headers, kv) // TODO(HEAP): inc=16B slice growth when capacity exceeded + if len(hb.headers) == cap(hb.headers) { + // Refuse to grow the headers slice: caller must pre-allocate + // sufficient capacity via reset or use a larger initial size. + ss.err = errOOM + return + } + hb.headers = append(hb.headers, kv) } debuglog("http:nexthdr:done") } diff --git a/internal/ip.go b/internal/ip.go index 42e354c..6dcfa44 100644 --- a/internal/ip.go +++ b/internal/ip.go @@ -26,6 +26,18 @@ func GetIPAddr(buf []byte) (src, dst []byte, id, ipEndOff uint16, err error) { return src, dst, id, ipEndOff, err } +// IsMulticastIPAddr reports whether addr is an IPv4 or IPv6 multicast address. +func IsMulticastIPAddr(addr []byte) bool { + switch len(addr) { + case 4: + return addr[0]&0xf0 == 0xe0 + case 16: + return addr[0] == 0xff + default: + return false + } +} + func SetIPAddrs(buf []byte, id uint16, src, dst []byte) (err error) { var dstaddr, srcaddr []byte version := buf[0] >> 4 diff --git a/internal/ip_test.go b/internal/ip_test.go new file mode 100644 index 0000000..29715dc --- /dev/null +++ b/internal/ip_test.go @@ -0,0 +1,26 @@ +package internal + +import "testing" + +func TestIsMulticastIPAddr(t *testing.T) { + tests := []struct { + name string + addr []byte + want bool + }{ + {"ipv4 multicast", []byte{224, 0, 0, 1}, true}, + {"ipv4 multicast upper", []byte{239, 255, 255, 255}, true}, + {"ipv4 unicast", []byte{192, 0, 2, 1}, false}, + {"ipv6 multicast", []byte{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, true}, + {"ipv6 unicast", []byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, false}, + {"invalid length", []byte{224}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsMulticastIPAddr(tt.addr); got != tt.want { + t.Fatalf("got %t; want %t", got, tt.want) + } + }) + } +} diff --git a/internal/ring.go b/internal/ring.go index 5918d66..94b2ec9 100644 --- a/internal/ring.go +++ b/internal/ring.go @@ -69,7 +69,7 @@ func (r *Ring) Write(b []byte) (int, error) { n := copy(r.Buf[r.End:r.Off], b) r.End += n if r.End <= 0 { - panic("zero end after write") // TODO: remove panics after validation. + panic("zero end after write") // invariant: End must be >0 after writing into midFree region } return n, nil } else if r.End == 0 { @@ -87,7 +87,7 @@ func (r *Ring) Write(b []byte) (int, error) { n += n2 } if r.End <= 0 { - panic("zero end after write") + panic("zero end after write") // invariant: End must be >0 after appending to the tail region } return n, nil } diff --git a/internet/stack-ip4.go b/internet/stack-ip4.go index b71cdee..82153da 100644 --- a/internet/stack-ip4.go +++ b/internet/stack-ip4.go @@ -101,7 +101,7 @@ func (si4 *stackip4) demux4(carrierData []byte, offset int) error { } dst := ifrm.DestinationAddr() if si4.ip4 != ([4]byte{}) && *dst != si4.ip4 { - if !si4.acceptMulticast || dst[0]&0xF0 != 0xE0 { + if !si4.acceptMulticast || !internal.IsMulticastIPAddr(dst[:]) { si4.handlers.debug("ip:not-for-us") return lneto.ErrPacketDrop // Not meant for us. } diff --git a/internet/stack-ip6.go b/internet/stack-ip6.go index 2a386da..f357b67 100644 --- a/internet/stack-ip6.go +++ b/internet/stack-ip6.go @@ -5,6 +5,7 @@ import ( "github.com/soypat/lneto" "github.com/soypat/lneto/ethernet" + "github.com/soypat/lneto/internal" "github.com/soypat/lneto/ipv6" "github.com/soypat/lneto/tcp" "github.com/soypat/lneto/udp" @@ -89,7 +90,7 @@ func (si6 *stackip6) demux6(carrierData []byte, offset int) error { } dst := ifrm.DestinationAddr() if si6.ip6 != ([16]byte{}) && *dst != si6.ip6 { - if !si6.acceptMulticast || dst[0] != 0xFF { + if !si6.acceptMulticast || !internal.IsMulticastIPAddr(dst[:]) { si6.handlers.debug("ip6:not-for-us") return lneto.ErrPacketDrop } diff --git a/internet/stack-udpport.go b/internet/stack-udpport.go index 617dffd..21f7106 100644 --- a/internet/stack-udpport.go +++ b/internet/stack-udpport.go @@ -45,7 +45,15 @@ func (sudp *StackUDPPort) Demux(carrierData []byte, frameOffset int) error { if dst != sudp.h.lport { return lneto.ErrPacketDrop // Not meant for us. } - // TODO remote ip address handling. + if len(sudp.raddr) > 0 && !internal.IsMulticastIPAddr(sudp.raddr) { + srcIP, _, _, _, err := internal.GetIPAddr(carrierData[:frameOffset]) + if err != nil { + return err + } + if !internal.BytesEqual(srcIP, sudp.raddr) { + return lneto.ErrPacketDrop + } + } src := ufrm.SourcePort() if sudp.rmport != 0 && src != sudp.rmport { diff --git a/tcp/conn.go b/tcp/conn.go index 2a5d62f..b51927d 100644 --- a/tcp/conn.go +++ b/tcp/conn.go @@ -362,7 +362,9 @@ func (conn *Conn) Demux(buf []byte, off int) (err error) { conn.mu.Lock() defer conn.mu.Unlock() if off >= len(buf) { - return lneto.ErrTruncatedFrame // TODO: this check is bad. + // off is the IP header length; if it equals or exceeds the frame + // length, there are zero bytes of TCP payload — drop the frame. + return lneto.ErrTruncatedFrame } raddr, _, id, _, err := internal.GetIPAddr(buf[:off]) if err != nil { diff --git a/tcp/handler.go b/tcp/handler.go index cad6c06..34dea8f 100644 --- a/tcp/handler.go +++ b/tcp/handler.go @@ -174,8 +174,7 @@ func (h *Handler) Recv(incomingPacket []byte) error { err = h.scb.Recv(segIncoming) if err != nil { if h.scb.State() == StateClosed { - // TODO(soypat): Should return EOF/ErrClosed? - err = net.ErrClosed //err // Connection closed by reset. + err = net.ErrClosed // Connection closed by RST; signal caller to tear down. } return err } diff --git a/x/xnet/xnet_mdns_test.go b/x/xnet/xnet_mdns_test.go index ae056f2..adac64b 100644 --- a/x/xnet/xnet_mdns_test.go +++ b/x/xnet/xnet_mdns_test.go @@ -297,7 +297,9 @@ func newMDNSStack(t *testing.T, hostname string, seed int64, t.Fatal(hostname, "mdns configure:", err) } - err = stack.RegisterUDP4(&client, addr.As4(), mdns.Port) + var remote [4]byte + copy(remote[:], mdnsCfg.MulticastAddr) + err = stack.RegisterUDP4(&client, remote, mdns.Port) if err != nil { t.Fatal(hostname, "register udp:", err) }