From 123d4d20cc87dc0fe69c7f7e5af51227271a89d6 Mon Sep 17 00:00:00 2001 From: soypat Date: Mon, 28 Jul 2025 00:23:53 -0300 Subject: [PATCH] begin adding icmpv4 --- internet/stack-ip.go | 39 +++++++++--- ipv4/icmpv4/icmpv4.go | 138 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 ipv4/icmpv4/icmpv4.go diff --git a/internet/stack-ip.go b/internet/stack-ip.go index 1264042..64b0384 100644 --- a/internet/stack-ip.go +++ b/internet/stack-ip.go @@ -11,6 +11,7 @@ import ( "github.com/soypat/lneto/ethernet" "github.com/soypat/lneto/internal" "github.com/soypat/lneto/ipv4" + "github.com/soypat/lneto/ipv4/icmpv4" "github.com/soypat/lneto/tcp" "github.com/soypat/lneto/udp" ) @@ -18,11 +19,12 @@ import ( var _ StackNode = (*StackIP)(nil) type StackIP struct { - connID uint64 - ipID uint16 - ip [4]byte - validator lneto.Validator - handlers []node + connID uint64 + ipID uint16 + ip [4]byte + validator lneto.Validator + handlers []node + pendingICMP [][]byte logger } @@ -36,11 +38,12 @@ func (sb *StackIP) Reset(addr netip.Addr, maxNodes int) error { } sb.handlers = slices.Grow(sb.handlers[:0], maxNodes) *sb = StackIP{ - connID: sb.connID + 1, - validator: sb.validator, - handlers: sb.handlers, - logger: sb.logger, - ip: sb.ip, + connID: sb.connID + 1, + validator: sb.validator, + handlers: sb.handlers, + logger: sb.logger, + ip: sb.ip, + pendingICMP: make([][]byte, maxNodes*4), } return nil } @@ -99,6 +102,9 @@ func (sb *StackIP) Demux(carrierData []byte, offset int) error { off := ifrm.HeaderLength() totalLen := ifrm.TotalLength() proto := ifrm.Protocol() + if proto == lneto.IPProtoICMP { + return sb.recvicmp(ifrm.RawData(), ifrm.HeaderLength()) + } nodeIdx := getNodeByProto(sb.handlers, uint16(proto)) if nodeIdx < 0 { // Drop packet. @@ -218,6 +224,19 @@ func (sb *StackIP) Register(h StackNode) error { }) } +func (sb *StackIP) recvicmp(carrierData []byte, offset int) error { + var crc lneto.CRC791 + cfrm, err := icmpv4.NewFrame(carrierData[offset:]) + if err != nil { + return err + } + cfrm.CRCWrite(&crc) + if crc.Sum16() != cfrm.CRC() { + return errors.New("ICMP CRC mismatch") + } + return nil +} + type logger struct { log *slog.Logger } diff --git a/ipv4/icmpv4/icmpv4.go b/ipv4/icmpv4/icmpv4.go new file mode 100644 index 0000000..317e1d7 --- /dev/null +++ b/ipv4/icmpv4/icmpv4.go @@ -0,0 +1,138 @@ +package icmpv4 + +import ( + "encoding/binary" + "errors" + + "github.com/soypat/lneto" +) + +type Type uint8 + +const ( + TypeEchoReply Type = 0 // echo reply + TypeEcho Type = 8 // echo + + TypeDestinationUnreachable Type = 3 // destination unreachable + TypeSourceQuench Type = 4 // source quench + TypeRedirect Type = 5 // redirect + + TypeTimeExceeded Type = 11 // time exceeded + TypeParameterProblem Type = 12 // parameter problem + + TypeTimestamp Type = 13 // timestamp + TypeTimestampReply Type = 14 // timestamp reply + + TypeInfoRequest Type = 15 // information request + TypeInfoRequestReply Type = 16 // information request reply +) + +type CodeTimeExceeded uint8 + +const ( + CodeExceededInTransit CodeTimeExceeded = iota // TTL exceeded in transit + CodeFragmentReassembly // fragment reassembly time exceeded +) + +type CodeDestinationUnreachable uint8 + +const ( + CodeNetUnreachable CodeDestinationUnreachable = iota // net unreachable + CodeHostUnreachable // host unreachable + CodeProtoUnreachable // protocol unreachable + CodePortUnreachable // port unreachable + CodeFragNeededAndDFSet // fragmentation needed and DF set + CodeSourceRouteFailed // source route failed +) + +type CodeRedirect uint8 + +const ( + CodeRedirectForNetwork CodeRedirect = iota // redirect for network + CodeRedirectForHost // redirect for host + CodeRedirectForToSAndNetwork // redirect for ToS+network + CodeRedirectToSAndHost // redirect for ToS+host +) + +var ( + errShortFrame = errors.New("icmpv4: short frame") +) + +func NewFrame(buf []byte) (Frame, error) { + if len(buf) < 8 { + return Frame{}, errShortFrame + } + return Frame{buf: buf}, nil +} + +type Frame struct { + buf []byte +} + +func (frm Frame) Type() Type { return Type(frm.buf[0]) } + +func (frm Frame) SetType(t Type) { frm.buf[0] = uint8(t) } + +func (frm Frame) Code() uint8 { return frm.buf[1] } + +func (frm Frame) SetCode(code uint8) { frm.buf[1] = code } + +// CRC returns the checksum field of the frame. +func (frm Frame) CRC() uint16 { + return binary.BigEndian.Uint16(frm.buf[2:4]) +} + +// SetCRC sets the checksum field of the frame. +func (frm Frame) SetCRC(crc uint16) { + binary.BigEndian.PutUint16(frm.buf[2:4], crc) +} + +// CRCWrite calculates the checksum of the ICMP packet. Treats the checksum field as zero as per RFC 792. +func (frm Frame) CRCWrite(crc *lneto.CRC791) { + crc.AddUint16(binary.BigEndian.Uint16(frm.buf[0:2])) + crc.Write(frm.buf[4:]) +} + +func (frm Frame) payload() []byte { + return frm.buf[4:] +} + +type FrameDestinationUnreachable struct { + Frame +} + +func (frm FrameDestinationUnreachable) Code() CodeDestinationUnreachable { + return CodeDestinationUnreachable(frm.Frame.Code()) +} + +func (frm FrameDestinationUnreachable) SetCode(code CodeDestinationUnreachable) { + frm.Frame.SetCode(uint8(code)) +} + +type FrameEcho struct { + Frame +} + +func (frm FrameEcho) Identifier() uint16 { + return binary.BigEndian.Uint16(frm.buf[4:6]) +} + +func (frm FrameEcho) SetIdentifier(id uint16) { + binary.BigEndian.PutUint16(frm.buf[4:6], id) +} + +func (frm FrameEcho) SequenceNumber() uint16 { + return binary.BigEndian.Uint16(frm.buf[6:8]) +} + +func (frm FrameEcho) SetSequenceNumber(seq uint16) { + binary.BigEndian.PutUint16(frm.buf[6:8], seq) +} + +func (frm FrameEcho) Data() []byte { + return frm.buf[8:] +} + +func (frm FrameEcho) RawData() []byte { + return frm.buf +}