diff --git a/examples/stack/main.go b/examples/stack/main.go new file mode 100644 index 0000000..81a23bf --- /dev/null +++ b/examples/stack/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "errors" + "io" + + "github.com/soypat/lneto" +) + +func main() { + +} + +type LinkStack struct { + mac [6]byte + mtu uint16 +} + +func (ls *LinkStack) RecvEth(ethFrame []byte) (err error) { + eframe, err := lneto.NewEthFrame(ethFrame) + if err != nil { + return err + } + if !eframe.IsBroadcast() && ls.mac != *eframe.DestinationHardwareAddr() { + return errors.New("packet MAC mismatch") + } + + // Convert to dynamic handling. + etype := eframe.EtherTypeOrSize() + if etype != lneto.EtherTypeARP && etype != lneto.EtherTypeIPv4 && etype != lneto.EtherTypeIPv6 { + return nil + } + + return nil +} + +func (ls *LinkStack) HandleEth(dst []byte) (n int, err error) { + if len(dst) < int(ls.mtu) { + return 0, io.ErrShortBuffer + } + n, addr, etype, err := ls.handleUpper(dst[14:]) + if err != nil || n == 0 { + return 0, err + } + eframe, _ := lneto.NewEthFrame(dst[:14]) + *eframe.DestinationHardwareAddr() = addr + *eframe.SourceHardwareAddr() = ls.mac + eframe.SetEtherType(etype) + return 14 + n, nil +} + +func (ls *LinkStack) handleUpper(dst []byte) (n int, dstAddr [6]byte, etype lneto.EtherType, err error) { + return +} + +type IPv4Stack struct { + ip [4]byte + mtu uint16 + validator lneto.Validator +} + +func (is *IPv4Stack) Recv(ipframe []byte) error { + iframe, err := lneto.NewIPv4Frame(ipframe) + if err != nil { + return err + } + if *iframe.DestinationAddr() != is.ip { + return errors.New("packet not for us") + } + iframe.Validate(&is.validator) + err = is.validator.Err() + if err != nil { + return err + } + return nil + +} diff --git a/frames.go b/frames.go index 38967b3..95c13e8 100644 --- a/frames.go +++ b/frames.go @@ -111,6 +111,12 @@ func (efrm EthFrame) DestinationHardwareAddr() (dst *[6]byte) { return (*[6]byte)(efrm.buf[0:6]) } +// IsBroadcast returns true if the destination is the broadcast address ff:ff:ff:ff:ff:ff, false otherwise. +func (efrm EthFrame) IsBroadcast() bool { + return efrm.buf[0] == 0xff && efrm.buf[1] == 0xff && efrm.buf[2] == 0xff && + efrm.buf[3] == 0xff && efrm.buf[4] == 0xff && efrm.buf[5] == 0xff +} + // SourceHardwareAddr returns the sender's MAC/hardware address of the ethernet packet. func (efrm EthFrame) SourceHardwareAddr() (src *[6]byte) { return (*[6]byte)(efrm.buf[6:12]) @@ -267,9 +273,8 @@ func (ifrm IPv4Frame) HeaderLength() int { return int(ifrm.ihl()) * 4 } -func (ifrm IPv4Frame) ihl() uint8 { - return ifrm.buf[0] & 0xf -} +func (ifrm IPv4Frame) ihl() uint8 { return ifrm.buf[0] & 0xf } +func (ifrm IPv4Frame) version() uint8 { return ifrm.buf[0] >> 4 } // VersionAndIHL returns the version and IHL fields in the IPv4 header. Version should always be 4. func (ifrm IPv4Frame) VersionAndIHL() (version, IHL uint8) { diff --git a/lneto_test.go b/lneto_test.go index 3d64cc9..b4fc5e6 100644 --- a/lneto_test.go +++ b/lneto_test.go @@ -217,16 +217,17 @@ func (gen *packetGen) appendRandomIPv4TCPPacket(dst []byte, rng *rand.Rand) []by case len(tcpPayload) > 0 && firstPayloadByte != tcpPayload[0]: panic("TCP options overwrite payload") } - err = efrm.ValidateSize() - if err != nil { + var vld Validator + efrm.ValidateSize(&vld) + if err = vld.Err(); err != nil { panic(err) } - err = ifrm.ValidateSize() - if err != nil { + ifrm.Validate(&vld) + if err = vld.Err(); err != nil { panic(err) } - err = tfrm.ValidateSize() - if err != nil { + tfrm.ValidateSize(&vld) + if err = vld.Err(); err != nil { panic(err) } return dst diff --git a/tcp/tcp_test.go b/tcp/tcp_test.go index c8ab48a..27ebfa9 100644 --- a/tcp/tcp_test.go +++ b/tcp/tcp_test.go @@ -578,6 +578,7 @@ func TestExchange_helloworld_client(t *testing.T) { } func parseSegment(t *testing.T, b []byte) (tcp.Segment, []byte) { + var vld lneto.Validator t.Helper() efrm, err := lneto.NewEthFrame(b) if err != nil { @@ -586,9 +587,9 @@ func parseSegment(t *testing.T, b []byte) (tcp.Segment, []byte) { if efrm.EtherTypeOrSize() != lneto.EtherTypeIPv4 { t.Fatalf("not IPv4") } - err = efrm.ValidateSize() - if err != nil { - t.Fatal(err) + efrm.ValidateSize(&vld) + if err := vld.Err(); err != nil { + t.Fatal(vld.Err()) } ifrm, err := lneto.NewIPv4Frame(efrm.Payload()) if err != nil { @@ -601,16 +602,18 @@ func parseSegment(t *testing.T, b []byte) (tcp.Segment, []byte) { if v != 4 { t.Fatal("invalid IP version", v) } - err = ifrm.ValidateSize() - if err != nil { - t.Fatal(err) + ifrm.ValidateSize(&vld) + if err := vld.Err(); err != nil { + t.Fatal(vld.Err()) } ipl := ifrm.Payload() tfrm, err := lneto.NewTCPFrame(ipl) if err != nil { t.Fatal(err) - } else if err = tfrm.ValidateSize(); err != nil { + } + tfrm.ValidateSize(&vld) + if err := vld.Err(); err != nil { t.Fatal(err) } _ = tfrm.String() diff --git a/validation.go b/validation.go index 08ca8f7..6346163 100644 --- a/validation.go +++ b/validation.go @@ -14,77 +14,117 @@ var ( errShortARP = errors.New("bad ARP size") errShortTCP = errors.New("TCP offset exceeds frame") errBadTCPOff = errors.New("TCP offset invalid") + + errBadIPVersion = errors.New("bad IP version field") + errEvilPacket = errors.New("evil packet") ) -// ValidateSize checks the frame's size fields and compares with the actual buffer -// the frame. It returns a non-nil error on finding an inconsistency. -func (efrm EthFrame) ValidateSize() error { - sz := efrm.EtherTypeOrSize() - if sz.IsSize() && len(efrm.buf) < int(sz) { - return errShortEth - } else if sz == EtherTypeVLAN && len(efrm.buf) < 18 { - return errShortVLAN +type Validator struct { + checkEvil bool + accum []error +} + +func (v *Validator) ResetErr() { + v.accum = v.accum[:0] +} + +func (v *Validator) Err() error { + if len(v.accum) == 1 { + return v.accum[0] + } else if len(v.accum) == 0 { + return nil } - return nil + return errors.Join(v.accum...) +} + +func (v *Validator) gotErr(err error) { + v.accum = append(v.accum, err) } // ValidateSize checks the frame's size fields and compares with the actual buffer // the frame. It returns a non-nil error on finding an inconsistency. -func (afrm ARPFrame) ValidateSize() error { +func (efrm EthFrame) ValidateSize(v *Validator) { + sz := efrm.EtherTypeOrSize() + if sz.IsSize() && len(efrm.buf) < int(sz) { + v.gotErr(errShortEth) + } + if sz == EtherTypeVLAN && len(efrm.buf) < 18 { + v.gotErr(errShortVLAN) + } +} + +// ValidateSize checks the frame's size fields and compares with the actual buffer +// the frame. It returns a non-nil error on finding an inconsistency. +func (afrm ARPFrame) ValidateSize(v *Validator) { _, hlen := afrm.Hardware() _, ilen := afrm.Protocol() minLen := 8 + 2*(hlen+ilen) if len(afrm.buf) < int(minLen) { - return errShortARP + v.gotErr(errShortARP) } - return nil } // ValidateSize checks the frame's size fields and compares with the actual buffer // the frame. It returns a non-nil error on finding an inconsistency. -func (ufrm UDPFrame) ValidateSize() error { - ul := ufrm.Length() - if ul < sizeHeaderUDP { - return errBadUDPLen - } else if int(ul) > len(ufrm.RawData()) { - return errShortUDP - } - return nil -} - -// ValidateSize checks the frame's size fields and compares with the actual buffer -// the frame. It returns a non-nil error on finding an inconsistency. -func (ifrm IPv4Frame) ValidateSize() error { +func (ifrm IPv4Frame) ValidateSize(v *Validator) { ihl := ifrm.ihl() tl := ifrm.TotalLength() if tl < sizeHeaderIPv4 { - return errBadIPv4TL - } else if int(tl) > len(ifrm.RawData()) { - return errShortIPv4 - } else if ihl < 5 { - return errBadIPv4IHL + v.gotErr(errBadIPv4TL) } - return nil + if int(tl) > len(ifrm.RawData()) { + v.gotErr(errShortIPv4) + } + if ihl < 5 { + v.gotErr(errBadIPv4IHL) + } +} + +func (ifrm IPv4Frame) ValidateFields(v *Validator) { + flags := ifrm.Flags() + if ifrm.version() != 4 { + v.gotErr(errBadIPVersion) + } + if v.checkEvil && flags.IsEvil() { + v.gotErr(errEvilPacket) + } +} + +// Validate checks for invalid frame values. +func (ifrm IPv4Frame) Validate(v *Validator) { + ifrm.ValidateSize(v) + ifrm.ValidateFields(v) } // ValidateSize checks the frame's size fields and compares with the actual buffer // the frame. It returns a non-nil error on finding an inconsistency. -func (tfrm TCPFrame) ValidateSize() error { - off := tfrm.HeaderLength() - if off < sizeHeaderTCP { - return errBadTCPOff - } else if off > len(tfrm.RawData()) { - return errShortTCP - } - return nil -} - -// ValidateSize checks the frame's size fields and compares with the actual buffer -// the frame. It returns a non-nil error on finding an inconsistency. -func (i6frm IPv6Frame) ValidateSize() error { +func (i6frm IPv6Frame) ValidateSize(v *Validator) { tl := i6frm.PayloadLength() if int(tl)+sizeHeaderIPv6 > len(i6frm.RawData()) { - return errShortIPv6 + v.gotErr(errShortIPv6) + } +} + +// ValidateSize checks the frame's size fields and compares with the actual buffer +// the frame. It returns a non-nil error on finding an inconsistency. +func (tfrm TCPFrame) ValidateSize(v *Validator) { + off := tfrm.HeaderLength() + if off < sizeHeaderTCP { + v.gotErr(errBadTCPOff) + } + if off > len(tfrm.RawData()) { + v.gotErr(errShortTCP) + } +} + +// ValidateSize checks the frame's size fields and compares with the actual buffer +// the frame. It returns a non-nil error on finding an inconsistency. +func (ufrm UDPFrame) ValidateSize(v *Validator) { + ul := ufrm.Length() + if ul < sizeHeaderUDP { + v.gotErr(errBadUDPLen) + } + if int(ul) > len(ufrm.RawData()) { + v.gotErr(errShortUDP) } - return nil }