From d996040d92d26ae814fafa38c3981935381a122b Mon Sep 17 00:00:00 2001 From: soypat Date: Thu, 20 Feb 2025 23:41:27 -0300 Subject: [PATCH] add stringer methods --- README.md | 7 +++++++ examples/stack/main.go | 3 +++ ipv4/frame.go | 15 +++++++++++++++ tcp/definitions.go | 7 +++++++ tcp/frame.go | 4 +++- tcp/handler.go | 11 ++++++++--- 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a8520b3..f317c68 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,11 @@ go mod download github.com/soypat/lneto@latest ``` +## Developing (linux) +- [`tap`](./examples/tap) (linux only, root privilidges required) Program opens a TAP interface and assigns an IP address to it and exposes the interface via a HTTP interface. This program is run with root privilidges to facilitate debugging of lneto since no root privilidges are required to interact with the HTTP interface exposed. + - `POST http://127.0.0.1:7070/send`: Receives a POST with request body containing JSON string of data to send over TAP interface. Response contains only status code. + - `GET http://127.0.0.1:7070/recv`: Receives a GET request. Response contains a JSON string of oldest unread TAP interface packet. If string is empty then there is no more data to read. + +- [`stack`](./examples/stack) Contains stack implementation which can interact with `tap` program. No root privilidges required. + - Can expose a HTTP server. \ No newline at end of file diff --git a/examples/stack/main.go b/examples/stack/main.go index 305670f..f2ca15a 100644 --- a/examples/stack/main.go +++ b/examples/stack/main.go @@ -394,6 +394,7 @@ func (ts *TCPStack) Recv(ipFrame []byte, tcpOff int) error { case 4: ifrm, _ := ipv4.NewFrame(ipFrame) ifrm.CRCWriteTCPPseudo(&ts.crc) + ts.log.Info("tcpStack:recv", slog.String("ipfrm", ifrm.String()), slog.String("frame", tfrm.String())) case 6: i6frm, _ := ipv6.NewFrame(ipFrame) i6frm.CRCWritePseudo(&ts.crc) @@ -435,6 +436,7 @@ func (ts *TCPStack) Handle(ipFrame []byte, tcpOff int) (n int, err error) { } // TCP packet written. tfrm, _ := tcp.NewFrame(ipFrame[tcpOff : tcpOff+n]) + ts.validator.ResetErr() tfrm.ValidateSize(&ts.validator) // Perform basic validation. if err = ts.validator.Err(); err != nil { @@ -445,6 +447,7 @@ func (ts *TCPStack) Handle(ipFrame []byte, tcpOff int) (n int, err error) { case 4: ifrm, _ := ipv4.NewFrame(ipFrame) ifrm.CRCWriteTCPPseudo(&ts.crc) + ts.log.Info("tcpStack:send", slog.String("ipfrm", ifrm.String()), slog.String("frame", tfrm.String())) case 6: i6frm, _ := ipv6.NewFrame(ipFrame) i6frm.CRCWritePseudo(&ts.crc) diff --git a/ipv4/frame.go b/ipv4/frame.go index d53b38c..cc93e34 100644 --- a/ipv4/frame.go +++ b/ipv4/frame.go @@ -3,6 +3,8 @@ package ipv4 import ( "encoding/binary" "errors" + "fmt" + "net/netip" "github.com/soypat/lneto/lneto2" ) @@ -218,3 +220,16 @@ func (ifrm Frame) ValidateExceptCRC(v *lneto2.Validator) { v.AddError(errEvil) } } + +func (ifrm Frame) String() string { + dst := netip.AddrFrom4(*ifrm.DestinationAddr()) + src := netip.AddrFrom4(*ifrm.SourceAddr()) + + hl := ifrm.HeaderLength() + tl := int(ifrm.TotalLength()) + ttl := ifrm.TTL() + id := ifrm.ID() + proto := ifrm.Protocol() + tos := ifrm.ToS() + return fmt.Sprintf("IP %s SRC=%s DST=%s LEN=%d OPT=%d TTL=%d ID=%d ToS=0x%x", proto.String(), src.String(), dst.String(), tl, tl-hl, ttl, id, tos) +} diff --git a/tcp/definitions.go b/tcp/definitions.go index 0a6f700..a0d928d 100644 --- a/tcp/definitions.go +++ b/tcp/definitions.go @@ -73,6 +73,13 @@ func (seg Segment) isFirstSYN() bool { return seg.Flags == FlagSYN && seg.ACK == 0 && seg.DATALEN == 0 && seg.WND > 0 } +func (seg Segment) String() string { + if seg.DATALEN == 0 { + return fmt.Sprintf("SEG %s ACK=%d SEQ=%d WND=%d", seg.Flags, seg.ACK, seg.SEQ, seg.WND) + } + return fmt.Sprintf("SEG %s ACK=%d SEQ=%d WND=%d DATALEN=%d", seg.Flags, seg.ACK, seg.SEQ, seg.WND, seg.DATALEN) +} + // ClientSynSegment is a the first packet sent over a TCP connection to a server. Typically the client // will call ClientSynSegment to generate a new SYN packet to send over to the server to initiate communications: // diff --git a/tcp/frame.go b/tcp/frame.go index 752cf51..6ba67ff 100644 --- a/tcp/frame.go +++ b/tcp/frame.go @@ -174,8 +174,10 @@ func (frm Frame) ClearHeader() { } func (tfrm Frame) String() string { + src := tfrm.SourcePort() + dst := tfrm.DestinationPort() seg := tfrm.Segment(len(tfrm.Payload())) - return fmt.Sprintf("%+v", seg) + return fmt.Sprintf("TCP :%d -> :%d %s", src, dst, seg.String()) } // diff --git a/tcp/handler.go b/tcp/handler.go index 0e8f271..9879548 100644 --- a/tcp/handler.go +++ b/tcp/handler.go @@ -11,7 +11,8 @@ import ( ) var ( - errMismatchedPort = errors.New("mismatched port") + errMismatchedSrcPort = errors.New("source port mismatch") + errMismatchedDstPort = errors.New("destination port mismatch") ) // Handler is a low level TCP handling data structure. It implements logic @@ -120,13 +121,14 @@ func (h *Handler) Recv(b []byte) error { if err != nil { return err } + remotePort := tfrm.SourcePort() if h.remotePort != 0 && remotePort != h.remotePort { - return errMismatchedPort + return errMismatchedSrcPort } dstPort := tfrm.DestinationPort() if h.localPort != dstPort { - return errMismatchedPort + return errMismatchedDstPort } payload := tfrm.Payload() if len(payload) > h.bufRx.Free() { @@ -160,6 +162,9 @@ func (h *Handler) Recv(b []byte) error { h.debug("tcp.Handler:rx-remoteport-set", slog.Uint64("port", uint64(h.localPort)), slog.Uint64("remoteport", uint64(remotePort))) h.remotePort = remotePort } + if h.logenabled(internal.LevelTrace) { + h.trace("tcp.Handler:rx-done", slog.Uint64("port", uint64(h.localPort)), slog.Uint64("remoteport", uint64(remotePort)), slog.String("seg", segIncoming.String())) + } return nil }