1455 lines
61 KiB
C#
1455 lines
61 KiB
C#
// ----------------------------------------------------------------------------
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// #define DEBUG_TCP
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
|
|
#if !SINGULARITY
|
|
using System.Net;
|
|
#endif
|
|
|
|
using Microsoft.Contracts;
|
|
|
|
using NetStack.Common;
|
|
using TcpError = NetStack.Contracts.TcpError;
|
|
|
|
namespace NetStack.Runtime
|
|
{
|
|
using Protocols;
|
|
|
|
public enum TcpStateEnum
|
|
{
|
|
Undefined = 0,
|
|
Closed = 1,
|
|
Listen = 2,
|
|
SynRecvd = 3,
|
|
SynSent = 4,
|
|
Established = 5,
|
|
CloseWait = 6,
|
|
LastAck = 7,
|
|
FinWait1 = 8,
|
|
FinWait2 = 9,
|
|
Closing = 10,
|
|
}
|
|
|
|
internal class TcpState
|
|
{
|
|
/// <summary>
|
|
/// Names corresponding to Enums, above (reflection missing).
|
|
/// </summary>
|
|
internal static readonly string[] TcpStateNames = new string[] {
|
|
"Undefined",
|
|
"Closed ",
|
|
"Listen ",
|
|
"SynRecvd ",
|
|
"SynSent ",
|
|
"Establish",
|
|
"CloseWait",
|
|
"LastAck ",
|
|
"FinWait1 ",
|
|
"FinWait2 ",
|
|
"Closing ",
|
|
};
|
|
|
|
private static TcpState! instance = new TcpState();
|
|
|
|
internal static TcpState! InstanceOfUndefined()
|
|
{
|
|
return TcpState.instance;
|
|
}
|
|
|
|
// ctor
|
|
internal TcpState()
|
|
{
|
|
}
|
|
|
|
// make a state transition
|
|
protected void ChangeState(TcpSession! tcpSession, TcpState newState)
|
|
{
|
|
tcpSession.ChangeState((!)newState);
|
|
}
|
|
|
|
// enter the new state
|
|
virtual internal void OnStateEnter(TcpSession! tcpSession)
|
|
{
|
|
}
|
|
|
|
// a new packet(=event) received, should handle it
|
|
// according to the current state.
|
|
virtual internal NetStatus OnPacketReceive(TcpSession! tcpSession,
|
|
NetPacket! packet,
|
|
object context)
|
|
{
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
|
|
// leave the current state to the new one
|
|
virtual internal void OnStateLeave(TcpSession! tcpSession, TcpState newState)
|
|
{
|
|
}
|
|
|
|
// Handle timeout events.
|
|
// args is a session argument which is session specific
|
|
virtual internal NetStatus OnTimeout(TcpSession! tcpSession, Dispatcher.CallbackArgs args)
|
|
{
|
|
TcpSessionEventsSource.EventLog.LogTimeout(
|
|
tcpSession.Uid,
|
|
tcpSession.currentState.StateEnum,
|
|
TcpSession.TcpTimeoutType.Unknown);
|
|
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
|
|
// Get the state name for debugging
|
|
internal string GetStateName()
|
|
{
|
|
return TcpState.TcpStateNames[(int)this.StateEnum];
|
|
}
|
|
|
|
// get state enum for eventing (tracing)
|
|
virtual internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.Undefined;
|
|
}
|
|
|
|
// State's Enumeration as a Property
|
|
internal TcpStateEnum StateEnum
|
|
{
|
|
get { return this.GetStateEnum(); }
|
|
}
|
|
|
|
// State's Name as a Property
|
|
internal string StateName
|
|
{
|
|
get { return TcpState.TcpStateNames[(int)this.StateEnum]; }
|
|
}
|
|
|
|
[ Conditional("DEBUG_TCP") ]
|
|
internal static void DebugPrint(string format, params object [] args)
|
|
{
|
|
Core.Log(format, args);
|
|
}
|
|
|
|
[ Conditional("DEBUG_TCP") ]
|
|
internal static void DebugPrintLine(string format, params object [] args)
|
|
{
|
|
TcpState.DebugPrint(format, args);
|
|
}
|
|
}
|
|
|
|
///
|
|
// This the TCP Finite State Machine Implementation
|
|
// NOTICE: This is just a quick implementation...
|
|
// should handle FIN, RST, window management, slow start, double acks...
|
|
//
|
|
internal class TCPFSM
|
|
{
|
|
|
|
// Definition of the TCP states
|
|
internal static TcpState! CLOSED { get {return TCPStateClosed.Instance();}}
|
|
|
|
internal static TcpState! LISTEN { get {return TCPStateListen.Instance();}}
|
|
internal static TcpState! SYN_RECVD { get {return TCPStateSynRcv.Instance();}}
|
|
internal static TcpState! SYN_SENT { get {return TCPStateSynSent.Instance();}}
|
|
internal static TcpState! ESTABLISHED { get {return TCPStateEstab.Instance();}}
|
|
internal static TcpState! CLOSE_WAIT { get {return TCPCloseWait.Instance();}}
|
|
internal static TcpState! LAST_ACK { get {return TCPLastAck.Instance();}}
|
|
|
|
internal static TcpState! FIN_WAIT1 { get {return TCPFINWait1.Instance();}}
|
|
internal static TcpState! FIN_WAIT2 { get {return TCPFINWait2.Instance();}}
|
|
internal static TcpState! CLOSING { get {return TCPClosing.Instance();}}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Compare two TCP sequence numbers:
|
|
// - means A < B, 0 means A == B and + means A > B
|
|
[Pure]
|
|
[Inline]
|
|
private static int TcpSequenceNumberCompare(uint seqA, uint seqB)
|
|
{
|
|
// Exploit integer underflow to compare correctly even in the case
|
|
// of sequence number wraparound. This assumes the two numbers are
|
|
// always in the same half of the numberspace.
|
|
|
|
return unchecked((int)(unchecked(seqA - seqB)));
|
|
}
|
|
|
|
[Pure]
|
|
internal static bool TCPSeqEQ(uint seqA, uint seqB)
|
|
{
|
|
return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) == 0;
|
|
}
|
|
|
|
[Pure]
|
|
internal static bool TCPSeqNEQ(uint seqA, uint seqB)
|
|
{
|
|
return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) != 0;
|
|
}
|
|
|
|
[Pure]
|
|
internal static bool TCPSeqLEQ(uint seqA, uint seqB)
|
|
{
|
|
return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) <= 0;
|
|
}
|
|
|
|
[Pure]
|
|
internal static bool TCPSeqLess(uint seqA, uint seqB)
|
|
{
|
|
return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) < 0;
|
|
}
|
|
|
|
[Pure]
|
|
internal static bool TCPSeqGEQ(uint seqA, uint seqB)
|
|
{
|
|
return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) >= 0;
|
|
}
|
|
|
|
[Pure]
|
|
internal static bool TCPSeqGreater(uint seqA, uint seqB)
|
|
{
|
|
return TCPFSM.TcpSequenceNumberCompare(seqA, seqB) > 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
internal sealed class TCPStateClosed : TcpState
|
|
{
|
|
static TCPStateClosed! instance = new TCPStateClosed();
|
|
static TCPStateClosed() {}
|
|
|
|
internal static TCPStateClosed! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get state enum for eventing (tracing) and debugging.
|
|
override internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.Closed;
|
|
}
|
|
|
|
// enter the Closed state
|
|
override internal void OnStateEnter(TcpSession! tcpSession)
|
|
{
|
|
tcpSession.DestroyRetransmitTimer();
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! tcpSession, TcpState newState)
|
|
{
|
|
assert newState != null;
|
|
base.OnStateLeave(tcpSession, newState);
|
|
switch (newState.StateEnum) {
|
|
case TcpStateEnum.Listen:
|
|
break;
|
|
case TcpStateEnum.SynSent:
|
|
if (!TCPFSM.SendSYN(tcpSession, TcpFormat.TCP_MSS)) {
|
|
// This should never happen; our outbound
|
|
// packet queue should never be empty
|
|
// while we're setting up a session
|
|
Debug.Assert(false);
|
|
}
|
|
break;
|
|
case TcpStateEnum.SynRecvd:
|
|
break;
|
|
default:
|
|
Core.Panic("TCP: Ses{0,3} ({1}) state machine error!",
|
|
tcpSession.Uid, this.StateName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
override internal NetStatus OnTimeout(TcpSession! tcpSession, Dispatcher.CallbackArgs args)
|
|
{
|
|
base.OnTimeout(tcpSession, args);
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
|
|
NetPacket! packet,
|
|
object context)
|
|
{
|
|
// A Closed session cannot handle any packets.
|
|
// BUGBUG: The parent class should make this the default behavior. // TODO
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// a passive connection listen state
|
|
internal sealed class TCPStateListen : TcpState
|
|
{
|
|
static TCPStateListen! instance = new TCPStateListen();
|
|
static TCPStateListen() {}
|
|
|
|
internal static TCPStateListen! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get state enum for eventing (tracing) and debugging.
|
|
override internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.Listen;
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! tcpSession) {
|
|
DebugPrintLine("TCP: Ses{0,3} ({1}) Waiting for connections.",
|
|
tcpSession.Uid, this.StateName);
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
|
|
NetPacket! packet,
|
|
object context)
|
|
{
|
|
assert context != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
|
|
|
|
if (TcpFormat.IsReset(ref tcpHeader)) {
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR; // we ignore it
|
|
}
|
|
|
|
if (TcpFormat.IsAck(ref tcpHeader)) {
|
|
SendReset(tcpSession, false);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (TcpFormat.IsSync(ref tcpHeader)) {
|
|
// we received a syn!
|
|
// create a new TCP session and initialize it (only if
|
|
// we have room) TBC: should use listen param for bound)
|
|
// new session's new state (OnEnter) will send syn-ack
|
|
// (this is the result of the ChangeState call).
|
|
|
|
TcpSession client =
|
|
(TcpSession!)tcpSession.Protocol.CreateSession();
|
|
client.passiveSession = tcpSession; // this is our "owner"
|
|
client.SetLocalEndPoint(tcpSession.LocalAddress,
|
|
tcpSession.LocalPort);
|
|
client.InitializeServerSession(tcpHeader.seq,
|
|
tcpHeader.window);
|
|
|
|
// we have access to the IP header from the packet overlapped
|
|
// context (TCPModule put it there)
|
|
IPFormat.IPHeader ipHeader =
|
|
packet.OverlapContext as IPFormat.IPHeader;
|
|
assert ipHeader != null;
|
|
|
|
client.SetRemoteEndPoint(ipHeader.Source,
|
|
tcpHeader.sourcePort);
|
|
|
|
// Save ourselves work if we should reject the session right away
|
|
if (tcpSession.AcceptQueueIsFull()) {
|
|
TcpSegment reset = CreateResetSegment(client, true);
|
|
tcpSession.Protocol.OnProtocolSend(reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// New session starts at syn-rcv. It immediately (from
|
|
// OnEntry) sends a SYN-Ack and starts the connect timer.
|
|
client.ChangeState(SYN_RECVD);
|
|
|
|
// WE are left at the SAME state!
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
else {
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
internal sealed class TCPStateSynRcv : TcpState
|
|
{
|
|
static TCPStateSynRcv! instance = new TCPStateSynRcv();
|
|
static TCPStateSynRcv() {}
|
|
|
|
internal static TCPStateSynRcv! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get state enum for eventing (tracing)
|
|
override internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.SynRecvd;
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! tcpSession)
|
|
{
|
|
// send syn-ack
|
|
if (!SendSYNAck(tcpSession)) {
|
|
// This should never happen; our outbound packet queues
|
|
// should never be full while we're setting up a session.
|
|
Debug.Assert(false);
|
|
}
|
|
|
|
// Start timer to limit the time to establishing the new session
|
|
tcpSession.StartConnectTimer();
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
|
|
NetPacket! packet,
|
|
object context)
|
|
{
|
|
assert context != null;
|
|
// all TCP states get the tcpHeader context
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
|
|
uint segmentSize = (uint)packet.Available;
|
|
|
|
bool packetIsAcceptable =
|
|
IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize);
|
|
|
|
if (!packetIsAcceptable) {
|
|
if (!TcpFormat.IsReset(ref tcpHeader)) {
|
|
// send an ACK; don't care about whether this works
|
|
// or not since we're discarding the packet anyhow.
|
|
TCPFSM.SendAck(tcpSession);
|
|
}
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// we accept the segment
|
|
Debug.Assert(tcpHeader.seq == tcpSession.sessionTCB.RCV.NXT);
|
|
|
|
// if we get a reset
|
|
if (TcpFormat.IsReset(ref tcpHeader)) {
|
|
NetStatus.Code result;
|
|
switch (tcpSession.oldStateEnum) {
|
|
case TcpStateEnum.Listen:
|
|
// if we came from the listen state (passive open)
|
|
// the reset will return us to the Listen state
|
|
tcpSession.FlushRetransmissions();
|
|
ChangeState(tcpSession, LISTEN);
|
|
result = NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
break;
|
|
case TcpStateEnum.SynSent:
|
|
// we came from SYN_SENT (active open)
|
|
// the connection was refused, close it!
|
|
HandleTerminateSession(tcpSession, null, TcpError.Refused);
|
|
result = NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
break;
|
|
default:
|
|
result = NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
if (TcpFormat.IsSync(ref tcpHeader)) {
|
|
// reset
|
|
HandleTerminateSession(tcpSession,
|
|
CreateResetSegment(tcpSession, false),
|
|
TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (TcpFormat.IsAck(ref tcpHeader)) {
|
|
if (TCPSeqLEQ(tcpSession.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
|
|
TCPSeqGEQ(tcpSession.sessionTCB.SND.NXT, tcpHeader.ackSeq))
|
|
{
|
|
// enter established state
|
|
|
|
// we get an Ack for our syn-ack
|
|
// remove the SYN-ACK from the retransmit Q
|
|
TCPFSM.HandleTCPAck(tcpSession, ref tcpHeader);
|
|
AttemptToEnterESTABLISHED(tcpSession, ref tcpHeader);
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
else {
|
|
// reset
|
|
HandleTerminateSession(tcpSession,
|
|
CreateResetSegment(tcpSession, false),
|
|
TcpError.Reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
|
|
if (TcpFormat.IsFIN(ref tcpHeader)) {
|
|
DebugPrintLine("TCP: Ses{0,3} ({1)} TCPStateSynRcv: Received FIN.",
|
|
tcpSession.Uid, this.StateName);
|
|
|
|
// RCV.NXT should reflect the data that we processed out of
|
|
// this packet, but not the FIN. Advance over the FIN.
|
|
tcpSession.sessionTCB.RCV.NXT += 1;
|
|
|
|
// ack the FIN. Don't worry if this doesn't actually work;
|
|
// the remote side will think the ACK got lost and retransmit,
|
|
// which we will hopefully be able to successfully ACK later.
|
|
SendAck(tcpSession);
|
|
ChangeState(tcpSession, CLOSE_WAIT);
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
internal sealed class TCPStateSynSent : TcpState
|
|
{
|
|
static TCPStateSynSent! instance = new TCPStateSynSent();
|
|
static TCPStateSynSent() {}
|
|
|
|
internal static TCPStateSynSent! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get state enum for eventing (tracing) and debugging.
|
|
override internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.SynSent;
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! tcpSession)
|
|
{
|
|
DebugPrintLine("TCP: Ses{0,3} ({1}) Sending SYN.",
|
|
tcpSession.Uid, this.StateName);
|
|
}
|
|
|
|
// we sent a syn we wait for syn-ack
|
|
override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
|
|
NetPacket! packet,
|
|
object context)
|
|
{
|
|
assert context != null;
|
|
// all TCP states get the tcpHeader context
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
|
|
bool ackAcceptable=false;
|
|
|
|
if (TcpFormat.IsAck(ref tcpHeader)) {
|
|
if (TCPSeqLEQ(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.ISS) ||
|
|
TCPSeqGreater(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.NXT))
|
|
{
|
|
if (TcpFormat.IsReset(ref tcpHeader)) {
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
SendReset(tcpSession, false);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (TCPSeqLEQ(tcpSession.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
|
|
TCPSeqLEQ(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.NXT))
|
|
{
|
|
ackAcceptable=true;
|
|
}
|
|
}
|
|
|
|
if (TcpFormat.IsReset(ref tcpHeader)) {
|
|
if (ackAcceptable) {
|
|
// connection was reset, drop the segment
|
|
// and close the connection.
|
|
HandleTerminateSession(tcpSession, null, TcpError.Reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
else {
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
|
|
// check syn
|
|
// ack is is ok or there is no ack and no RST
|
|
if (TcpFormat.IsSync(ref tcpHeader)) {
|
|
if (ackAcceptable) {
|
|
DebugPrintLine("TCP: Ses{0,3} ({1}) SYNSENT, Received SYN-ACK",
|
|
tcpSession.Uid, this.StateName);
|
|
|
|
// use the ack parameters to complete the session's data
|
|
tcpSession.sessionTCB.RCV.NXT = tcpHeader.seq+1;
|
|
tcpSession.sessionTCB.RCV.IRS = tcpHeader.seq;
|
|
tcpSession.sessionTCB.SND.UNA = tcpHeader.ackSeq;
|
|
tcpSession.sessionTCB.SND.WND = tcpHeader.window;
|
|
tcpSession.sessionTCB.RCV.WND = TcpFormat.TCP_MSS;
|
|
|
|
// now remove the SYN from the retransmit Q
|
|
// (so we don't retransmit it again)
|
|
TCPFSM.HandleTCPAck(tcpSession, ref tcpHeader);
|
|
|
|
if (tcpSession.sessionTCB.SND.UNA > tcpSession.sessionTCB.SND.ISS) {
|
|
// our syn has been acked.
|
|
TCPFSM.SendAck(tcpSession);
|
|
|
|
// change the state to established!
|
|
AttemptToEnterESTABLISHED(tcpSession, ref tcpHeader);
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
}
|
|
else {
|
|
// received a new SYN (overlap connection)
|
|
// (see SYN_RECVD for more details)
|
|
|
|
// Create a new object to track the new session
|
|
TcpSession client =
|
|
(TcpSession!) (tcpSession.Protocol.CreateSession());
|
|
client.passiveSession = tcpSession; // this is our "owner"
|
|
client.SetLocalEndPoint(tcpSession.LocalAddress,
|
|
tcpSession.LocalPort);
|
|
client.InitializeServerSession(tcpHeader.seq,
|
|
tcpHeader.window);
|
|
|
|
// we have access to the IP header from the packet overlapped
|
|
// context (TCPModule put it there)
|
|
IPFormat.IPHeader ipHeader = packet.OverlapContext as IPFormat.IPHeader;
|
|
assert ipHeader!=null;
|
|
client.SetRemoteEndPoint(ipHeader.Source,
|
|
tcpHeader.sourcePort);
|
|
|
|
// Save ourselves work if we should reject the session right away
|
|
if (tcpSession.AcceptQueueIsFull()) {
|
|
TcpSegment reset = CreateResetSegment(client, true);
|
|
tcpSession.Protocol.OnProtocolSend(reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// New session starts at syn-rcv. It immediately (from
|
|
// OnEntry) sends a SYN-Ack and starts the connect timer.
|
|
client.ChangeState(SYN_RECVD);
|
|
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
}
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
} // TCPStateSynSent
|
|
|
|
//----------------------------------------------------------------------------
|
|
// This is the working mode state
|
|
internal sealed class TCPStateEstab : TcpState
|
|
{
|
|
static TCPStateEstab! instance = new TCPStateEstab();
|
|
static TCPStateEstab() {}
|
|
|
|
internal static TCPStateEstab! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get state enum for eventing (tracing) and debugging.
|
|
override internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.Established;
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! tcpSession)
|
|
{
|
|
// When becoming Established, stop the connect timer
|
|
// if there is one ticking.
|
|
tcpSession.DestroyConnectTimer();
|
|
|
|
// Wake up anyone waiting for the connection to complete
|
|
tcpSession.setupCompleteEvent.Set();
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
|
|
NetPacket! packet,
|
|
object context)
|
|
{
|
|
assert context != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
|
|
uint segmentSize = (uint)packet.Available; // TCPModule already shrinked it for us
|
|
|
|
NetStatus res = NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
|
|
bool accept=IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize);
|
|
|
|
if (!accept) {
|
|
if (!TcpFormat.IsReset(ref tcpHeader)) {
|
|
// send an ACK and return
|
|
TCPFSM.SendAck(tcpSession);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
if (TcpFormat.IsReset(ref tcpHeader)) {
|
|
// for simplicity, just close the connection.
|
|
HandleTerminateSession(tcpSession, null, TcpError.Reset);
|
|
return res;
|
|
}
|
|
|
|
// check syn
|
|
if (TcpFormat.IsSync(ref tcpHeader)) {
|
|
// send a reset
|
|
HandleTerminateSession(tcpSession, CreateResetSegment(tcpSession, false), TcpError.Reset);
|
|
return res;
|
|
}
|
|
|
|
res = HandleTCPData(ref tcpHeader, packet, tcpSession);
|
|
|
|
// our peer wants to end the relationship...
|
|
if (TcpFormat.IsFIN(ref tcpHeader)) {
|
|
// RCV.NXT should reflect any data in this packet, but not the FIN.
|
|
// Advance over the FIN.
|
|
tcpSession.sessionTCB.RCV.NXT += 1;
|
|
|
|
// ack the FIN
|
|
SendAck(tcpSession);
|
|
ChangeState(tcpSession, CLOSE_WAIT);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
// CLOSE_WAIT is entered when we receive (and ACK) a FIN from the
|
|
// remote side. There is no further data to receive, but we can
|
|
// continue sending if we like.
|
|
internal sealed class TCPCloseWait : TcpState
|
|
{
|
|
static TCPCloseWait! instance = new TCPCloseWait();
|
|
static TCPCloseWait() {}
|
|
|
|
internal static TCPCloseWait! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get state enum (debug)
|
|
override internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.CloseWait;
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! tcpSession)
|
|
{
|
|
base.OnStateEnter(tcpSession);
|
|
|
|
// Signal that there is nothing more to read on this
|
|
// connection
|
|
tcpSession.ValidForRead = false;
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
|
|
NetPacket! packet,
|
|
object context)
|
|
{
|
|
assert context != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
|
|
uint segmentSize = (uint)packet.Available;
|
|
|
|
if (!IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize)) {
|
|
if (!TcpFormat.IsReset(ref tcpHeader)) {
|
|
// send an ACK and return
|
|
TCPFSM.SendAck(tcpSession);
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (TcpFormat.IsReset(ref tcpHeader) ||
|
|
TcpFormat.IsSync(ref tcpHeader) ||
|
|
(!TcpFormat.IsAck(ref tcpHeader)))
|
|
{
|
|
// Abort
|
|
HandleTerminateSession(tcpSession, null, TcpError.Reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
TCPFSM.HandleTCPAck(tcpSession, ref tcpHeader);
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// LAST_ACK is entered when we close our side of the duplex
|
|
// connection and the remote site had sent us its FIN
|
|
// previously. We send out our own FIN and just hang around
|
|
// to make sure it was received properly before shutting down.
|
|
//
|
|
// CALLER SHOULD ARRANGE TO TRANSMIT THE FIN PACKET BEFORE
|
|
// ENTERING THIS STATE!
|
|
internal sealed class TCPLastAck : TcpState
|
|
{
|
|
static TCPLastAck! instance = new TCPLastAck();
|
|
static TCPLastAck() {}
|
|
|
|
internal static TCPLastAck! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get state enum for eventing (tracing) and debugging.
|
|
override internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.LastAck;
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! tcpSession)
|
|
{
|
|
base.OnStateEnter(tcpSession);
|
|
|
|
// Flag that writing is now disallowed on this session
|
|
tcpSession.ValidForWrite = false;
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! tcbSession,
|
|
NetPacket! packet,
|
|
object context)
|
|
{
|
|
assert context != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
|
|
uint segmentSize = (uint)packet.Available; // TCPModule already shrinked it for us
|
|
|
|
// RST is illegal at this point
|
|
if (TcpFormat.IsReset(ref tcpHeader)) {
|
|
// for simplicity, just close the connection.
|
|
HandleTerminateSession(tcbSession, null, TcpError.Reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// SYNC causes us to issue a RST and abort
|
|
if (TcpFormat.IsSync(ref tcpHeader)) {
|
|
// send a reset
|
|
HandleTerminateSession(tcbSession,
|
|
CreateResetSegment(tcbSession, false),
|
|
TcpError.Reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (!IsSegmentAcceptable(tcbSession, ref tcpHeader, segmentSize)) {
|
|
// Just drop it
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// Do ACK housekeeping
|
|
if (TCPSeqLess(tcbSession.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
|
|
TCPSeqLEQ(tcpHeader.ackSeq, tcbSession.sessionTCB.SND.NXT))
|
|
{
|
|
// This appears to be ACKing something we sent.
|
|
tcbSession.sessionTCB.SND.UNA = tcpHeader.ackSeq;
|
|
|
|
// remove the packet(s) from the retransmit queue
|
|
TCPFSM.HandleTCPAck(tcbSession, ref tcpHeader);
|
|
}
|
|
|
|
// Note the weirdness here; because we prepacketize
|
|
// data, we don't want to compare to SND.NXT
|
|
if (TCPSeqGEQ(tcbSession.sessionTCB.SND.UNA, tcbSession.sessionTCB.SND.NextSeq)) {
|
|
// The remote site has acknowledged everything we sent.
|
|
// We're done.
|
|
HandleTerminateSession(tcbSession, null, TcpError.Reset);
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// FIN_WAIT1 is entered when we close our side of the duplex
|
|
// connection. We don't send any more data, but we continue to
|
|
// receive data from the remote side.
|
|
//
|
|
// CALLER SHOULD ARRANGE TO SEND THE FIN PACKET BEFORE ENTERING
|
|
// THIS STATE!
|
|
internal sealed class TCPFINWait1 : TcpState
|
|
{
|
|
static TCPFINWait1! instance = new TCPFINWait1();
|
|
static TCPFINWait1() {}
|
|
|
|
internal static TCPFINWait1! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get state enum (debug)
|
|
override internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.FinWait1;
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! tcpSession)
|
|
{
|
|
base.OnStateEnter(tcpSession);
|
|
|
|
// Flag that writing is now disallowed on this session
|
|
tcpSession.ValidForWrite = false;
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! tcpSession, TcpState newState)
|
|
{
|
|
assert newState != null;
|
|
base.OnStateLeave(tcpSession, newState);
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! tcpSession, NetPacket! packet, object context)
|
|
{
|
|
assert context != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
|
|
uint segmentSize = (uint)packet.Available; // TCPModule already shrinked it for us
|
|
|
|
// RST is illegal at this point
|
|
if (TcpFormat.IsReset(ref tcpHeader)) {
|
|
// for simplicity, just close the connection.
|
|
HandleTerminateSession(tcpSession, null, TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// SYNC causes us to issue a RST and abort
|
|
if (TcpFormat.IsSync(ref tcpHeader)) {
|
|
// send a reset
|
|
HandleTerminateSession(tcpSession, CreateResetSegment(tcpSession, false), TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (!IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize)) {
|
|
if (!TcpFormat.IsReset(ref tcpHeader)) {
|
|
// send an ACK and return
|
|
TCPFSM.SendAck(tcpSession);
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// Chew on the payload...
|
|
NetStatus retval = HandleTCPData(ref tcpHeader, packet, tcpSession);
|
|
|
|
if (TCPSeqLEQ(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT) &&
|
|
(TcpFormat.IsFIN(ref tcpHeader)))
|
|
{
|
|
// This is the remote side's FIN, and we have
|
|
// received all data preceding it, too!
|
|
// advance RCV.NXT over the FIN sequence
|
|
tcpSession.sessionTCB.RCV.NXT += 1;
|
|
|
|
// Note the weirdness here; because we
|
|
// prepacketize data, we don't want to test
|
|
// against SND.NXT
|
|
if (TCPSeqLess(tcpSession.sessionTCB.SND.UNA,
|
|
tcpSession.sessionTCB.SND.NextSeq)) {
|
|
// ...but the remote side hasn't received
|
|
// everything we have said, yet.
|
|
|
|
// ack the FIN
|
|
SendAck(tcpSession);
|
|
ChangeState(tcpSession, CLOSING);
|
|
}
|
|
else {
|
|
// The remote side has heard everything we
|
|
// have to say. We're done.
|
|
// ACK the FIN and shut down
|
|
TcpSegment ackSeg = CreateAckSegment(tcpSession);
|
|
HandleTerminateSession(tcpSession, ackSeg, TcpError.Closed);
|
|
}
|
|
}
|
|
else {
|
|
// Note the weirdness here; because we
|
|
// prepacketize data, we do not want to test
|
|
// against SND.NXT, but rather SND.NextSeq.
|
|
if (TCPSeqGEQ(tcpSession.sessionTCB.SND.UNA,
|
|
tcpSession.sessionTCB.SND.NextSeq)) {
|
|
// The remote side has ACKed everything we have
|
|
// said, but they haven't FINed themselves.
|
|
ChangeState(tcpSession, FIN_WAIT2);
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// FIN_WAIT2 is entered when the other side has received our FIN, but has
|
|
// more data to send. We wait for them to signal FIN as well.
|
|
internal sealed class TCPFINWait2 : TcpState
|
|
{
|
|
static TCPFINWait2! instance = new TCPFINWait2();
|
|
static TCPFINWait2() {}
|
|
|
|
internal static TCPFINWait2! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get state enum (debug)
|
|
override internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.FinWait2;
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! tcpSession, TcpState newState)
|
|
{
|
|
base.OnStateLeave(tcpSession, newState);
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
|
|
NetPacket! packet,
|
|
object context)
|
|
{
|
|
assert context != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
|
|
uint segmentSize = (uint)packet.Available; // TCPModule already shrinked it for us
|
|
|
|
// RST is illegal at this point
|
|
if (TcpFormat.IsReset(ref tcpHeader)) {
|
|
// for simplicity, just close the connection.
|
|
HandleTerminateSession(tcpSession,
|
|
null,
|
|
TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// SYNC causes us to issue a RST and abort
|
|
if (TcpFormat.IsSync(ref tcpHeader)) {
|
|
// send a reset
|
|
HandleTerminateSession(tcpSession,
|
|
CreateResetSegment(tcpSession, false),
|
|
TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (!IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize)) {
|
|
if (!TcpFormat.IsReset(ref tcpHeader)) {
|
|
// send an ACK and return
|
|
TCPFSM.SendAck(tcpSession);
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// Chew on the payload...
|
|
NetStatus retval = HandleTCPData(ref tcpHeader, packet, tcpSession);
|
|
|
|
// Only check for FIN if we have previously received
|
|
// all data.
|
|
if (TCPSeqLEQ(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT) &&
|
|
(TcpFormat.IsFIN(ref tcpHeader)))
|
|
{
|
|
// RCV.NXT should reflect the data in this packet, but not
|
|
// the FIN. Advance over the FIN...
|
|
tcpSession.sessionTCB.RCV.NXT += 1;
|
|
|
|
// ack the FIN and shut down
|
|
TcpSegment ackSeg = CreateAckSegment(tcpSession);
|
|
HandleTerminateSession(tcpSession, ackSeg, TcpError.Closed);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// CLOSING is entered when both sides have sent a FIN, but we're not sure
|
|
// the other side has received all our data yet. We hang around processing
|
|
// ACKs until we're sure the other side has heard everything we have to say.
|
|
internal sealed class TCPClosing : TcpState
|
|
{
|
|
static TCPClosing! instance = new TCPClosing();
|
|
static TCPClosing() {}
|
|
|
|
internal static TCPClosing! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get state enum (debug)
|
|
override internal TcpStateEnum GetStateEnum()
|
|
{
|
|
return TcpStateEnum.Closing;
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! tcpSession)
|
|
{
|
|
base.OnStateEnter(tcpSession);
|
|
|
|
// Signal that there's nothing more to read on this session
|
|
tcpSession.ValidForRead = false;
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! tcpSession, TcpState newState)
|
|
{
|
|
assert newState != null;
|
|
base.OnStateLeave(tcpSession, newState);
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! tcpSession,
|
|
NetPacket! packet,
|
|
object context)
|
|
{
|
|
assert context != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)context;
|
|
uint segmentSize = (uint)packet.Available; // TCPModule already shrinked it for us
|
|
|
|
// RST is illegal at this point
|
|
if (TcpFormat.IsReset(ref tcpHeader)) {
|
|
// for simplicity, just close the connection.
|
|
HandleTerminateSession(tcpSession, null, TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// SYNC causes us to issue a RST and abort
|
|
if (TcpFormat.IsSync(ref tcpHeader)) {
|
|
// send a reset
|
|
HandleTerminateSession(tcpSession,
|
|
CreateResetSegment(tcpSession, false),
|
|
TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (!IsSegmentAcceptable(tcpSession, ref tcpHeader, segmentSize)) {
|
|
// Just drop it
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// Do ACK housekeeping
|
|
if (TCPSeqLess(tcpSession.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
|
|
TCPSeqLEQ(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.NXT))
|
|
{
|
|
// This appears to be ACKing something we sent.
|
|
tcpSession.sessionTCB.SND.UNA = tcpHeader.ackSeq;
|
|
|
|
// remove the packet(s) from the retransmit queue
|
|
TCPFSM.HandleTCPAck(tcpSession, ref tcpHeader);
|
|
}
|
|
|
|
// Note the weirdness here; because we
|
|
// prepacketize data, we don't want to compare against
|
|
// SND.NXT
|
|
if (TCPSeqGEQ(tcpSession.sessionTCB.SND.UNA, tcpSession.sessionTCB.SND.NextSeq)) {
|
|
// The remote side has now heard everything we said.
|
|
HandleTerminateSession(tcpSession, null, TcpError.Closed);
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Create an ACK for data we have received to date
|
|
internal static TcpSegment! CreateAckSegment(TcpSession! tcpSession)
|
|
{
|
|
byte[] ackBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];
|
|
|
|
TcpFormat.WriteTcpSegment(
|
|
ackBuffer, tcpSession.LocalPort, tcpSession.RemotePort,
|
|
tcpSession.sessionTCB.RCV.NXT, tcpSession.sessionTCB.SND.NextSeq,
|
|
TcpFormat.TCP_MSS, tcpSession.LocalAddress,
|
|
tcpSession.RemoteAddress, 0,
|
|
true, false, false, false, false);
|
|
|
|
return new TcpSegment(ackBuffer, tcpSession,
|
|
tcpSession.sessionTCB.SND.NextSeq, true);
|
|
}
|
|
|
|
// sends an ack
|
|
internal static bool SendAck(TcpSession! tcpSession)
|
|
{
|
|
TcpSegment ackSeg = CreateAckSegment(tcpSession);
|
|
|
|
// put it on this session outgoing queue
|
|
return tcpSession.PutPacket(tcpSession.outQueue, ackSeg, false);
|
|
}
|
|
|
|
// Sends a FIN
|
|
internal static bool SendFin(TcpSession! tcpSession, bool canBlock)
|
|
{
|
|
byte[] finBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];
|
|
|
|
TcpFormat.WriteTcpSegment(
|
|
finBuffer, tcpSession.LocalPort, tcpSession.RemotePort, tcpSession.sessionTCB.RCV.NXT,
|
|
tcpSession.sessionTCB.SND.NextSeq, TcpFormat.TCP_MSS, tcpSession.LocalAddress, tcpSession.RemoteAddress, 0,
|
|
true, false, false, true, false);
|
|
|
|
// ok, we have a ready segment.
|
|
TcpSegment syn = new TcpSegment(finBuffer, tcpSession, tcpSession.sessionTCB.SND.NextSeq, true);
|
|
|
|
bool retVal = tcpSession.PutPacket(tcpSession.outQueue, syn, canBlock);
|
|
|
|
// Only advance the segment counter if we successfully queued the
|
|
// outbound packet
|
|
if (retVal) {
|
|
tcpSession.sessionTCB.SND.NextSeq++;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
// sends a syn ack packet
|
|
internal static bool SendSYNAck(TcpSession! tcpSession)
|
|
{
|
|
byte[] synackBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];
|
|
|
|
TcpFormat.WriteTcpSegment(synackBuffer, tcpSession.LocalPort, tcpSession.RemotePort,
|
|
tcpSession.sessionTCB.RCV.NXT,
|
|
tcpSession.sessionTCB.SND.ISS,
|
|
(ushort)tcpSession.sessionTCB.SND.WND,
|
|
tcpSession.LocalAddress, tcpSession.RemoteAddress,
|
|
0, true, true, false, false, false);
|
|
|
|
// ok, we have a ready segment.
|
|
// (SYN is regular segment)
|
|
TcpSegment syn = new TcpSegment(synackBuffer, tcpSession, tcpSession.sessionTCB.SND.ISS, true);
|
|
|
|
// put it on this session's outgoing queue
|
|
return tcpSession.PutPacket(tcpSession.outQueue, syn, false);
|
|
}
|
|
|
|
// sends a syn packet, with MSS options
|
|
internal static bool SendSYN(TcpSession! tcpSession, ushort MSS)
|
|
{
|
|
byte[] synBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size+4];
|
|
|
|
// setup the session
|
|
tcpSession.sessionTCB.SND.ISS=(uint)DateTime.UtcNow.Ticks;
|
|
tcpSession.sessionTCB.SND.UNA=tcpSession.sessionTCB.SND.ISS;
|
|
tcpSession.sessionTCB.SND.NXT=tcpSession.sessionTCB.SND.ISS+1; // next packet sequence
|
|
tcpSession.sessionTCB.SND.NextSeq = tcpSession.sessionTCB.SND.NXT;
|
|
tcpSession.sessionTCB.SND.WND=TcpFormat.TCP_MSS;
|
|
|
|
// we must first write the data...
|
|
byte[] options=new byte[] {2, 4, ((byte)(MSS>>8)), (byte)MSS};
|
|
Array.Copy(options, 0, synBuffer, EthernetFormat.Size+IPFormat.Size+TcpFormat.Size, 4);
|
|
|
|
// now create the segment+checksum
|
|
TcpFormat.WriteTcpSegment(
|
|
synBuffer, tcpSession.LocalPort, tcpSession.RemotePort, 0,
|
|
tcpSession.sessionTCB.SND.ISS, MSS, tcpSession.LocalAddress, tcpSession.RemoteAddress, 4,
|
|
false, true, false, false, true);
|
|
|
|
// ok, we have a ready segment.
|
|
// (SYN is regular segment)
|
|
TcpSegment syn = new TcpSegment(synBuffer, tcpSession, tcpSession.sessionTCB.SND.ISS, false);
|
|
|
|
// put it on this session outgoing queue
|
|
return tcpSession.PutPacket(tcpSession.outQueue, syn, false);
|
|
}
|
|
|
|
// send a TCP reset
|
|
internal static bool SendReset(TcpSession! tcpSession, bool isAck)
|
|
{
|
|
// we won't retransmit it (it is like an Ack)
|
|
TcpSegment syn = CreateResetSegment(tcpSession, true);
|
|
|
|
// put it on this session outgoing queue
|
|
return tcpSession.PutPacket(tcpSession.outQueue, syn, false);
|
|
}
|
|
|
|
// send a TCP reset
|
|
internal static TcpSegment! CreateResetSegment(TcpSession! tcpSession, bool isAck)
|
|
{
|
|
byte[] rstBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];
|
|
|
|
// NOTE: use SND.NXT as the sequence number here instead of nextSeq,
|
|
// otherwise the sequence number may be far ahead (if there's lots of queued
|
|
// data) and the receiving host may ignore it as outside its window.
|
|
TcpFormat.WriteTcpSegment(
|
|
rstBuffer, tcpSession.LocalPort, tcpSession.RemotePort, tcpSession.sessionTCB.RCV.NXT,
|
|
tcpSession.sessionTCB.SND.NXT, TcpFormat.TCP_MSS, tcpSession.LocalAddress,
|
|
tcpSession.RemoteAddress, 0, isAck, false, true, false, false);
|
|
|
|
// we won't retransmit it
|
|
TcpSegment syn = new TcpSegment(rstBuffer, tcpSession, tcpSession.sessionTCB.SND.NXT, true);
|
|
return syn;
|
|
}
|
|
|
|
// Handle received TCP data
|
|
// Returns the appropriate NetStatus.Code
|
|
internal static NetStatus HandleTCPData(ref TcpFormat.TcpHeader tcpHeader,
|
|
NetPacket! packet, TcpSession! tcpSession)
|
|
{
|
|
uint segmentSize = (uint)packet.Available; // TCPModule already shrinked it for us
|
|
|
|
// TBC: We assume the segment start with
|
|
// the RCV.NXT !!! (otherwise we can buffer them)
|
|
if (TCPSeqGreater(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT)) {
|
|
// we missed one or few, send ack again
|
|
TCPFSM.SendAck(tcpSession);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (!TcpFormat.IsAck(ref tcpHeader)) {
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// First, deal with the window advertised in this packet.
|
|
tcpSession.sessionTCB.SND.WND = tcpHeader.window;
|
|
tcpSession.HandleWindowUpdate();
|
|
|
|
// ack is set and it is relevant (ack something we sent)
|
|
if (TCPSeqLess(tcpSession.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
|
|
TCPSeqLEQ(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.NXT))
|
|
{
|
|
tcpSession.sessionTCB.SND.UNA = tcpHeader.ackSeq;
|
|
|
|
// remove the packet(s) from the retransmit queue
|
|
TCPFSM.HandleTCPAck(tcpSession, ref tcpHeader);
|
|
}
|
|
else if (tcpSession.sessionTCB.SND.UNA > tcpHeader.ackSeq) {
|
|
// a duplicate ack
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
else if (TCPSeqGreater(tcpHeader.ackSeq, tcpSession.sessionTCB.SND.NXT)) {
|
|
// ack for future data..
|
|
TCPFSM.SendAck(tcpSession);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// check URG bit
|
|
if (TcpFormat.IsUrg(ref tcpHeader)) {
|
|
// TBC
|
|
}
|
|
|
|
// check PUSH bit
|
|
if (TcpFormat.IsPush(ref tcpHeader)) {
|
|
// TBC
|
|
}
|
|
|
|
// at last, process the segment data!!!
|
|
// at the end send an ACK (TBC: change to accumulate acks)
|
|
if (segmentSize > 0) {
|
|
// put the data in the session's inQ
|
|
if (tcpSession.PutPacket(tcpSession.inQueue, packet, false)) {
|
|
tcpSession.sessionTCB.RCV.NXT+=segmentSize; // we expect the next segment seq
|
|
tcpSession.sessionTCB.RCV.WND=TcpFormat.TCP_MSS; // TBC: change according to buffer
|
|
|
|
// send our ack if we ack data
|
|
SendAck(tcpSession);
|
|
|
|
// don't release the packet
|
|
return NetStatus.Code.PROTOCOL_PROCESSING;
|
|
}
|
|
else {
|
|
// packet was dropped...
|
|
// send our ack if we ack data
|
|
SendAck(tcpSession);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
else {
|
|
// Payload was empty...
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
|
|
// handle a TCP received ack
|
|
// the ack is already checked for validity (by the actual state)
|
|
internal static void HandleTCPAck(TcpSession! tcpSession,
|
|
ref TcpFormat.TcpHeader tcpHdr)
|
|
{
|
|
tcpSession.ACKThrough(tcpHdr.ackSeq);
|
|
}
|
|
|
|
// check if we can accept the segment
|
|
internal static bool IsSegmentAcceptable(TcpSession! tcpSession,
|
|
ref TcpFormat.TcpHeader tcpHeader,
|
|
uint segmentSize)
|
|
{
|
|
bool accept=false;
|
|
|
|
// first check sequence number
|
|
if ((segmentSize == 0) && (tcpSession.sessionTCB.RCV.WND == 0)) {
|
|
accept = (tcpHeader.seq == tcpSession.sessionTCB.RCV.NXT);
|
|
}
|
|
else if ((segmentSize == 0) && (tcpSession.sessionTCB.RCV.WND > 0)) {
|
|
accept=(TCPSeqGEQ(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT) &&
|
|
TCPSeqLess(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT+tcpSession.sessionTCB.RCV.WND));
|
|
}
|
|
else if ((segmentSize > 0) && (tcpSession.sessionTCB.RCV.WND > 0)) {
|
|
accept=(TCPSeqGEQ(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT) &&
|
|
TCPSeqLess(tcpHeader.seq, tcpSession.sessionTCB.RCV.NXT+tcpSession.sessionTCB.RCV.WND));
|
|
|
|
accept = accept || (TCPSeqLEQ(tcpSession.sessionTCB.RCV.NXT, tcpHeader.seq+segmentSize-1) &&
|
|
TCPSeqLess(tcpHeader.seq +segmentSize - 1,
|
|
tcpSession.sessionTCB.RCV.NXT + tcpSession.sessionTCB.RCV.WND));
|
|
}
|
|
|
|
return accept;
|
|
}
|
|
|
|
private static void AttemptToEnterESTABLISHED(TcpSession! tcpSession,
|
|
ref TcpFormat.TcpHeader tcpHeader)
|
|
{
|
|
TcpSession passiveOwner = tcpSession.passiveSession;
|
|
|
|
if (passiveOwner != null) {
|
|
bool success = passiveOwner.AddAcceptedSession(tcpSession);
|
|
|
|
if (!success) {
|
|
// Oops; while this connection was being established
|
|
// we ran out of room in the accept queue. Abort
|
|
// the connection!
|
|
HandleTerminateSession(tcpSession,
|
|
CreateResetSegment(tcpSession, false),
|
|
TcpError.ResourcesExhausted);
|
|
return;
|
|
}
|
|
}
|
|
|
|
tcpSession.ChangeState(ESTABLISHED);
|
|
}
|
|
|
|
// this method terminated the given session
|
|
// if nextSegment is not null than it is sent before final
|
|
// removal.
|
|
internal static void HandleTerminateSession(TcpSession! tcpSession,
|
|
TcpSegment nextSegment,
|
|
TcpError connectError)
|
|
{
|
|
tcpSession.StopPersistTimer();
|
|
|
|
// 1. first clear the retransmit queue
|
|
// 2. make the session not valid for users!
|
|
// 3. release any user waiting for read/write on the session
|
|
// 4. clear the out/in queues
|
|
// 5. remove the session from TCP if it is no longer needed
|
|
// (when it considered to be closed)
|
|
tcpSession.FlushRetransmissions();
|
|
|
|
// some user may wait on the q to write/read data
|
|
// we will release them by trigger the monitors.
|
|
// they will fail since Valid is false and they are users.
|
|
// only TCP can still use this session.
|
|
lock (tcpSession.outQueue.SyncRoot) {
|
|
// from now on, every user thread will
|
|
// fail to read/write data
|
|
tcpSession.ValidForRead = false;
|
|
tcpSession.ValidForWrite = false;
|
|
|
|
tcpSession.DrainQueue(tcpSession.outQueue);
|
|
if (nextSegment != null)
|
|
tcpSession.outQueue.Add(nextSegment);
|
|
|
|
// Don't forget these!
|
|
Monitor.PulseAll(tcpSession.outQueue.SyncRoot);
|
|
Core.Instance().SignalOutboundPackets();
|
|
}
|
|
|
|
lock (tcpSession.inQueue.SyncRoot) {
|
|
// Pulse anyone waiting to read data so they can notice
|
|
// they are unlikely to succeed, but don't clear the
|
|
// queue; if there is data on it, we may want to drain
|
|
// it, still.
|
|
Monitor.PulseAll(tcpSession.inQueue.SyncRoot);
|
|
}
|
|
|
|
if (nextSegment == null) {
|
|
IProtocol tcpProtocol = tcpSession.Protocol;
|
|
Core.Instance().DeregisterSession(tcpProtocol, tcpSession);
|
|
}
|
|
|
|
// change the state to close
|
|
tcpSession.ChangeState(TCPFSM.CLOSED);
|
|
|
|
// Signal anyone who was waiting for this session to begin or end
|
|
tcpSession.connectError = connectError;
|
|
tcpSession.setupCompleteEvent.Set();
|
|
|
|
// NOTE: If we are transmitting a last-gasp packet,
|
|
// don't signal the session as closed just yet; wait for the packet
|
|
// to actually get transmitted.
|
|
if (nextSegment == null) {
|
|
tcpSession.closedEvent.Set();
|
|
}
|
|
}
|
|
}
|
|
}
|