diff --git a/examples/stack/main.go b/examples/stack/main.go index 9c3e774..6a18b03 100644 --- a/examples/stack/main.go +++ b/examples/stack/main.go @@ -70,11 +70,15 @@ func main() { if err != nil { log.Fatal(err) } + log.Println("success receiving packet") n, err := lStack.HandleEth(buf) if err != nil { log.Fatal(n, err) + } else if n > 0 { + log.Println("success sending packet") + } else { + log.Println("no packet sent") } - log.Println("success receiving packet") } type Handler interface { diff --git a/tcp/control.go b/tcp/control.go index 9627257..a4bf173 100644 --- a/tcp/control.go +++ b/tcp/control.go @@ -138,14 +138,13 @@ type recvSpace struct { WND Size // receive window defined by local. Permitted number of remote unacked octets in flight. } -// Open implements a passive/active opening of a connection. -// state must be StateListen or StateSynSent. -func (tcb *ControlBlock) Open(iss Value, wnd Size, state State) (err error) { +// Open implements a passive opening of a connection (wait for incoming packets). +// Upon success [ControlBlock] enters LISTEN state, such as that of a server. +// To open an active connection use [ControlBlock.Send] with a segment generated with [ClientSynSegment]. +func (tcb *ControlBlock) Open(iss Value, wnd Size) (err error) { switch { case tcb._state != StateClosed && tcb._state != StateListen: err = errTCBNotClosed - case state != StateListen && state != StateSynSent: - err = errInvalidState case wnd > math.MaxUint16: err = errWindowTooLarge } @@ -153,15 +152,17 @@ func (tcb *ControlBlock) Open(iss Value, wnd Size, state State) (err error) { tcb.logerr("tcb:open", slog.String("err", err.Error())) return err } - tcb._state = state + tcb._state = StateListen + tcb.prepareToHandshake(iss, wnd) + tcb.trace("tcb:open-server") + return nil +} + +// prepareToHandshake initializes the TCB send/receive spaces with initial send sequence number and local window. +func (tcb *ControlBlock) prepareToHandshake(iss Value, wnd Size) { tcb.resetRcv(wnd, 0) tcb.resetSnd(iss, 1) tcb.pending = [2]Flags{} - if state == StateSynSent { - tcb.pending[0] = FlagSYN - } - tcb.trace("tcb:open", slog.String("state", tcb._state.String())) - return nil } // HasPending returns true if there is a pending control segment to send. Calls to Send will advance the pending queue. @@ -305,6 +306,12 @@ func (tcb *ControlBlock) Send(seg Segment) error { hasACK := seg.Flags.HasAny(FlagACK) var newPending Flags switch tcb._state { + case StateClosed: + if seg.Flags == FlagSYN { + tcb._state = StateSynSent + tcb.prepareToHandshake(seg.SEQ, seg.WND) + tcb.trace("tcb:open-client") + } case StateSynRcvd: if hasFIN { tcb._state = StateFinWait1 // RFC 9293: 3.10.4 CLOSE call. @@ -348,14 +355,15 @@ func (tcb *ControlBlock) Send(seg Segment) error { func (tcb *ControlBlock) validateOutgoingSegment(seg Segment) (err error) { hasAck := seg.Flags.HasAny(FlagACK) - checkSeq := !seg.Flags.HasAny(FlagRST) + isFirst := tcb._state == StateClosed && seg.isFirstSYN() + checkSeq := !isFirst && !seg.Flags.HasAny(FlagRST) seglast := seg.Last() // Extra check for when send Window is zero and no data is being sent. zeroWindowOK := tcb.snd.WND == 0 && seg.DATALEN == 0 && seg.SEQ == tcb.snd.NXT outOfWindow := checkSeq && !seg.SEQ.InWindow(tcb.snd.NXT, tcb.snd.WND) && !zeroWindowOK switch { - case tcb._state == StateClosed: + case tcb._state == StateClosed && !isFirst: err = io.ErrClosedPipe case seg.WND > math.MaxUint16: err = errWindowTooLarge diff --git a/tcp/definitions.go b/tcp/definitions.go index 6a95ab8..9b5f950 100644 --- a/tcp/definitions.go +++ b/tcp/definitions.go @@ -68,6 +68,25 @@ func (seg *Segment) Last() Value { return Add(seg.SEQ, seglen) - 1 } +func (seg Segment) isFirstSYN() bool { + return seg.Flags == FlagSYN && seg.ACK == 0 && seg.DATALEN == 0 && seg.WND > 0 +} + +// 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: +// +// synseg := ClientSynSegment(100, 256) +// err := clientTCB.Send(synseg) // By now the client's TCB is in StateSynSent and is attempting to open a connection. +func ClientSynSegment(clientISS Value, clientWND Size) Segment { + return Segment{ + SEQ: clientISS, + WND: clientWND, + Flags: FlagSYN, + ACK: 0, + DATALEN: 0, + } +} + // StringExchange returns a string representation of a segment exchange over // a network in RFC9293 styled visualization. invertDir inverts the arrow directions. // i.e: diff --git a/tcp/handler.go b/tcp/handler.go index 0b623d9..33b8dcc 100644 --- a/tcp/handler.go +++ b/tcp/handler.go @@ -53,7 +53,7 @@ func (h *Handler) LocalPort() uint16 { func (h *Handler) Open(state State, localPort, remotePort uint16, iss Value) error { // Open will fail unless SCB in closed state. - err := h.scb.Open(iss, Size(h.bufRx.Size()), state) + err := h.scb.Open(iss, Size(h.bufRx.Size())) if err != nil { return err } @@ -156,13 +156,15 @@ func (h *Handler) Send(b []byte) (int, error) { // No pending control segment or data to send. Yield. return 0, nil } - n, seq, err := h.bufTx.MakePacket(b[sizeHeaderTCP : sizeHeaderTCP+segment.DATALEN]) - if err != nil { - return 0, err - } else if seq != segment.SEQ { - panic("mismatching sequence numbers") - } else if n != int(segment.DATALEN) { - panic("expected n == available") + if available > 0 { + n, seq, err := h.bufTx.MakePacket(b[sizeHeaderTCP : sizeHeaderTCP+segment.DATALEN]) + if err != nil { + return 0, err + } else if seq != segment.SEQ { + panic("mismatching sequence numbers") + } else if n != int(segment.DATALEN) { + panic("expected n == available") + } } } prevState := h.scb.State() diff --git a/tcp/handler_test.go b/tcp/handler_test.go new file mode 100644 index 0000000..aa21417 --- /dev/null +++ b/tcp/handler_test.go @@ -0,0 +1,20 @@ +package tcp + +import ( + "math/rand" + "testing" +) + +func TestHandler(t *testing.T) { + +} + +func setupClientServer(rng *rand.Rand) (client, server Handler) { + + // err := server.Open(StateListen, uint16(rng.Uint32()), 0, 0) + // if err != nil { + // panic(err) + // } + // err = client.Open(State) + return client, server +} diff --git a/tcp/tcp_test.go b/tcp/tcp_test.go index 27ebfa9..2ba2fd4 100644 --- a/tcp/tcp_test.go +++ b/tcp/tcp_test.go @@ -316,22 +316,28 @@ func TestExchange_rfc9293_figure13(t *testing.T) { func TestExchange_noDupAckDuringEstablished(t *testing.T) { var tcbA tcp.ControlBlock const issA, issB, windowA, windowB = 300, 334222749, 256, 64240 - err := tcbA.Open(issA, issA, tcp.StateSynSent) + synseg := tcp.ClientSynSegment(issA, windowA) + + // err := tcbA.Open(issA, issA, tcp.StateSynSent) tcbA.SetRecvWindow(windowA) - if err != nil { - t.Fatal(err) - } + // if err != nil { + // t.Fatal(err) + // } establishA := []tcp.Exchange{ - 0: { // B sends SYN to A. + 0: { // A sends SYN to B. + Outgoing: &synseg, + WantState: tcp.StateSynSent, + }, + 1: { // B sends SYN to A. Incoming: &tcp.Segment{SEQ: issB, ACK: 0, WND: windowB, Flags: tcp.FlagSYN}, WantPending: &tcp.Segment{SEQ: issA, ACK: issB + 1, WND: windowA, Flags: SYNACK}, WantState: tcp.StateSynRcvd, }, - 1: { // Send SYNACK to B. + 2: { // Send SYNACK to B. Outgoing: &tcp.Segment{SEQ: issA, ACK: issB + 1, WND: windowA, Flags: SYNACK}, WantState: tcp.StateSynRcvd, }, - 2: { // B ACKs SYNACK, thus establishing the connection on both sides. + 3: { // B ACKs SYNACK, thus establishing the connection on both sides. Incoming: &tcp.Segment{SEQ: issB + 1, ACK: issA + 1, WND: windowB, Flags: tcp.FlagACK}, WantState: tcp.StateEstablished, },