diff --git a/examples/stack/main.go b/examples/stack/main.go index 3e58c50..44dfd1b 100644 --- a/examples/stack/main.go +++ b/examples/stack/main.go @@ -43,7 +43,9 @@ func main() { gatewayMAC := tap.HardwareAddr6() mtu := tap.MTU() - stack, err := NewEthernetTCPStack(stackHWAddr, gatewayMAC, addrPort, uint16(mtu)) + + var stack Stack + err := stack.Reset(stackHWAddr, gatewayMAC, addrPort.Addr(), mtu) if err != nil { log.Fatal(err) } @@ -113,7 +115,7 @@ func main() { } } -func doHTTP(conn *internet.TCPConn, hdr *httpraw.Header) error { +func doHTTP(conn *tcp.Conn, hdr *httpraw.Header) error { const asRequest = false if conn.State() != tcp.StateEstablished || conn.BufferedInput() == 0 { return nil // No data yet. @@ -160,10 +162,57 @@ type Stack struct { arp internet.NodeARP } -func (stack *Stack) OpenPassiveTCP(port uint16, iss tcp.Value) (*internet.TCPConn, error) { +func (stack *Stack) Reset(ourMAC, gwMAC [6]byte, ip netip.Addr, mtu int) (err error) { + err = stack.ethernet.Reset6(ourMAC, gwMAC, mtu) + if err != nil { + return err + } + err = stack.ip.Reset(ip) + if err != nil { + return err + } + stack.tcpports.Reset(uint64(lneto.IPProtoTCP), 2) + ipaddr := ip.As4() + err = stack.arp.Reset(arp.HandlerConfig{ + HardwareAddr: ourMAC[:], + ProtocolAddr: ipaddr[:], + MaxQueries: 2, + MaxPending: 2, + HardwareType: 1, + ProtocolType: ethernet.TypeIPv4, + }) + if err != nil { + return err + } + + // Register stacks and nodes. + err = stack.ethernet.Register(&stack.arp) + if err != nil { + return err + } + err = stack.ethernet.Register(&stack.ip) + if err != nil { + return err + } + err = stack.ip.Register(&stack.tcpports) + if err != nil { + return err + } + return nil +} + +func (stack *Stack) Recv(b []byte) error { + return stack.ethernet.Demux(b, 0) +} + +func (stack *Stack) Send(b []byte) (int, error) { + return stack.ethernet.Encapsulate(b, 0) +} + +func (stack *Stack) OpenPassiveTCP(port uint16, iss tcp.Value) (*tcp.Conn, error) { mtu := stack.ethernet.MTU() - conn := new(internet.TCPConn) - err := conn.Configure(&internet.TCPConnConfig{ + conn := new(tcp.Conn) + err := conn.Configure(&tcp.ConnConfig{ RxBuf: make([]byte, mtu), TxBuf: make([]byte, mtu), TxPacketQueueSize: 3, @@ -182,57 +231,6 @@ func (stack *Stack) OpenPassiveTCP(port uint16, iss tcp.Value) (*internet.TCPCon return conn, nil } -func NewEthernetTCPStack(ourMAC, gwMAC [6]byte, ip netip.AddrPort, mtu uint16) (*Stack, error) { - var stack Stack - var err error - err = stack.ethernet.Reset6(ourMAC, gwMAC, int(mtu)) - if err != nil { - return nil, err - } - err = stack.ip.Reset(ip.Addr()) - if err != nil { - return nil, err - } - stack.tcpports.Reset(uint64(lneto.IPProtoTCP), 2) - ipaddr := ip.Addr().As4() - err = stack.arp.Reset(arp.HandlerConfig{ - HardwareAddr: ourMAC[:], - ProtocolAddr: ipaddr[:], - MaxQueries: 2, - MaxPending: 2, - HardwareType: 1, - ProtocolType: ethernet.TypeIPv4, - }) - if err != nil { - return nil, err - } - - // Register stacks and nodes. - err = stack.ethernet.Register(&stack.arp) - if err != nil { - return nil, err - } - err = stack.ethernet.Register(&stack.ip) - if err != nil { - return nil, err - } - err = stack.ip.Register(&stack.tcpports) - if err != nil { - return nil, err - } - return &stack, nil -} - -func debugHex(b []byte) string { - var d []byte - for i := 0; i < len(b); i++ { - c1 := tblhex[b[i]&0xf] - c2 := tblhex[b[i]>>4] - d = append(d, c2, c1, ' ') - } - return string(d) -} - const tblhex = "0123456789abcdef" func getTCPFlags(frames []pcap.Frame, pkt []byte) (flags tcp.Flags) { diff --git a/examples/stackbasic/main.go b/examples/stackbasic/main.go index f99595c..7b9380a 100644 --- a/examples/stackbasic/main.go +++ b/examples/stackbasic/main.go @@ -120,7 +120,7 @@ func main() { } } -func doHTTP(conn *internet.TCPConn, hdr *httpraw.Header) error { +func doHTTP(conn *tcp.Conn, hdr *httpraw.Header) error { const asRequest = false if conn.State() != tcp.StateEstablished || conn.BufferedInput() == 0 { return nil // No data yet. @@ -160,7 +160,7 @@ func doHTTP(conn *internet.TCPConn, hdr *httpraw.Header) error { return nil } -func NewEthernetTCPStack(ourMAC, gwMAC [6]byte, ip netip.AddrPort, mtu uint16, slogger logger) (*LinkStack, *internet.TCPConn, error) { +func NewEthernetTCPStack(ourMAC, gwMAC [6]byte, ip netip.AddrPort, mtu uint16, slogger logger) (*LinkStack, *tcp.Conn, error) { var err error lStack := LinkStack{ logger: slogger, @@ -181,8 +181,8 @@ func NewEthernetTCPStack(ourMAC, gwMAC [6]byte, ip netip.AddrPort, mtu uint16, s proto: ethernet.TypeIPv4, lport: 0, }) - var conn internet.TCPConn - err = conn.Configure(&internet.TCPConnConfig{ + var conn tcp.Conn + err = conn.Configure(&tcp.ConnConfig{ RxBuf: make([]byte, mtu), TxBuf: make([]byte, mtu), TxPacketQueueSize: 3, diff --git a/internal/ip.go b/internal/ip.go new file mode 100644 index 0000000..e3ff6ee --- /dev/null +++ b/internal/ip.go @@ -0,0 +1,44 @@ +package internal + +import ( + "encoding/binary" + "errors" +) + +var ( + errUnsupportedIP = errors.New("unsupported IP version") + errInvalidIPVersionToSetAddr = errors.New("invalid ip version to setDstAddr") +) + +func GetIPSourceAddr(buf []byte) (addr []byte, id uint16, err error) { + version := buf[0] >> 4 + switch version { // + case 4: + addr = buf[12:16] + id = binary.BigEndian.Uint16(buf[4:6]) + case 6: + addr = buf[8:24] + default: + err = errUnsupportedIP + } + return addr, id, err +} + +func SetIPDestinationAddr(buf []byte, id uint16, addr []byte) (err error) { + var dstaddr []byte + version := buf[0] >> 4 + switch version { + case 4: + dstaddr = buf[16:20] + binary.BigEndian.PutUint16(buf[4:6], id) + case 6: + dstaddr = buf[24:40] + default: + err = errUnsupportedIP + } + if err == nil && len(dstaddr) != len(addr) { + return errInvalidIPVersionToSetAddr + } + copy(dstaddr, addr) + return nil +} diff --git a/internet/node-tcplistener.go b/internet/node-tcplistener.go new file mode 100644 index 0000000..346f2c4 --- /dev/null +++ b/internet/node-tcplistener.go @@ -0,0 +1,149 @@ +package internet + +import ( + "bytes" + "errors" + "log/slog" + "net" + "time" + + "github.com/soypat/lneto" + "github.com/soypat/lneto/internal" + "github.com/soypat/lneto/tcp" +) + +var _ StackNode = (*NodeTCPListener)(nil) + +type NodeTCPListener struct { + connID uint64 + conns []tcp.Conn + accepted []bool + port uint16 + getISS func() uint32 +} + +func (listener *NodeTCPListener) AcceptRaw() (*tcp.Conn, error) { + connid := listener.connID + for { + if listener.isClosed() || connid != listener.connID { + return nil, net.ErrClosed + } + for i := range listener.conns { + isAvailable := listener.connReceivedSyn(i) && !listener.connAccepted(i) + if !isAvailable { + continue + } + // Connection received as SYN and is not yet accepted. + listener.accepted[i] = true + return &listener.conns[i], nil + } + time.Sleep(5 * time.Millisecond) + } + panic("unreachable") +} + +func (listener *NodeTCPListener) Close() error { + if listener.isClosed() { + return errors.New("already closed") + } + listener.connID++ + listener.port = 0 + return nil +} + +func (listener *NodeTCPListener) LocalPort() uint16 { return listener.port } + +func (listener *NodeTCPListener) ConnectionID() *uint64 { return &listener.connID } + +func (listener *NodeTCPListener) Protocol() uint64 { return uint64(lneto.IPProtoTCP) } + +func (listener *NodeTCPListener) Encapsulate(carrierData []byte, tcpFrameOffset int) (int, error) { + if listener.isClosed() { + return 0, net.ErrClosed + } + for i := range listener.conns { + conn := &listener.conns[i] + if conn.State().IsClosed() { + continue + } + n, err := conn.Encapsulate(carrierData, tcpFrameOffset) + if err != nil { + listener.maintainConn(i, err) + } + if n == 0 { + continue + } + return n, err + } + return 0, nil +} + +func (listener *NodeTCPListener) Demux(carrierData []byte, tcpFrameOffset int) error { + if listener.isClosed() { + return net.ErrClosed + } + tfrm, err := tcp.NewFrame(carrierData[tcpFrameOffset:]) + if err != nil { + return err + } + addr, _, err := internal.GetIPSourceAddr(carrierData) + if err != nil { + return err + } + dst := tfrm.DestinationPort() + if dst != listener.port { + return errors.New("not our port") + } + src := tfrm.DestinationPort() + _, flags := tfrm.OffsetAndFlags() + for i := range listener.conns { + if listener.conns[i].RemotePort() != src || !bytes.Equal(listener.conns[i].RemoteAddr(), addr) { + continue + } + conn := &listener.conns[i] + err := conn.Demux(carrierData, tcpFrameOffset) + if err != nil { + listener.maintainConn(i, err) + } + return err + } + if !flags.HasAll(tcp.FlagSYN) { + return nil // Not a synchronizing packet, drop it. + } + // New connection must be assigned. + for i := range listener.conns { + conn := &listener.conns[i] + isOpen := !conn.State().IsClosed() + if isOpen { + continue + } + if conn.State() == tcp.StateTimeWait { + conn.Abort() + } + + err = conn.OpenListen(dst, tcp.Value(listener.getISS())) + if err != nil { + return err + } + return conn.Demux(carrierData, tcpFrameOffset) + } + slog.Error("tcpListener:no-free-conn") + return nil +} + +func (listener *NodeTCPListener) maintainConn(connIdx int, err error) { + if err == net.ErrClosed { + listener.conns[connIdx].Abort() + } +} + +func (listener *NodeTCPListener) isClosed() bool { + return listener.port == 0 +} + +func (listener *NodeTCPListener) connReceivedSyn(idx int) bool { + return listener.conns[idx].RemotePort() != 0 +} +func (listener *NodeTCPListener) connAccepted(idx int) bool { + return listener.accepted[idx] +} diff --git a/internet/stack-ip.go b/internet/stack-ip.go index 39fbcfc..46e16c0 100644 --- a/internet/stack-ip.go +++ b/internet/stack-ip.go @@ -164,7 +164,7 @@ func (sb *StackIP) Register(h StackNode) error { return nil } -func (sb *StackIP) RegisterTCPConn(conn *TCPConn) error { +func (sb *StackIP) RegisterTCPConn(conn *tcp.Conn) error { if conn.LocalPort() == 0 { return errZeroPort } diff --git a/internet/stackbasic_test.go b/internet/stackbasic_test.go index 7733cbe..82f95de 100644 --- a/internet/stackbasic_test.go +++ b/internet/stackbasic_test.go @@ -11,7 +11,7 @@ import ( func TestBasicStack(t *testing.T) { rng := rand.New(rand.NewSource(1)) var sbCl, sbSv StackIP - var connCl, connSv TCPConn + var connCl, connSv tcp.Conn setupClientServer(t, rng, &sbCl, &sbSv, &connCl, &connSv) var buf [2048]byte nextToSend := &sbCl @@ -37,7 +37,7 @@ func TestBasicStack(t *testing.T) { func TestBasicStack2(t *testing.T) { rng := rand.New(rand.NewSource(1)) var sbCl, sbSv StackIP - var connCl, connSv TCPConn + var connCl, connSv tcp.Conn setupClientServerEstablished(t, rng, &sbCl, &sbSv, &connCl, &connSv) } @@ -57,7 +57,7 @@ func expectExchange(t *testing.T, from, to *StackIP, buf []byte) { } } -func setupClientServerEstablished(t *testing.T, rng *rand.Rand, client, server *StackIP, connClient, connServer *TCPConn) { +func setupClientServerEstablished(t *testing.T, rng *rand.Rand, client, server *StackIP, connClient, connServer *tcp.Conn) { t.Helper() setupClientServer(t, rng, client, server, connClient, connServer) var buf [2048]byte @@ -85,7 +85,7 @@ func setupClientServerEstablished(t *testing.T, rng *rand.Rand, client, server * } } -func setupClientServer(t *testing.T, rng *rand.Rand, client, server *StackIP, connClient, connServer *TCPConn) { +func setupClientServer(t *testing.T, rng *rand.Rand, client, server *StackIP, connClient, connServer *tcp.Conn) { bufsize := 2048 // Ensure buffer sizes are OK with reused buffers. svip := netip.AddrPortFrom(netip.AddrFrom4([4]byte{192, 168, 1, 0}), 80) @@ -93,7 +93,7 @@ func setupClientServer(t *testing.T, rng *rand.Rand, client, server *StackIP, co server.SetAddr(svip.Addr()) client.SetAddr(clip.Addr()) - err := connServer.Configure(&TCPConnConfig{ + err := connServer.Configure(&tcp.ConnConfig{ RxBuf: make([]byte, bufsize), TxBuf: make([]byte, bufsize), TxPacketQueueSize: 3, @@ -102,7 +102,7 @@ func setupClientServer(t *testing.T, rng *rand.Rand, client, server *StackIP, co if err != nil { t.Fatal(err) } - err = connClient.Configure(&TCPConnConfig{ + err = connClient.Configure(&tcp.ConnConfig{ RxBuf: make([]byte, bufsize), TxBuf: make([]byte, bufsize), TxPacketQueueSize: 3, diff --git a/internet/tcpconn.go b/tcp/conn.go similarity index 68% rename from internet/tcpconn.go rename to tcp/conn.go index fd93b16..1b0d621 100644 --- a/internet/tcpconn.go +++ b/tcp/conn.go @@ -1,4 +1,4 @@ -package internet +package tcp import ( "bytes" @@ -12,17 +12,19 @@ import ( "github.com/soypat/lneto" "github.com/soypat/lneto/internal" - "github.com/soypat/lneto/ipv4" - "github.com/soypat/lneto/ipv6" - "github.com/soypat/lneto/tcp" ) var ( errDeadlineExceeded = os.ErrDeadlineExceeded ) -type TCPConn struct { - h tcp.Handler +// Conn builds on the [Handler] abstraction and adds IP header knowledge, time management, and familiar user facing API +// like Write and Read methods. +// +// Note that the complete emulation of [net.TCPConn] at this level of abstraction is yet a non-goal, +// even though the functionality provided is similar. +type Conn struct { + h Handler remoteAddr []byte rdead time.Time @@ -35,14 +37,14 @@ type TCPConn struct { logger } -type TCPConnConfig struct { +type ConnConfig struct { RxBuf []byte TxBuf []byte TxPacketQueueSize int Logger *slog.Logger } -func (conn *TCPConn) Configure(config *TCPConnConfig) (err error) { +func (conn *Conn) Configure(config *ConnConfig) (err error) { err = conn.h.SetBuffers(config.TxBuf, config.RxBuf, config.TxPacketQueueSize) if err != nil { return err @@ -52,20 +54,22 @@ func (conn *TCPConn) Configure(config *TCPConnConfig) (err error) { } // LocalPort returns the local port on which the socket is listening or connected to. -func (conn *TCPConn) LocalPort() uint16 { return conn.h.LocalPort() } +func (conn *Conn) LocalPort() uint16 { return conn.h.LocalPort() } // RemotePort returns the port of the incoming remote connection. Is non-zero if connection is established. -func (conn *TCPConn) RemotePort() uint16 { return conn.h.RemotePort() } +func (conn *Conn) RemotePort() uint16 { return conn.h.RemotePort() } + +func (conn *Conn) RemoteAddr() []byte { return conn.remoteAddr } // State returns the TCP state of the socket. -func (conn *TCPConn) State() tcp.State { return conn.h.State() } +func (conn *Conn) State() State { return conn.h.State() } // BufferedInput returns the number of bytes in the socket's receive/input buffer. -func (conn *TCPConn) BufferedInput() int { return conn.h.BufferedInput() } +func (conn *Conn) BufferedInput() int { return conn.h.BufferedInput() } // OpenActive opens a connection to a remote peer with a known IP address and port combination. // iss is the initial send sequence number which is ideally a random number which is far away from the last sequence number used on a connection to the same host. -func (conn *TCPConn) OpenActive(remote netip.AddrPort, localPort uint16, iss tcp.Value) error { +func (conn *Conn) OpenActive(remote netip.AddrPort, localPort uint16, iss Value) error { err := conn.h.OpenActive(localPort, remote.Port(), iss) if err != nil { return err @@ -84,7 +88,7 @@ func (conn *TCPConn) OpenActive(remote netip.AddrPort, localPort uint16, iss tcp // OpenListen opens a passive connection which listens for the first SYN packet to be received on a local port. // iss is the initial send sequence number which is usually a randomly chosen number. -func (conn *TCPConn) OpenListen(localPort uint16, iss tcp.Value) error { +func (conn *Conn) OpenListen(localPort uint16, iss Value) error { err := conn.h.OpenListen(localPort, iss) if err != nil { return err @@ -93,17 +97,27 @@ func (conn *TCPConn) OpenListen(localPort uint16, iss tcp.Value) error { return nil } -func (conn *TCPConn) Close() error { +func (conn *Conn) Close() error { conn.trace("TCPConn.Close") return conn.h.Close() } -func (conn *TCPConn) Demux(buf []byte, off int) (err error) { +// Abort terminates all state of the connection forcibly. +func (conn *Conn) Abort() { + conn.h.Abort() + *conn = Conn{ + h: conn.h, + remoteAddr: conn.remoteAddr[:0], + logger: conn.logger, + } +} + +func (conn *Conn) Demux(buf []byte, off int) (err error) { conn.trace("tcpconn.Recv:start") if off >= len(buf) { return errors.New("bad offset in TCPConn.Recv") } - raddr, id, err := getIPAddr(buf[:off]) + raddr, id, err := internal.GetIPSourceAddr(buf[:off]) if err != nil { return err } @@ -122,7 +136,7 @@ func (conn *TCPConn) Demux(buf []byte, off int) (err error) { } // Write writes argument data to the TCPConns's output buffer which is queued to be sent. -func (conn *TCPConn) Write(b []byte) (int, error) { +func (conn *Conn) Write(b []byte) (int, error) { err := conn.checkPipeOpen() if err != nil { return 0, err @@ -164,7 +178,7 @@ func (conn *TCPConn) Write(b []byte) (int, error) { // Read reads data from the socket's input buffer. If the buffer is empty, // Read will block until data is available or connection closes. -func (conn *TCPConn) Read(b []byte) (int, error) { +func (conn *Conn) Read(b []byte) (int, error) { err := conn.checkPipeOpen() if err != nil { return 0, err @@ -172,7 +186,7 @@ func (conn *TCPConn) Read(b []byte) (int, error) { conn.trace("TCPConn.Read:start") connid := conn.h.ConnectionID() backoff := internal.NewBackoff(internal.BackoffTCPConn) - for conn.h.BufferedInput() == 0 && conn.State() == tcp.StateEstablished { + for conn.h.BufferedInput() == 0 && conn.State() == StateEstablished { if conn.abortErr != nil { return 0, conn.abortErr } else if connid != conn.h.ConnectionID() { @@ -187,7 +201,7 @@ func (conn *TCPConn) Read(b []byte) (int, error) { return n, err } -func (conn *TCPConn) checkPipeOpen() error { +func (conn *Conn) checkPipeOpen() error { if conn.abortErr != nil { return conn.abortErr } @@ -198,11 +212,11 @@ func (conn *TCPConn) checkPipeOpen() error { return nil } -func (conn *TCPConn) Encapsulate(buf []byte, off int) (n int, err error) { +func (conn *Conn) Encapsulate(buf []byte, off int) (n int, err error) { if len(conn.remoteAddr) == 0 { return 0, errors.New("unset IP address") } - raddr, _, err := getIPAddr(buf[:off]) + raddr, _, err := internal.GetIPSourceAddr(buf[:off]) if err != nil { return 0, err } else if len(raddr) != len(conn.remoteAddr) { @@ -212,8 +226,7 @@ func (conn *TCPConn) Encapsulate(buf []byte, off int) (n int, err error) { if err != nil { return 0, err } - - err = setDstAddr(buf[:off], conn.ipID, conn.remoteAddr) + err = internal.SetIPDestinationAddr(buf[:off], conn.ipID, conn.remoteAddr) if err != nil { return 0, err } @@ -221,63 +234,16 @@ func (conn *TCPConn) Encapsulate(buf []byte, off int) (n int, err error) { return n, nil } -func (conn *TCPConn) Protocol() uint64 { +func (conn *Conn) Protocol() uint64 { return uint64(lneto.IPProtoTCP) } -func getIPAddr(buf []byte) (addr []byte, id uint16, err error) { - switch buf[0] >> 4 { - case 4: - ifrm4, err := ipv4.NewFrame(buf) - if err != nil { - return addr, 0, err - } - addr = ifrm4.SourceAddr()[:] - id = ifrm4.ID() - case 6: - ifrm6, err := ipv6.NewFrame(buf) - if err != nil { - return addr, 0, err - } - addr = ifrm6.SourceAddr()[:] - default: - err = errors.New("unsupported IP version") - } - return addr, id, err -} - -func setDstAddr(buf []byte, id uint16, addr []byte) (err error) { - var dstaddr []byte - switch buf[0] >> 4 { - case 4: - ifrm4, err := ipv4.NewFrame(buf) - if err != nil { - return err - } - dstaddr = ifrm4.DestinationAddr()[:] - ifrm4.SetID(id) - case 6: - ifrm6, err := ipv6.NewFrame(buf) - if err != nil { - return err - } - dstaddr = ifrm6.DestinationAddr()[:] - default: - err = errors.New("unsupported IP version") - } - if err == nil && len(dstaddr) != len(addr) { - return errors.New("invalid ip version to setDstAddr") - } - copy(dstaddr, addr) - return nil -} - -func (conn *TCPConn) isRaddrSet() bool { +func (conn *Conn) isRaddrSet() bool { return len(conn.remoteAddr) != 0 } -func (conn *TCPConn) reset(h tcp.Handler) { - *conn = TCPConn{ +func (conn *Conn) reset(h Handler) { + *conn = Conn{ h: h, remoteAddr: conn.remoteAddr[:0], logger: conn.logger, @@ -287,7 +253,7 @@ func (conn *TCPConn) reset(h tcp.Handler) { // SetDeadline sets the read and write deadlines associated // with the connection. It is equivalent to calling both // SetReadDeadline and SetWriteDeadline. Implements [net.Conn]. -func (conn *TCPConn) SetDeadline(t time.Time) error { +func (conn *Conn) SetDeadline(t time.Time) error { err := conn.SetReadDeadline(t) if err != nil { return err @@ -297,7 +263,7 @@ func (conn *TCPConn) SetDeadline(t time.Time) error { // SetReadDeadline sets the deadline for future Read calls // and any currently-blocked Read call. A zero value for t means Read will not time out. -func (conn *TCPConn) SetReadDeadline(t time.Time) error { +func (conn *Conn) SetReadDeadline(t time.Time) error { conn.trace("TCPConn.SetReadDeadline:start") err := conn.checkPipeOpen() if err == nil { @@ -311,7 +277,7 @@ func (conn *TCPConn) SetReadDeadline(t time.Time) error { // Even if write times out, it may return n > 0, indicating that // some of the data was successfully written. // A zero value for t means Write will not time out. -func (conn *TCPConn) SetWriteDeadline(t time.Time) error { +func (conn *Conn) SetWriteDeadline(t time.Time) error { conn.trace("TCPConn.SetWriteDeadline:start") err := conn.checkPipeOpen() if err == nil { @@ -320,10 +286,10 @@ func (conn *TCPConn) SetWriteDeadline(t time.Time) error { return err } -func (conn *TCPConn) deadlineExceeded(deadline time.Time) bool { +func (conn *Conn) deadlineExceeded(deadline time.Time) bool { return !deadline.IsZero() && time.Since(deadline) > 0 } -func (conn *TCPConn) ConnectionID() *uint64 { +func (conn *Conn) ConnectionID() *uint64 { return conn.h.ConnectionID() } diff --git a/tcp/control.go b/tcp/control.go index 9f5e856..f2c7a0c 100644 --- a/tcp/control.go +++ b/tcp/control.go @@ -19,6 +19,10 @@ import ( // A ControlBlock's internal state is modified by the available "System Calls" as defined in // RFC9293, such as Close, Listen/Open, Send, and Receive. // Sent and received data is represented with the [Segment] struct type. +// +// Note that [ControlBlock] is the lowest level implementation of TCP and as such is missing most useful functionality. +// See [Handler], which uses ControlBlock, for a higher level implementation. [Conn] is an even higher level implementation +// which makes use of a [Handler]. type ControlBlock struct { // # Send Sequence Space // diff --git a/tcp/handler.go b/tcp/handler.go index 0b31240..806bf7b 100644 --- a/tcp/handler.go +++ b/tcp/handler.go @@ -20,6 +20,8 @@ var ( // related to data buffering, frame sequencing and connection state handling. // Does NOT implement IP related logic, so no CRC calculation/validation or pseudo header logic. // Does NOT implement connection lifetime handling, so NO deadlines, keepalives, backoffs or anything that requires use of time package. +// +// See [Conn] for a higher level abstraction of a TCP connection, and see [ControlBlock] for the lower level bits of a TCP connection. type Handler struct { connid uint64 scb ControlBlock @@ -117,10 +119,10 @@ func (h *Handler) Abort() { func (h *Handler) reset(localPort, remotePort uint16, iss Value) { *h = Handler{ + connid: h.connid + 1, scb: h.scb, bufTx: h.bufTx, bufRx: h.bufRx, - connid: h.connid + 1, localPort: localPort, remotePort: remotePort, validator: h.validator, @@ -300,7 +302,12 @@ func (h *Handler) BufferedInput() int { return h.bufRx.Buffered() } -// AwaitingSynResponse checks if the Handler is waiting for a Syn to arrive. +// InUse returns true if the connection has been initialized and is being used to reach a remote port or if it is awaiting a remote packet. +func (h *Handler) InUse() bool { + return h.remotePort != 0 || !h.State().IsClosed() +} + +// AwaitingSynResponse returns true if the Handler is an active client opened with [Handler.OpenActive] and has already sent out the first SYN packet to the remote client. func (h *Handler) AwaitingSynResponse() bool { return h.remotePort != 0 && h.scb.State() == StateSynSent }