mirror of
https://github.com/soypat/lneto.git
synced 2026-08-10 01:43:41 +00:00
474 lines
17 KiB
Go
474 lines
17 KiB
Go
package tcp
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/bits"
|
|
"strconv"
|
|
"strings"
|
|
"unsafe"
|
|
)
|
|
|
|
//go:generate stringer -type=State,OptionKind -linecomment -output stringers.go .
|
|
|
|
var (
|
|
// errDropSegment is a flag that signals to drop a segment silently.
|
|
errDropSegment = errors.New("drop segment")
|
|
errWindowTooLarge = errors.New("invalid window size > 2**16")
|
|
|
|
errBufferTooSmall = errors.New("tcp buffer too small")
|
|
errNeedClosedTCBToOpen = errors.New("need closed TCB to call open")
|
|
errInvalidState = errors.New("invalid state")
|
|
errConnNotExist = errors.New("connection does not exist")
|
|
errConnectionClosing = errors.New("connection closing")
|
|
errExpectedSYN = errors.New("seqs:expected SYN")
|
|
errBadSegack = errors.New("seqs:bad segack")
|
|
errFinwaitExpectedACK = errors.New("seqs:finwait1 expected ACK")
|
|
errFinwaitExpectedFinack = errors.New("seqs:finwait2 expected FINACK")
|
|
|
|
errWindowOverflow = newRejectErr("wnd > 2**16")
|
|
errSeqNotInWindow = newRejectErr("seq not in snd/rcv.wnd")
|
|
errZeroWindow = newRejectErr("zero window")
|
|
errLastNotInWindow = newRejectErr("last not in snd/rcv.wnd")
|
|
errRequireSequential = newRejectErr("seq != rcv.nxt (require sequential segments)")
|
|
errAckNotNext = newRejectErr("ack != snd.nxt")
|
|
)
|
|
|
|
func newRejectErr(err string) *RejectError { return &RejectError{err: "reject in/out seg: " + err} }
|
|
|
|
// RejectError represents an error that arises during admission of a segment into the
|
|
// Transmission Control Block logic in which the packet cannot be processed by the TCB.
|
|
type RejectError struct {
|
|
err string
|
|
}
|
|
|
|
func (e *RejectError) Error() string { return e.err }
|
|
|
|
// Segment represents an incoming/outgoing TCP segment in the sequence space.
|
|
type Segment struct {
|
|
SEQ Value // sequence number of first octet of segment. If SYN is set it is the initial sequence number (ISN) and the first data octet is ISN+1.
|
|
ACK Value // acknowledgment number. If ACK is set it is sequence number of first octet the sender of the segment is expecting to receive next.
|
|
DATALEN Size // The number of octets occupied by the data (payload) not counting SYN and FIN.
|
|
WND Size // segment window
|
|
Flags Flags // TCP flags.
|
|
}
|
|
|
|
// LEN returns the length of the segment in octets including SYN and FIN flags.
|
|
func (seg *Segment) LEN() Size {
|
|
add := Size(seg.Flags>>0) & 1 // Add FIN bit.
|
|
add += Size(seg.Flags>>1) & 1 // Add SYN bit.
|
|
return seg.DATALEN + add
|
|
}
|
|
|
|
// End returns the sequence number of the last octet of the segment.
|
|
func (seg *Segment) Last() Value {
|
|
seglen := seg.LEN()
|
|
if seglen == 0 {
|
|
return seg.SEQ
|
|
}
|
|
return Add(seg.SEQ, seglen) - 1
|
|
}
|
|
|
|
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:
|
|
//
|
|
// 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:
|
|
//
|
|
// SynSent --> <SEQ=300><ACK=91>[SYN,ACK] --> SynRcvd
|
|
func StringExchange(seg Segment, A, B State, invertDir bool) string {
|
|
b := make([]byte, 0, 64)
|
|
b = appendStringExchange(b, seg, A, B, invertDir)
|
|
return unsafe.String(unsafe.SliceData(b), len(b))
|
|
}
|
|
|
|
// appendStringExchange appends a RFC9293 styled visualization of exchange to buf.
|
|
// i.e:
|
|
//
|
|
// SynSent --> <SEQ=300><ACK=91>[SYN,ACK] --> SynRcvd
|
|
func appendStringExchange(buf []byte, seg Segment, A, B State, invertDir bool) []byte {
|
|
const emptySpaces = " "
|
|
const fill = len(emptySpaces) - 1
|
|
appendVal := func(buf []byte, name string, i Value) []byte {
|
|
buf = append(buf, '<')
|
|
buf = append(buf, name...)
|
|
buf = append(buf, '=')
|
|
buf = strconv.AppendInt(buf, int64(i), 10)
|
|
buf = append(buf, '>')
|
|
return buf
|
|
}
|
|
startLen := len(buf)
|
|
dirSep := []byte(" --> ")
|
|
if invertDir {
|
|
dirSep = []byte(" <-- ")
|
|
}
|
|
astr := A.String()
|
|
buf = append(buf, astr...)
|
|
if len(astr) < fill {
|
|
// Space padding.
|
|
buf = append(buf, emptySpaces[:fill-len(astr)]...)
|
|
}
|
|
buf = append(buf, dirSep...)
|
|
buf = appendVal(buf, "SEQ", seg.SEQ)
|
|
buf = appendVal(buf, "ACK", seg.ACK)
|
|
if seg.DATALEN > 0 {
|
|
buf = appendVal(buf, "DATA", Value(seg.DATALEN))
|
|
}
|
|
buf = append(buf, '[')
|
|
buf = seg.Flags.AppendFormat(buf)
|
|
buf = append(buf, ']')
|
|
if len(buf)-startLen < 48 {
|
|
// More space padding.
|
|
buf = append(buf, emptySpaces[:48-len(buf)]...)
|
|
}
|
|
buf = append(buf, dirSep...)
|
|
buf = append(buf, B.String()...)
|
|
return buf
|
|
}
|
|
|
|
// Flags is a TCP flags bit-masked implementation i.e: SYN, FIN, ACK.
|
|
type Flags uint16
|
|
|
|
const (
|
|
FlagFIN Flags = 1 << iota // FlagFIN - No more data from sender.
|
|
FlagSYN // FlagSYN - Synchronize sequence numbers.
|
|
FlagRST // FlagRST - Reset the connection.
|
|
FlagPSH // FlagPSH - Push function.
|
|
FlagACK // FlagACK - Acknowledgment field significant.
|
|
FlagURG // FlagURG - Urgent pointer field significant.
|
|
FlagECE // FlagECE - ECN-Echo has a nonce-sum in the SYN/ACK.
|
|
FlagCWR // FlagCWR - Congestion Window Reduced.
|
|
FlagNS // FlagNS - Nonce Sum flag (see RFC 3540).
|
|
)
|
|
|
|
const flagMask = 0x01ff
|
|
|
|
// The union of SYN|FIN|PSH and ACK flags is commonly found throughout the specification, so we define unexported shorthands.
|
|
const (
|
|
synack = FlagSYN | FlagACK
|
|
finack = FlagFIN | FlagACK
|
|
pshack = FlagPSH | FlagACK
|
|
)
|
|
|
|
// HasAll checks if mask bits are all set in the receiver flags.
|
|
func (flags Flags) HasAll(mask Flags) bool { return flags&mask == mask }
|
|
|
|
// HasAny checks if one or more mask bits are set in receiver flags.
|
|
func (flags Flags) HasAny(mask Flags) bool { return flags&mask != 0 }
|
|
|
|
// Mask returns the flags with non-flag bits unset.
|
|
func (flags Flags) Mask() Flags { return flags & flagMask }
|
|
|
|
// StringFlags returns human readable flag string. i.e:
|
|
//
|
|
// "[SYN,ACK]"
|
|
//
|
|
// Flags are printed in order from LSB (FIN) to MSB (NS).
|
|
// All flags are printed with length of 3, so a NS flag will
|
|
// end with a space i.e. [ACK,NS ]
|
|
func (flags Flags) String() string {
|
|
// Cover most common cases without heap allocating.
|
|
switch flags {
|
|
case 0:
|
|
return "[]"
|
|
case synack:
|
|
return "[SYN,ACK]"
|
|
case finack:
|
|
return "[FIN,ACK]"
|
|
case pshack:
|
|
return "[PSH,ACK]"
|
|
case FlagACK:
|
|
return "[ACK]"
|
|
case FlagSYN:
|
|
return "[SYN]"
|
|
case FlagFIN:
|
|
return "[FIN]"
|
|
case FlagRST:
|
|
return "[RST]"
|
|
}
|
|
if flags&flagMask != flags {
|
|
return strInvalidTCPFlags
|
|
}
|
|
buf := make([]byte, 0, 2+3*bits.OnesCount16(uint16(flags)))
|
|
buf = append(buf, '[')
|
|
buf = flags.AppendFormat(buf)
|
|
buf = append(buf, ']')
|
|
return string(buf)
|
|
}
|
|
|
|
const strInvalidTCPFlags = "<invalid TCP flags>"
|
|
|
|
// AppendFormat appends a human readable flag string to b returning the extended buffer.
|
|
func (flags Flags) AppendFormat(b []byte) []byte {
|
|
if flags == 0 {
|
|
return b
|
|
} else if flags&flagMask != flags {
|
|
return append(b, strInvalidTCPFlags...)
|
|
}
|
|
|
|
// String Flag const
|
|
const flaglen = 3
|
|
const strflags = "FINSYNRSTPSHACKURGECECWRNS "
|
|
var addcommas bool
|
|
for flags != 0 { // written by Github Copilot- looks OK.
|
|
i := bits.TrailingZeros16(uint16(flags))
|
|
if addcommas {
|
|
b = append(b, ',')
|
|
} else {
|
|
addcommas = true
|
|
}
|
|
b = append(b, strflags[i*flaglen:i*flaglen+flaglen]...)
|
|
flags &= ^(1 << i)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// State enumerates states a TCP connection progresses through during its lifetime as per RFC9293.
|
|
type State uint8
|
|
|
|
const (
|
|
// CLOSED - represents no connection state at all. Is not a valid state of the TCP state machine but rather a pseudo-state pre-initialization.
|
|
StateClosed State = iota // CLOSED
|
|
// LISTEN - represents waiting for a connection request from any remote TCP and port.
|
|
StateListen // LISTEN
|
|
// SYN-RECEIVED - represents waiting for a confirming connection request acknowledgment
|
|
// after having both received and sent a connection request.
|
|
StateSynRcvd // SYN-RECEIVED
|
|
// SYN-SENT - represents waiting for a matching connection request after having sent a connection request.
|
|
StateSynSent // SYN-SENT
|
|
// ESTABLISHED - represents an open connection, data received can be delivered
|
|
// to the user. The normal state for the data transfer phase of the connection.
|
|
StateEstablished // ESTABLISHED
|
|
// FIN-WAIT-1 - represents waiting for a connection termination request
|
|
// from the remote TCP, or an acknowledgment of the connection
|
|
// termination request previously sent.
|
|
StateFinWait1 // FIN-WAIT-1
|
|
// FIN-WAIT-2 - represents waiting for a connection termination request
|
|
// from the remote TCP.
|
|
StateFinWait2 // FIN-WAIT-2
|
|
// CLOSING - represents waiting for a connection termination request
|
|
// acknowledgment from the remote TCP.
|
|
StateClosing // CLOSING
|
|
// TIME-WAIT - represents waiting for enough time to pass to be sure the remote
|
|
// TCP received the acknowledgment of its connection termination request.
|
|
StateTimeWait // TIME-WAIT
|
|
// CLOSE-WAIT - represents waiting for a connection termination request
|
|
// from the local user.
|
|
StateCloseWait // CLOSE-WAIT
|
|
// LAST-ACK - represents waiting for an acknowledgment of the
|
|
// connection termination request previously sent to the remote TCP
|
|
// (which includes an acknowledgment of its connection termination request).
|
|
StateLastAck // LAST-ACK
|
|
)
|
|
|
|
// IsPreestablished returns true if the connection is in a state preceding the established state.
|
|
// Returns false for Closed pseudo state.
|
|
func (s State) IsPreestablished() bool {
|
|
return s == StateSynRcvd || s == StateSynSent || s == StateListen
|
|
}
|
|
|
|
// IsClosing returns true if the connection is in a closing state but not yet terminated (relieved of remote connection state).
|
|
// Returns false for Closed pseudo state.
|
|
func (s State) IsClosing() bool {
|
|
return s == StateFinWait1 || s == StateFinWait2 || s == StateClosing || s == StateLastAck || s == StateCloseWait
|
|
}
|
|
|
|
// IsClosed returns true if the connection closed and can possibly relieved of
|
|
// all state related to the remote connection. It returns true if Closed or in TimeWait.
|
|
func (s State) IsClosed() bool {
|
|
return s == StateClosed || s == StateTimeWait
|
|
}
|
|
|
|
// IsSynchronized returns true if the connection has gone through the Established state.
|
|
func (s State) IsSynchronized() bool {
|
|
return s >= StateEstablished && !s.IsClosed()
|
|
}
|
|
|
|
// TxDataOpen returns true if the state allows for outgoing data segments to be sent.
|
|
// Combine with [State.IsPreestablished] to know whether there is no more data to be sent over the network.
|
|
func (s State) TxDataOpen() bool {
|
|
// In CloseWait state the remote endpoint has closed
|
|
// our receive hald of the connection but we can still transmit indefinitely.
|
|
return s == StateEstablished || s == StateCloseWait
|
|
}
|
|
|
|
// RxDataOpen returns true if the state allows the receiving of incoming data segments.
|
|
// Combine with [State.IsPreestablished] to know whether there is no more data to be received over the network.
|
|
func (s State) RxDataOpen() bool {
|
|
return s == StateEstablished || s == StateFinWait1 || s == StateFinWait2
|
|
}
|
|
|
|
// IsDataOpen returns true if the connection allows sending and receiving of data.
|
|
func (s State) isOpen() bool {
|
|
return !s.IsClosed()
|
|
}
|
|
|
|
// hasIRS checks if the ControlBlock has received a valid initial sequence number (IRS).
|
|
func (s State) hasIRS() bool {
|
|
return s.isOpen() && s != StateSynSent && s != StateListen
|
|
}
|
|
|
|
type OptionKind uint8
|
|
|
|
const (
|
|
OptEnd OptionKind = iota // end of option list
|
|
OptNop // no-operation
|
|
OptMaxSegmentSize // maximum segment size
|
|
OptWindowScale // window scale
|
|
OptSACKPermitted // SACK permitted
|
|
OptSACK // SACK
|
|
OptEcho // echo(obsolete)
|
|
optEchoReply // echo reply(obsolete)
|
|
OptTimestamps // timestamps
|
|
optPOCP // partial order connection permitted(obsolete)
|
|
optPOSP // partial order service profile(obsolete)
|
|
optCC // CC(obsolete)
|
|
optCCnew // CC.new(obsolete)
|
|
optCCecho // CC.echo(obsolete)
|
|
optACR // alternate checksum request(obsolete)
|
|
optACD // alternate checksum data(obsolete)
|
|
optSkeeter // skeeter
|
|
optBubba // bubba
|
|
OptTrailerChecksum // trailer checksum
|
|
optMD5Signature // MD5 signature(obsolete)
|
|
OptSCPSCapabilities // SCPS capabilities
|
|
OptSNA // selective negative acks
|
|
OptRecordBoundaries // record boundaries
|
|
OptCorruptionExperienced // corruption experienced
|
|
OptSNAP // SNAP
|
|
OptUnassigned // unassigned
|
|
OptCompressionFilter // compression filter
|
|
OptQuickStartResponse // quick-start response
|
|
OptUserTimeout // user timeout or unauthorized use
|
|
OptAuthetication // Authentication TCP-AO
|
|
OptMultipath // multipath TCP
|
|
)
|
|
|
|
const (
|
|
OptFastOpenCookie OptionKind = 34 // fast open cookie
|
|
OptEncryptionNegotiation OptionKind = 69 // encryption negotiation
|
|
OptAccurateECN0 OptionKind = 172 // accurate ECN order 0
|
|
OptAccurateECN1 OptionKind = 174 // accurate ECN order 1
|
|
)
|
|
|
|
// IsObsolete returns true if option considered obsolete by newer TCP specifications.
|
|
func (kind OptionKind) IsObsolete() bool {
|
|
if kind.IsDefined() {
|
|
return strings.HasSuffix(kind.String(), "(obsolete)")
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsDefined returns true if the option is a known unreserved option kind.
|
|
func (kind OptionKind) IsDefined() bool {
|
|
return kind <= 30 || kind == 34 || kind == 69 || kind == 172 || kind == 174
|
|
}
|
|
|
|
type OptionCodec struct {
|
|
Flags OptionFlags
|
|
}
|
|
|
|
type OptionFlags uint8
|
|
|
|
const (
|
|
OptFlagSkipSizeValidation OptionFlags = 1 << iota
|
|
OptFlagSkipObsolete
|
|
)
|
|
|
|
func (flags OptionFlags) HasAny(ofTheseFlags OptionFlags) bool {
|
|
return flags&ofTheseFlags != 0
|
|
}
|
|
|
|
func (op OptionCodec) PutOption16(dst []byte, kind OptionKind, v uint16) (int, error) {
|
|
return op.PutOption(dst, kind, byte(v>>8), byte(v))
|
|
}
|
|
|
|
func (op OptionCodec) PutOption32(dst []byte, kind OptionKind, v uint32) (int, error) {
|
|
return op.PutOption(dst, kind, byte(v>>24), byte(v>>16), byte(v>>7), byte(v))
|
|
}
|
|
|
|
func (op OptionCodec) PutOption(dst []byte, kind OptionKind, data ...byte) (int, error) {
|
|
putSize := 2 + len(data)
|
|
if len(dst) < putSize {
|
|
return -1, errBufferTooSmall
|
|
} else if putSize > 255 {
|
|
return -1, errors.New("option data too large")
|
|
} else if kind == OptNop || kind == OptEnd {
|
|
return -1, errors.New("cant put Nop or End option type")
|
|
}
|
|
dst[0] = byte(kind)
|
|
dst[1] = byte(putSize)
|
|
copy(dst[2:], data)
|
|
return putSize, nil
|
|
}
|
|
|
|
func (op OptionCodec) ForEachOption(opts []byte, fn func(OptionKind, []byte) error) error {
|
|
off := 0
|
|
skipSizeValidation := op.Flags.HasAny(OptFlagSkipSizeValidation)
|
|
skipObsolete := op.Flags.HasAny(OptFlagSkipObsolete)
|
|
for off < len(opts) && opts[off] != 0 {
|
|
kind := OptionKind(opts[off])
|
|
off++
|
|
if kind == OptNop {
|
|
continue
|
|
}
|
|
if len(opts[off:]) < 2 {
|
|
return errors.New("short TCP options")
|
|
}
|
|
size := int(opts[off])
|
|
off++
|
|
if len(opts[off:]) < size {
|
|
return fmt.Errorf("option %q length %d exceeds buffer size %d", kind.String(), size, len(opts[off:]))
|
|
}
|
|
|
|
if !skipSizeValidation {
|
|
expectSize := -1
|
|
switch kind {
|
|
case OptTimestamps:
|
|
expectSize = 10
|
|
case OptMaxSegmentSize, OptUserTimeout:
|
|
expectSize = 4
|
|
case OptWindowScale:
|
|
expectSize = 3
|
|
case OptSACKPermitted:
|
|
expectSize = 2
|
|
}
|
|
if expectSize != -1 && size != expectSize {
|
|
return fmt.Errorf("bad TCP option %q size want %d got %d", kind.String(), expectSize, opts[off])
|
|
}
|
|
}
|
|
if skipObsolete && kind.IsObsolete() {
|
|
err := fn(kind, opts[off:off+size])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
off += size
|
|
}
|
|
return nil
|
|
}
|