| // Copyright 2016 The Netstack Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package tcpconntrack implements a TCP connection tracking object. It allows |
| // users with access to a segment stream to figure out when a connection is |
| // established, reset, and closed (and in the last case, who closed first). |
| package tcpconntrack |
| |
| import ( |
| "github.com/google/netstack/tcpip/header" |
| "github.com/google/netstack/tcpip/seqnum" |
| ) |
| |
| // Result is returned when the state of a TCB is updated in response to an |
| // inbound or outbound segment. |
| type Result int |
| |
| const ( |
| // ResultDrop indicates that the segment should be dropped. |
| ResultDrop Result = iota |
| |
| // ResultConnecting indicates that the connection remains in a |
| // connecting state. |
| ResultConnecting |
| |
| // ResultAlive indicates that the connection remains alive (connected). |
| ResultAlive |
| |
| // ResultReset indicates that the connection was reset. |
| ResultReset |
| |
| // ResultClosedByPeer indicates that the connection was gracefully |
| // closed, and the inbound stream was closed first. |
| ResultClosedByPeer |
| |
| // ResultClosedBySelf indicates that the connection was gracefully |
| // closed, and the outbound stream was closed first. |
| ResultClosedBySelf |
| ) |
| |
| // TCB is a TCP Control Block. It holds state necessary to keep track of a TCP |
| // connection and inform the caller when the connection has been closed. |
| type TCB struct { |
| inbound stream |
| outbound stream |
| |
| // State handlers. |
| handlerInbound func(*TCB, header.TCP) Result |
| handlerOutbound func(*TCB, header.TCP) Result |
| |
| // firstFin holds a pointer to the first stream to send a FIN. |
| firstFin *stream |
| } |
| |
| // Init initalizes the state of the TCB according to the initial SYN. |
| func (t *TCB) Init(initialSyn header.TCP) { |
| t.handlerInbound = synSentStateInbound |
| t.handlerOutbound = synSentStateOutbound |
| |
| iss := seqnum.Value(initialSyn.SequenceNumber()) |
| t.outbound.una = iss |
| t.outbound.nxt = iss.Add(logicalLen(initialSyn)) |
| t.outbound.end = t.outbound.nxt |
| |
| // Even though "end" is a sequence number, we don't know the initial |
| // receive sequence number yet, so we store the window size until we get |
| // a SYN from the peer. |
| t.inbound.una = 0 |
| t.inbound.nxt = 0 |
| t.inbound.end = seqnum.Value(initialSyn.WindowSize()) |
| } |
| |
| // UpdateStateInbound updates the state of the TCB based on the supplied inbound |
| // segment. |
| func (t *TCB) UpdateStateInbound(tcp header.TCP) Result { |
| return t.handlerInbound(t, tcp) |
| } |
| |
| // UpdateStateOutbound updates the state of the TCB based on the supplied |
| // outbound segment. |
| func (t *TCB) UpdateStateOutbound(tcp header.TCP) Result { |
| return t.handlerOutbound(t, tcp) |
| } |
| |
| // adapResult modifies the supplied "Result" according to the state of the TCB; |
| // if r is anything other than "Alive", or if one of the streams isn't closed |
| // yet, it is returned unmodified. Otherwise it's converted to either |
| // ClosedBySelf or ClosedByPeer depending on which stream was closed first. |
| func (t *TCB) adaptResult(r Result) Result { |
| // Check the unmodified case. |
| if r != ResultAlive || !t.inbound.closed() || !t.outbound.closed() { |
| return r |
| } |
| |
| // Find out which was closed first. |
| if t.firstFin == &t.outbound { |
| return ResultClosedBySelf |
| } |
| |
| return ResultClosedByPeer |
| } |
| |
| // synSentStateInbound is the state handler for inbound segments when the |
| // connection is in SYN-SENT state. |
| func synSentStateInbound(t *TCB, tcp header.TCP) Result { |
| flags := tcp.Flags() |
| ackPresent := flags&header.TCPFlagAck != 0 |
| ack := seqnum.Value(tcp.AckNumber()) |
| |
| // Ignore segment if ack is present but not acceptable. |
| if ackPresent && !(ack-1).InRange(t.outbound.una, t.outbound.nxt) { |
| return ResultConnecting |
| } |
| |
| // If reset is specified, we will let the packet through no matter what |
| // but we will also destroy the connection if the ACK is present (and |
| // implicitly acceptable). |
| if flags&header.TCPFlagRst != 0 { |
| if ackPresent { |
| return ResultReset |
| } |
| return ResultConnecting |
| } |
| |
| // Ignore segment if SYN is not set. |
| if flags&header.TCPFlagSyn == 0 { |
| return ResultConnecting |
| } |
| |
| // Update state informed by this SYN. |
| irs := seqnum.Value(tcp.SequenceNumber()) |
| t.inbound.una = irs |
| t.inbound.nxt = irs.Add(logicalLen(tcp)) |
| t.inbound.end += irs |
| |
| t.outbound.end = t.outbound.una.Add(seqnum.Size(tcp.WindowSize())) |
| |
| // If the ACK was set (it is acceptable), update our unacknowledgement |
| // tracking. |
| if ackPresent { |
| // Advance the "una" and "end" indices of the outbound stream. |
| if t.outbound.una.LessThan(ack) { |
| t.outbound.una = ack |
| } |
| |
| if end := ack.Add(seqnum.Size(tcp.WindowSize())); t.outbound.end.LessThan(end) { |
| t.outbound.end = end |
| } |
| } |
| |
| // Update handlers so that new calls will be handled by new state. |
| t.handlerInbound = allOtherInbound |
| t.handlerOutbound = allOtherOutbound |
| |
| return ResultAlive |
| } |
| |
| // synSentStateOutbound is the state handler for outbound segments when the |
| // connection is in SYN-SENT state. |
| func synSentStateOutbound(t *TCB, tcp header.TCP) Result { |
| // Drop outbound segments that aren't retransmits of the original one. |
| if tcp.Flags() != header.TCPFlagSyn || |
| tcp.SequenceNumber() != uint32(t.outbound.una) { |
| return ResultDrop |
| } |
| |
| // Update the receive window. We only remember the largest value seen. |
| if wnd := seqnum.Value(tcp.WindowSize()); wnd > t.inbound.end { |
| t.inbound.end = wnd |
| } |
| |
| return ResultConnecting |
| } |
| |
| // update updates the state of inbound and outbound streams, given the supplied |
| // inbound segment. For outbound segments, this same function can be called with |
| // swapped inbound/outbound streams. |
| func update(tcp header.TCP, inbound, outbound *stream, firstFin **stream) Result { |
| // Ignore segments out of the window. |
| s := seqnum.Value(tcp.SequenceNumber()) |
| if !inbound.acceptable(s, dataLen(tcp)) { |
| return ResultAlive |
| } |
| |
| flags := tcp.Flags() |
| if flags&header.TCPFlagRst != 0 { |
| return ResultReset |
| } |
| |
| // Ignore segments that don't have the ACK flag, and those with the SYN |
| // flag. |
| if flags&header.TCPFlagAck == 0 || flags&header.TCPFlagSyn != 0 { |
| return ResultAlive |
| } |
| |
| // Ignore segments that acknowledge not yet sent data. |
| ack := seqnum.Value(tcp.AckNumber()) |
| if outbound.nxt.LessThan(ack) { |
| return ResultAlive |
| } |
| |
| // Advance the "una" and "end" indices of the outbound stream. |
| if outbound.una.LessThan(ack) { |
| outbound.una = ack |
| } |
| |
| if end := ack.Add(seqnum.Size(tcp.WindowSize())); outbound.end.LessThan(end) { |
| outbound.end = end |
| } |
| |
| // Advance the "nxt" index of the inbound stream. |
| end := s.Add(logicalLen(tcp)) |
| if inbound.nxt.LessThan(end) { |
| inbound.nxt = end |
| } |
| |
| // Note the index of the FIN segment. And stash away a pointer to the |
| // first stream to see a FIN. |
| if flags&header.TCPFlagFin != 0 && !inbound.finSeen { |
| inbound.finSeen = true |
| inbound.fin = end - 1 |
| |
| if *firstFin == nil { |
| *firstFin = inbound |
| } |
| } |
| |
| return ResultAlive |
| } |
| |
| // allOtherInbound is the state handler for inbound segments in all states |
| // except SYN-SENT. |
| func allOtherInbound(t *TCB, tcp header.TCP) Result { |
| return t.adaptResult(update(tcp, &t.inbound, &t.outbound, &t.firstFin)) |
| } |
| |
| // allOtherOutbound is the state handler for outbound segments in all states |
| // except SYN-SENT. |
| func allOtherOutbound(t *TCB, tcp header.TCP) Result { |
| return t.adaptResult(update(tcp, &t.outbound, &t.inbound, &t.firstFin)) |
| } |
| |
| // streams holds the state of a TCP unidirectional stream. |
| type stream struct { |
| // The interval [una, end) is the allowed interval as defined by the |
| // receiver, i.e., anything less than una has already been acknowledged |
| // and anything greater than or equal to end is beyond the receiver |
| // window. The interval [una, nxt) is the acknowledgable range, whose |
| // right edge indicates the sequence number of the next byte to be sent |
| // by the sender, i.e., anything greater than or equal to nxt hasn't |
| // been sent yet. |
| una seqnum.Value |
| nxt seqnum.Value |
| end seqnum.Value |
| |
| // finSeen indicates if a FIN has already been sent on this stream. |
| finSeen bool |
| |
| // fin is the sequence number of the FIN. It is only valid after finSeen |
| // is set to true. |
| fin seqnum.Value |
| } |
| |
| // acceptable determines if the segment with the given sequence number and data |
| // length is acceptable, i.e., if it's within the [una, end) window or, in case |
| // the window is zero, if it's a packet with no payload and sequence number |
| // equal to una. |
| func (s *stream) acceptable(segSeq seqnum.Value, segLen seqnum.Size) bool { |
| wnd := s.una.Size(s.end) |
| if wnd == 0 { |
| return segLen == 0 && segSeq == s.una |
| } |
| |
| // Make sure [segSeq, seqSeq+segLen) is non-empty. |
| if segLen == 0 { |
| segLen = 1 |
| } |
| |
| return seqnum.Overlap(s.una, wnd, segSeq, segLen) |
| } |
| |
| // closed determines if the stream has already been closed. This happens when |
| // a FIN has been set by the sender and acknowledged by the receiver. |
| func (s *stream) closed() bool { |
| return s.finSeen && s.fin.LessThan(s.una) |
| } |
| |
| // dataLen returns the length of the TCP segment payload. |
| func dataLen(tcp header.TCP) seqnum.Size { |
| return seqnum.Size(len(tcp) - int(tcp.DataOffset())) |
| } |
| |
| // logicalLen calculates the logical length of the TCP segment. |
| func logicalLen(tcp header.TCP) seqnum.Size { |
| l := dataLen(tcp) |
| flags := tcp.Flags() |
| if flags&header.TCPFlagSyn != 0 { |
| l++ |
| } |
| if flags&header.TCPFlagFin != 0 { |
| l++ |
| } |
| return l |
| } |