1498 lines
58 KiB
C#
1498 lines
58 KiB
C#
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// #define DEBUG_TCP
|
|
|
|
using NetStack.Common;
|
|
using Microsoft.Contracts;
|
|
using System;
|
|
using System.Threading;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using NetStack.Contracts;
|
|
using TcpError = NetStack.Contracts.TcpError;
|
|
|
|
|
|
#if !SINGULARITY
|
|
using System.Net;
|
|
#endif
|
|
|
|
using System.Net.IP;
|
|
using Drivers.Net;
|
|
|
|
namespace NetStack.Runtime
|
|
{
|
|
using Protocols;
|
|
|
|
internal class TcpState
|
|
{
|
|
// ctor
|
|
internal TcpState()
|
|
{
|
|
}
|
|
|
|
// make a state transition
|
|
protected void ChangeState(TcpSession! owner, TcpState newState)
|
|
{
|
|
owner.ChangeState((!)newState);
|
|
}
|
|
|
|
// enter the new state
|
|
virtual internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
}
|
|
|
|
// a new packet(=event) received, should handle it
|
|
// according to the current state.
|
|
virtual internal NetStatus OnPacketReceive(TcpSession! owner,
|
|
NetPacket! pkt,
|
|
object ctx)
|
|
{
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
|
|
// leave the state to the new one
|
|
virtual internal void OnStateLeave(TcpSession! owner, TcpState newState)
|
|
{
|
|
}
|
|
|
|
// handle timeout events
|
|
// args is a session argument which is session specific
|
|
virtual internal NetStatus OnTimeout(Dispatcher.CallbackArgs args)
|
|
{
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
|
|
// get the state name (debug)
|
|
virtual internal string GetStateName()
|
|
{
|
|
return "UNDEFINED";
|
|
}
|
|
|
|
[ Conditional("DEBUG_TCP") ]
|
|
internal static void DebugPrint(string format, params object [] args)
|
|
{
|
|
Core.Log("TCP: ");
|
|
Core.Log(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: -1 means A < B, 0 means A == B and
|
|
// 1 means A > B
|
|
[Pure]
|
|
internal static int TCPSeqCmp(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.
|
|
uint diff = unchecked(seqA - seqB);
|
|
int signedDiff = unchecked((int)diff);
|
|
|
|
if (signedDiff < 0) {
|
|
return -1; // A < B
|
|
} else if (signedDiff > 0) {
|
|
return 1; // A > B
|
|
} else {
|
|
return 0; // A == B
|
|
}
|
|
}
|
|
|
|
[Pure]
|
|
internal static bool TCPSeqLEQ(uint seqA, uint seqB)
|
|
{
|
|
int cmp = TCPSeqCmp(seqA, seqB);
|
|
return (cmp == 0) || (cmp == -1);
|
|
}
|
|
|
|
[Pure]
|
|
internal static bool TCPSeqLess(uint seqA, uint seqB)
|
|
{
|
|
int cmp = TCPSeqCmp(seqA, seqB);
|
|
return (cmp == -1);
|
|
}
|
|
|
|
[Pure]
|
|
internal static bool TCPSeqGEQ(uint seqA, uint seqB)
|
|
{
|
|
int cmp = TCPSeqCmp(seqA, seqB);
|
|
return (cmp == 0) || (cmp == 1);
|
|
}
|
|
|
|
[Pure]
|
|
internal static bool TCPSeqGreater(uint seqA, uint seqB)
|
|
{
|
|
int cmp = TCPSeqCmp(seqA, seqB);
|
|
return (cmp == 1);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
internal sealed class TCPStateClosed : TcpState
|
|
{
|
|
static TCPStateClosed! instance = new TCPStateClosed();
|
|
static TCPStateClosed() {}
|
|
|
|
internal static TCPStateClosed! Instance()
|
|
{
|
|
return instance;
|
|
}
|
|
|
|
// get the state name (debug)
|
|
override internal string GetStateName()
|
|
{
|
|
return "CLOSED";
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
DebugPrint("TCP::{0}, OnStateEnter\n",GetStateName());
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! owner, TcpState newState)
|
|
{
|
|
assert newState != null;
|
|
DebugPrint("State Transit: {0}->{1}\n", GetStateName(),
|
|
newState.GetStateName());
|
|
if (newState==LISTEN)
|
|
{
|
|
// passive open
|
|
DebugPrint("---> waiting for connections...\n");
|
|
}
|
|
else if (newState==SYN_SENT)
|
|
{
|
|
// active open
|
|
DebugPrint("---> sending SYN...\n");
|
|
if (!TCPFSM.SendSYN(owner,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);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Core.Panic("TCP state machine error!!!");
|
|
}
|
|
|
|
}
|
|
|
|
override internal NetStatus OnTimeout(Dispatcher.CallbackArgs args)
|
|
{
|
|
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! owner, NetPacket! pkt,object ctx)
|
|
{
|
|
///assert ctx != null;
|
|
//TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)ctx;
|
|
//TcpSession s = (TcpSession)owner; // not used
|
|
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 the state name (debug)
|
|
override internal string GetStateName()
|
|
{
|
|
return "LISTEN";
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
DebugPrint("TCP::{0}, OnStateEnter\n",GetStateName());
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! owner, NetPacket! pkt,object ctx)
|
|
{
|
|
assert ctx != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)ctx;
|
|
TcpSession s = (TcpSession)owner;
|
|
|
|
if (TcpFormat.IsReset(ref tcpHeader))
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR; // we ignore it
|
|
|
|
if (TcpFormat.IsAck(ref tcpHeader))
|
|
{
|
|
SendReset(s, false);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (TcpFormat.IsSync(ref tcpHeader))
|
|
{
|
|
// we received a syn!
|
|
// 1. create a new TCP session and initialize it
|
|
// (only if we have room...TBC: should use listen param for bound)
|
|
// 2. send syn-ack in the context of the new session
|
|
|
|
TcpSession client =
|
|
(TcpSession!)s.Protocol.CreateSession();
|
|
client.passiveSession = s; // this is our "owner"
|
|
client.SetLocalEndPoint(s.LocalAddress, s.LocalPort);
|
|
client.sessionTCB.RCV.NXT = tcpHeader.seq+1;
|
|
client.sessionTCB.RCV.IRS = tcpHeader.seq;
|
|
client.sessionTCB.SND.ISS = (uint)DateTime.UtcNow.Ticks;
|
|
client.sessionTCB.SND.NXT = client.sessionTCB.SND.ISS+1;
|
|
client.sessionTCB.SND.NextSeq = client.sessionTCB.SND.NXT;
|
|
client.sessionTCB.SND.UNA = client.sessionTCB.SND.ISS;
|
|
client.sessionTCB.SND.WND = tcpHeader.window;
|
|
client.sessionTCB.RCV.WND = TcpFormat.TCP_MSS;
|
|
|
|
// we have access to the IP header from the packet overlapped
|
|
// context (TCPModule put it there)
|
|
IPFormat.IPHeader ipHeader = pkt.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 (s.AcceptQueueIsFull())
|
|
{
|
|
TcpSegment reset = CreateResetSegment(client, true);
|
|
s.Protocol.OnProtocolSend(reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// send syn-ack
|
|
if (!SendSYNAck(client))
|
|
{
|
|
// This should never happen; our outbound packet
|
|
// queues should never be full while we're setting
|
|
// up a session.
|
|
Debug.Assert(false);
|
|
}
|
|
|
|
// the new session starts at syn-rcv
|
|
client.stateContext=null;
|
|
client.oldState=null;
|
|
client.ChangeState(SYN_RECVD);
|
|
|
|
// Start the timer ticking on establishing the new session
|
|
client.StartConnectTimer();
|
|
|
|
// 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 the state name (debug)
|
|
override internal string GetStateName()
|
|
{
|
|
return "SYNRECVD";
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
DebugPrint("TCP::{0}, OnStateEnter\n", GetStateName());
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! owner, NetPacket! pkt,object ctx)
|
|
{
|
|
assert ctx != null;
|
|
// all TCP states get the tcpHeader context
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)ctx;
|
|
TcpSession s = (TcpSession)owner;
|
|
uint segmentSize = (uint)pkt.Available;
|
|
|
|
bool accept=IsSegmentAcceptable(s,ref tcpHeader,segmentSize);
|
|
|
|
if (!accept)
|
|
{
|
|
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(owner);
|
|
}
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// we accept the segment
|
|
Debug.Assert(tcpHeader.seq==s.sessionTCB.RCV.NXT);
|
|
|
|
// if we get a reset
|
|
if (TcpFormat.IsReset(ref tcpHeader))
|
|
{
|
|
if (s.oldState==LISTEN)
|
|
{
|
|
// if we came from the listen state (passive open)
|
|
// the reset will return us to the Listen state
|
|
s.FlushRetransmissions();
|
|
ChangeState(s,LISTEN);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
else
|
|
{
|
|
// we came from SYN_SENT (active open)
|
|
// the connection was refused, close it!
|
|
HandleTerminateSession(s,null, TcpError.Refused);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
if (TcpFormat.IsSync(ref tcpHeader))
|
|
{
|
|
// reset
|
|
HandleTerminateSession(s,CreateResetSegment(s, false), TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (TcpFormat.IsAck(ref tcpHeader))
|
|
{
|
|
if (TCPSeqLEQ(s.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
|
|
TCPSeqGEQ(s.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(s,ref tcpHeader);
|
|
AttemptToEnterESTABLISHED(s, ref tcpHeader);
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
else
|
|
{
|
|
// reset
|
|
HandleTerminateSession(s,CreateResetSegment(s, false), TcpError.Reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
|
|
if (TcpFormat.IsFIN(ref tcpHeader))
|
|
{
|
|
DebugPrint("TCP::TCPStateSynRcv: Received FIN!!!\n");
|
|
|
|
// RCV.NXT should reflect the data that we processed out of
|
|
// this packet, but not the FIN. Advance over the FIN.
|
|
s.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(s);
|
|
ChangeState(s,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 the state name (debug)
|
|
override internal string GetStateName()
|
|
{
|
|
return "SYNSENT";
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
DebugPrint("TCP::{0}, OnStateEnter\n",GetStateName());
|
|
}
|
|
|
|
// we sent a syn we wait for syn-ack
|
|
override internal NetStatus OnPacketReceive(TcpSession! owner,NetPacket! pkt,object ctx)
|
|
{
|
|
assert ctx != null;
|
|
// all TCP states get the tcpHeader context
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)ctx;
|
|
TcpSession s = (TcpSession)owner;
|
|
bool ackAcceptable=false;
|
|
|
|
if (TcpFormat.IsAck(ref tcpHeader))
|
|
{
|
|
if (TCPSeqLEQ(tcpHeader.ackSeq, s.sessionTCB.SND.ISS) ||
|
|
TCPSeqGreater(tcpHeader.ackSeq, s.sessionTCB.SND.NXT))
|
|
{
|
|
if (TcpFormat.IsReset(ref tcpHeader))
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
|
|
SendReset(s, false);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (TCPSeqLEQ(s.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
|
|
TCPSeqLEQ(tcpHeader.ackSeq, s.sessionTCB.SND.NXT))
|
|
{
|
|
ackAcceptable=true;
|
|
}
|
|
}
|
|
|
|
if (TcpFormat.IsReset(ref tcpHeader))
|
|
{
|
|
if (ackAcceptable)
|
|
{
|
|
// connection was reset, drop the segment
|
|
// and close the connection.
|
|
HandleTerminateSession(s,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)
|
|
{
|
|
DebugPrint("TCP::SYNSENT, Received SYN-ACK\n");
|
|
|
|
// grab the ack parameters and
|
|
// complete the session's data
|
|
s.sessionTCB.RCV.NXT = tcpHeader.seq+1;
|
|
s.sessionTCB.RCV.IRS = tcpHeader.seq;
|
|
s.sessionTCB.SND.UNA = tcpHeader.ackSeq;
|
|
s.sessionTCB.SND.WND = tcpHeader.window;
|
|
s.sessionTCB.RCV.WND = TcpFormat.TCP_MSS;
|
|
|
|
// great! now remove the SYN from
|
|
// the retransmit Q (so we don't
|
|
// retransmit it again)
|
|
TCPFSM.HandleTCPAck(s,ref tcpHeader);
|
|
|
|
if (s.sessionTCB.SND.UNA>s.sessionTCB.SND.ISS)
|
|
{
|
|
// our syn has been acked.
|
|
TCPFSM.SendAck(owner);
|
|
|
|
// change the state to established!
|
|
AttemptToEnterESTABLISHED(owner, 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!)s.Protocol.CreateSession();
|
|
client.passiveSession = s; // this is our "owner"
|
|
client.SetLocalEndPoint(s.LocalAddress,
|
|
s.LocalPort);
|
|
client.sessionTCB.RCV.NXT = tcpHeader.seq+1;
|
|
client.sessionTCB.RCV.IRS = tcpHeader.seq;
|
|
client.sessionTCB.SND.ISS = (uint)DateTime.UtcNow.Ticks;
|
|
client.sessionTCB.SND.NXT = client.sessionTCB.SND.ISS+1;
|
|
client.sessionTCB.SND.NextSeq = client.sessionTCB.SND.NXT;
|
|
client.sessionTCB.SND.UNA = client.sessionTCB.SND.ISS;
|
|
client.sessionTCB.SND.WND = tcpHeader.window;
|
|
client.sessionTCB.RCV.WND = TcpFormat.TCP_MSS;
|
|
|
|
// we have access to the IP header from the packet overlapped
|
|
// context (TCPModule put it there)
|
|
IPFormat.IPHeader ipHeader = pkt.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 (s.AcceptQueueIsFull())
|
|
{
|
|
TcpSegment reset = CreateResetSegment(client, true);
|
|
s.Protocol.OnProtocolSend(reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// send syn-ack
|
|
if (!SendSYNAck(client))
|
|
{
|
|
// This should never happen; our outbound packet
|
|
// queues should never be empty when we're setting
|
|
// up a session
|
|
Debug.Assert(false);
|
|
}
|
|
|
|
// the new session starts at syn-rcv
|
|
client.stateContext=null;
|
|
client.oldState=null;
|
|
client.ChangeState(SYN_RECVD);
|
|
|
|
// start the establishment countdown
|
|
client.StartConnectTimer();
|
|
|
|
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 the state name (debug)
|
|
override internal string GetStateName()
|
|
{
|
|
return "ESTABLISHED";
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
#if DEBUG_TCP
|
|
Core.Log("TCP::{0}, OnStateEnter\n",GetStateName());
|
|
#endif
|
|
// Wake up anyone waiting for the connection to complete
|
|
((TcpSession)owner).setupCompleteEvent.Set();
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! owner, TcpState newState)
|
|
{
|
|
#if DEBUG_TCP
|
|
assert newState != null;
|
|
Core.Log("State Transit: {0}->{1}\n", GetStateName(),
|
|
newState.GetStateName());
|
|
#endif
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! owner, NetPacket! pkt,object ctx)
|
|
{
|
|
assert ctx != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)ctx;
|
|
TcpSession s = (TcpSession)owner;
|
|
uint segmentSize = (uint)pkt.Available; // TCPModule already shrinked it for us
|
|
NetStatus res=NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
|
|
bool accept=IsSegmentAcceptable(s,ref tcpHeader,segmentSize);
|
|
|
|
if (!accept)
|
|
{
|
|
if (!TcpFormat.IsReset(ref tcpHeader))
|
|
{
|
|
// send an ACK and return
|
|
TCPFSM.SendAck(owner);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
if (TcpFormat.IsReset(ref tcpHeader))
|
|
{
|
|
// for simplicity, just close the connection.
|
|
HandleTerminateSession(s,null, TcpError.Reset);
|
|
return res;
|
|
}
|
|
|
|
// check syn
|
|
if (TcpFormat.IsSync(ref tcpHeader))
|
|
{
|
|
// send a reset
|
|
HandleTerminateSession(s,CreateResetSegment(s, false), TcpError.Reset);
|
|
return res;
|
|
}
|
|
|
|
res = HandleTCPData(ref tcpHeader, pkt, s);
|
|
|
|
// our peer wants to end the relationship...
|
|
if (TcpFormat.IsFIN(ref tcpHeader))
|
|
{
|
|
#if DEBUG_TCP
|
|
Core.Log("TCP::ESTABLISHED: Received FIN!!!\n");
|
|
#endif
|
|
// RCV.NXT should reflect any data in this packet, but not the FIN.
|
|
// Advance over the FIN.
|
|
s.sessionTCB.RCV.NXT += 1;
|
|
|
|
// ack the FIN
|
|
SendAck(s);
|
|
ChangeState(s,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 the state name (debug)
|
|
override internal string GetStateName()
|
|
{
|
|
return "CLOSE_WAIT";
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
#if DEBUG_TCP
|
|
Core.Log("TCP::{0}, OnStateEnter\n",GetStateName());
|
|
#endif
|
|
// Signal that there is nothing more to read on this
|
|
// connection
|
|
owner.ValidForRead = false;
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! owner, TcpState newState)
|
|
{
|
|
#if DEBUG_TCP
|
|
assert newState != null;
|
|
Core.Log("State Transit: {0}->{1}\n", GetStateName(),
|
|
newState.GetStateName());
|
|
#endif
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! owner, NetPacket! pkt,object ctx)
|
|
{
|
|
assert ctx != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)ctx;
|
|
TcpSession s = (TcpSession)owner;
|
|
uint segmentSize = (uint)pkt.Available;
|
|
|
|
if (!IsSegmentAcceptable(s,ref tcpHeader,segmentSize))
|
|
{
|
|
if (!TcpFormat.IsReset(ref tcpHeader))
|
|
{
|
|
// send an ACK and return
|
|
TCPFSM.SendAck(owner);
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (TcpFormat.IsReset(ref tcpHeader) ||
|
|
TcpFormat.IsSync(ref tcpHeader) ||
|
|
(!TcpFormat.IsAck(ref tcpHeader)))
|
|
{
|
|
// Abort
|
|
HandleTerminateSession(s,null, TcpError.Reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
TCPFSM.HandleTCPAck(s,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 the state name (debug)
|
|
override internal string GetStateName()
|
|
{
|
|
return "LAST_ACK";
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
#if DEBUG_TCP
|
|
Core.Log("TCP::{0}, OnStateEnter\n",GetStateName());
|
|
#endif
|
|
|
|
// Flag that writing is now disallowed on this session
|
|
((TcpSession)owner).ValidForWrite = false;
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! owner, TcpState newState)
|
|
{
|
|
#if DEBUG_TCP
|
|
assert newState != null;
|
|
Core.Log("State Transit: {0}->{1}\n",GetStateName(),
|
|
newState.GetStateName());
|
|
#endif
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! owner,NetPacket! pkt,object ctx)
|
|
{
|
|
assert ctx != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)ctx;
|
|
TcpSession s = (TcpSession)owner;
|
|
uint segmentSize = (uint)pkt.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(s,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(s,CreateResetSegment(s, false), TcpError.Reset);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (!IsSegmentAcceptable(s,ref tcpHeader,segmentSize))
|
|
{
|
|
// Just drop it
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// Do ACK housekeeping
|
|
if (TCPSeqLess(s.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
|
|
TCPSeqLEQ(tcpHeader.ackSeq, s.sessionTCB.SND.NXT))
|
|
{
|
|
// This appears to be ACKing something we sent.
|
|
s.sessionTCB.SND.UNA = tcpHeader.ackSeq;
|
|
|
|
// remove the packet(s) from the retransmit queue
|
|
TCPFSM.HandleTCPAck(s,ref tcpHeader);
|
|
}
|
|
|
|
// Note the weirdness here; because we prepacketize
|
|
// data, we don't want to compare to SND.NXT
|
|
if (TCPSeqGEQ(s.sessionTCB.SND.UNA, s.sessionTCB.SND.NextSeq))
|
|
{
|
|
// The remote site has acknowledged everything
|
|
// we sent. We're done.
|
|
HandleTerminateSession(s, 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 the state name (debug)
|
|
override internal string GetStateName()
|
|
{
|
|
return "FIN_WAIT1";
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
#if DEBUG_TCP
|
|
Core.Log("TCP::{0}, OnStateEnter\n",GetStateName());
|
|
#endif
|
|
|
|
// Flag that writing is now disallowed on this session
|
|
((TcpSession)owner).ValidForWrite = false;
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! owner, TcpState newState)
|
|
{
|
|
#if DEBUG_TCP
|
|
assert newState != null;
|
|
Core.Log("State Transit: {0}->{1}\n",GetStateName(),
|
|
newState.GetStateName());
|
|
#endif
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! owner, NetPacket! pkt,object ctx)
|
|
{
|
|
assert ctx != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)ctx;
|
|
TcpSession s = (TcpSession)owner;
|
|
uint segmentSize = (uint)pkt.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(s,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(s,CreateResetSegment(s, false), TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (!IsSegmentAcceptable(s,ref tcpHeader,segmentSize))
|
|
{
|
|
if (!TcpFormat.IsReset(ref tcpHeader))
|
|
{
|
|
// send an ACK and return
|
|
TCPFSM.SendAck(owner);
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// Chew on the payload...
|
|
NetStatus retval = HandleTCPData(ref tcpHeader, pkt, s);
|
|
|
|
if (TCPSeqLEQ(tcpHeader.seq, s.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
|
|
s.sessionTCB.RCV.NXT += 1;
|
|
|
|
// note the weirdness here; because we
|
|
// prepacketize data, we don't want to test
|
|
// against SND.NXT
|
|
if (TCPSeqLess(s.sessionTCB.SND.UNA, s.sessionTCB.SND.NextSeq))
|
|
{
|
|
// ...but the remote side hasn't received
|
|
// everything we have said, yet.
|
|
|
|
// ack the FIN
|
|
SendAck(s);
|
|
ChangeState(s, CLOSING);
|
|
}
|
|
else
|
|
{
|
|
// The remote side has heard everything we
|
|
// have to say. We're done.
|
|
// ACK the FIN and shut down
|
|
TcpSegment ackSeg = CreateAckSegment(s);
|
|
HandleTerminateSession(s, 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(s.sessionTCB.SND.UNA, s.sessionTCB.SND.NextSeq))
|
|
{
|
|
// The remote side has ACKed everything we have
|
|
// said, but they haven't FINed themselves.
|
|
ChangeState(s, 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 the state name (debug)
|
|
override internal string GetStateName()
|
|
{
|
|
return "FIN_WAIT2";
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
#if DEBUG_TCP
|
|
Core.Log("TCP::{0}, OnStateEnter\n",GetStateName());
|
|
#endif
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! owner, TcpState newState)
|
|
{
|
|
#if DEBUG_TCP
|
|
assert newState != null;
|
|
Core.Log("State Transit: {0}->{1}\n",GetStateName(),
|
|
newState.GetStateName());
|
|
#endif
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! owner, NetPacket! pkt,object ctx)
|
|
{
|
|
assert ctx != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)ctx;
|
|
TcpSession s = (TcpSession)owner;
|
|
uint segmentSize = (uint)pkt.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(s,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(s,CreateResetSegment(s, false), TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (!IsSegmentAcceptable(s,ref tcpHeader,segmentSize))
|
|
{
|
|
if (!TcpFormat.IsReset(ref tcpHeader))
|
|
{
|
|
// send an ACK and return
|
|
TCPFSM.SendAck(owner);
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// Chew on the payload...
|
|
NetStatus retval = HandleTCPData(ref tcpHeader, pkt, s);
|
|
|
|
// Only check for FIN if we have previously received
|
|
// all data.
|
|
if (TCPSeqLEQ(tcpHeader.seq, s.sessionTCB.RCV.NXT) &&
|
|
(TcpFormat.IsFIN(ref tcpHeader)))
|
|
{
|
|
#if DEBUG_TCP
|
|
Core.Log("FIN_WAIT2: Received FIN!!!\n");
|
|
#endif
|
|
// RCV.NXT should reflect the data in this packet, but not the
|
|
// FIN. Advance over the FIN...
|
|
s.sessionTCB.RCV.NXT += 1;
|
|
|
|
// ack the FIN and shut down
|
|
TcpSegment ackSeg = CreateAckSegment(s);
|
|
HandleTerminateSession(s, 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 the state name (debug)
|
|
override internal string GetStateName()
|
|
{
|
|
return "CLOSING";
|
|
}
|
|
|
|
override internal void OnStateEnter(TcpSession! owner)
|
|
{
|
|
#if DEBUG_TCP
|
|
Core.Log("TCP::{0}, OnStateEnter\n",GetStateName());
|
|
#endif
|
|
// Signal that there's nothing more to read on this session
|
|
owner.ValidForRead = false;
|
|
}
|
|
|
|
// leave the state
|
|
override internal void OnStateLeave(TcpSession! owner, TcpState newState)
|
|
{
|
|
#if DEBUG_TCP
|
|
assert newState != null;
|
|
Core.Log("State Transit: {0}->{1}\n",GetStateName(),
|
|
newState.GetStateName());
|
|
#endif
|
|
}
|
|
|
|
override internal NetStatus OnPacketReceive(TcpSession! owner, NetPacket! pkt, object ctx)
|
|
{
|
|
assert ctx != null;
|
|
TcpFormat.TcpHeader tcpHeader = (TcpFormat.TcpHeader)ctx;
|
|
TcpSession s = (TcpSession)owner;
|
|
uint segmentSize = (uint)pkt.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(s,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(s,CreateResetSegment(s, false), TcpError.ProtocolViolation);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
if (!IsSegmentAcceptable(s,ref tcpHeader,segmentSize))
|
|
{
|
|
// Just drop it
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// Do ACK housekeeping
|
|
if (TCPSeqLess(s.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
|
|
TCPSeqLEQ(tcpHeader.ackSeq, s.sessionTCB.SND.NXT))
|
|
{
|
|
// This appears to be ACKing something we sent.
|
|
s.sessionTCB.SND.UNA = tcpHeader.ackSeq;
|
|
|
|
// remove the packet(s) from the retransmit queue
|
|
TCPFSM.HandleTCPAck(s,ref tcpHeader);
|
|
}
|
|
|
|
// Note the weirdness here; because we
|
|
// prepacketize data, we don't want to compare against
|
|
// SND.NXT
|
|
if (TCPSeqGEQ(s.sessionTCB.SND.UNA, s.sessionTCB.SND.NextSeq))
|
|
{
|
|
// The remote side has now heard everything we said.
|
|
HandleTerminateSession(s, null, TcpError.Closed);
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
// Create an ACK for data we have received to date
|
|
internal static TcpSegment! CreateAckSegment(TcpSession! s)
|
|
{
|
|
byte[] ackBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];
|
|
|
|
TcpFormat.WriteTcpSegment(
|
|
ackBuffer,s.LocalPort,s.RemotePort,s.sessionTCB.RCV.NXT,
|
|
s.sessionTCB.SND.NextSeq,TcpFormat.TCP_MSS,s.LocalAddress,
|
|
s.RemoteAddress,0,true,false,false,false,false);
|
|
|
|
return new TcpSegment(ackBuffer,s,s.sessionTCB.SND.NextSeq,true);
|
|
}
|
|
|
|
// sends an ack
|
|
internal static bool SendAck(TcpSession! owner)
|
|
{
|
|
TcpSession s = (TcpSession)owner;
|
|
TcpSegment ackSeg = CreateAckSegment(s);
|
|
|
|
// put it on this session outgoing queue
|
|
return s.PutPacket(s.outQueue,ackSeg,false);
|
|
}
|
|
|
|
// Sends a FIN
|
|
internal static bool SendFin(TcpSession! s, bool canBlock)
|
|
{
|
|
byte[] finBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];
|
|
|
|
TcpFormat.WriteTcpSegment(
|
|
finBuffer,s.LocalPort,s.RemotePort,s.sessionTCB.RCV.NXT,
|
|
s.sessionTCB.SND.NextSeq,TcpFormat.TCP_MSS,s.LocalAddress,s.RemoteAddress,0,
|
|
true,false,false,true,false);
|
|
|
|
// ok, we have a ready segment.
|
|
TcpSegment syn = new TcpSegment(finBuffer,s,s.sessionTCB.SND.NextSeq,true);
|
|
|
|
bool retVal = s.PutPacket(s.outQueue,syn, canBlock);
|
|
|
|
// Only advance the segment counter if we successfully queued the
|
|
// outbound packet
|
|
if (retVal)
|
|
{
|
|
s.sessionTCB.SND.NextSeq++;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
// sends a syn ack packet
|
|
internal static bool SendSYNAck(TcpSession! owner)
|
|
{
|
|
TcpSession s = (TcpSession)owner;
|
|
byte[] synackBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size];
|
|
|
|
TcpFormat.WriteTcpSegment(synackBuffer,s.LocalPort,s.RemotePort,s.sessionTCB.RCV.NXT,
|
|
s.sessionTCB.SND.ISS,(ushort)s.sessionTCB.SND.WND,s.LocalAddress,
|
|
s.RemoteAddress,0,true,true,false,false,false);
|
|
|
|
// ok, we have a ready segment.
|
|
// (SYN is regular segment)
|
|
TcpSegment syn = new TcpSegment(synackBuffer, s, s.sessionTCB.SND.ISS, true);
|
|
|
|
// put it on this session outgoing queue
|
|
return s.PutPacket(s.outQueue,syn,false);
|
|
}
|
|
|
|
// sends a syn packet, with MSS options
|
|
internal static bool SendSYN(TcpSession! owner,ushort MSS)
|
|
{
|
|
TcpSession s = (TcpSession)owner;
|
|
byte[] synBuffer = new byte[EthernetFormat.Size+IPFormat.Size+TcpFormat.Size+4];
|
|
|
|
// setup the session
|
|
s.sessionTCB.SND.ISS=(uint)DateTime.UtcNow.Ticks;
|
|
s.sessionTCB.SND.UNA=s.sessionTCB.SND.ISS;
|
|
s.sessionTCB.SND.NXT=s.sessionTCB.SND.ISS+1; // next packet sequence
|
|
s.sessionTCB.SND.NextSeq = s.sessionTCB.SND.NXT;
|
|
s.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,s.LocalPort,s.RemotePort,0,
|
|
s.sessionTCB.SND.ISS,MSS,s.LocalAddress,s.RemoteAddress,4,
|
|
false,true,false,false,true);
|
|
|
|
// ok, we have a ready segment.
|
|
// (SYN is regular segment)
|
|
TcpSegment syn = new TcpSegment(synBuffer,s,s.sessionTCB.SND.ISS,false);
|
|
|
|
// put it on this session outgoing queue
|
|
return s.PutPacket(s.outQueue,syn,false);
|
|
}
|
|
|
|
// send a TCP reset
|
|
internal static bool SendReset(TcpSession! owner, bool isAck)
|
|
{
|
|
// we won't retransmit it (it is like an Ack)
|
|
TcpSegment syn = CreateResetSegment(owner, true);
|
|
|
|
// put it on this session outgoing queue
|
|
return owner.PutPacket(owner.outQueue,syn,false);
|
|
}
|
|
|
|
// send a TCP reset
|
|
internal static TcpSegment! CreateResetSegment(TcpSession! owner, bool isAck)
|
|
{
|
|
TcpSession s = (TcpSession)owner;
|
|
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,s.LocalPort,s.RemotePort,s.sessionTCB.RCV.NXT,
|
|
s.sessionTCB.SND.NXT,TcpFormat.TCP_MSS,s.LocalAddress,
|
|
s.RemoteAddress,0,isAck,false,true,false,false);
|
|
|
|
// we won't retransmit it
|
|
TcpSegment syn = new TcpSegment(rstBuffer,s,s.sessionTCB.SND.NXT,true);
|
|
return syn;
|
|
}
|
|
|
|
// Handle received TCP data
|
|
// Returns the appropriate NetStatus.Code
|
|
internal static NetStatus HandleTCPData(ref TcpFormat.TcpHeader tcpHeader,
|
|
NetPacket! pkt, TcpSession! s)
|
|
{
|
|
uint segmentSize = (uint)pkt.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, s.sessionTCB.RCV.NXT))
|
|
{
|
|
// we missed one or few, send ack again
|
|
TCPFSM.SendAck(s);
|
|
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.
|
|
s.sessionTCB.SND.WND = tcpHeader.window;
|
|
s.HandleWindowUpdate();
|
|
|
|
// ack is set and it is relevant (ack something we sent)
|
|
if (TCPSeqLess(s.sessionTCB.SND.UNA, tcpHeader.ackSeq) &&
|
|
TCPSeqLEQ(tcpHeader.ackSeq, s.sessionTCB.SND.NXT))
|
|
{
|
|
s.sessionTCB.SND.UNA = tcpHeader.ackSeq;
|
|
|
|
// remove the packet(s) from the retransmit queue
|
|
TCPFSM.HandleTCPAck(s,ref tcpHeader);
|
|
}
|
|
else if (s.sessionTCB.SND.UNA>tcpHeader.ackSeq)
|
|
{
|
|
// a duplicate ack
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
else if (TCPSeqGreater(tcpHeader.ackSeq, s.sessionTCB.SND.NXT))
|
|
{
|
|
// ack for future data..
|
|
TCPFSM.SendAck(s);
|
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
|
}
|
|
|
|
// check URG bit
|
|
if (TcpFormat.IsUrg(ref tcpHeader))
|
|
{
|
|
// TBC
|
|
}
|
|
|
|
if (TcpFormat.IsPush(ref tcpHeader))
|
|
{
|
|
#if DEBUG_TCP
|
|
Core.Log("TCP: Received PUSH data, Len={0} !!!!!!!\n",segmentSize);
|
|
#endif
|
|
}
|
|
|
|
// 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 (s.PutPacket(s.inQueue,pkt,false))
|
|
{
|
|
s.sessionTCB.RCV.NXT+=segmentSize; // we expect the next segment seq
|
|
s.sessionTCB.RCV.WND=TcpFormat.TCP_MSS; // TBC: change according to buffer
|
|
#if DEBUG_TCP
|
|
Core.Log("TCP:: PUSH data, Len={0} is on Queue\n",segmentSize);
|
|
#endif
|
|
// send our ack if we ack data
|
|
SendAck(s);
|
|
// don't release the packet
|
|
return NetStatus.Code.PROTOCOL_PROCESSING;
|
|
}
|
|
else
|
|
{
|
|
// packet was dropped...
|
|
// send our ack if we ack data
|
|
SendAck(s);
|
|
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! owner, ref TcpFormat.TcpHeader tcpHdr)
|
|
{
|
|
owner.ACKThrough(tcpHdr.ackSeq);
|
|
}
|
|
|
|
// check if we can accept the segment
|
|
internal static bool IsSegmentAcceptable(TcpSession! s, ref TcpFormat.TcpHeader tcpHeader,uint segmentSize)
|
|
{
|
|
bool accept=false;
|
|
// first check sequence number
|
|
if ((segmentSize==0)&&(s.sessionTCB.RCV.WND==0))
|
|
{
|
|
accept = (tcpHeader.seq==s.sessionTCB.RCV.NXT);
|
|
}
|
|
else if ((segmentSize==0)&&(s.sessionTCB.RCV.WND>0))
|
|
{
|
|
accept=(TCPSeqGEQ(tcpHeader.seq, s.sessionTCB.RCV.NXT) &&
|
|
TCPSeqLess(tcpHeader.seq, s.sessionTCB.RCV.NXT+s.sessionTCB.RCV.WND));
|
|
}
|
|
else if ((segmentSize>0)&&(s.sessionTCB.RCV.WND>0))
|
|
{
|
|
accept=(TCPSeqGEQ(tcpHeader.seq, s.sessionTCB.RCV.NXT) &&
|
|
TCPSeqLess(tcpHeader.seq, s.sessionTCB.RCV.NXT+s.sessionTCB.RCV.WND));
|
|
|
|
accept = accept || (TCPSeqLEQ(s.sessionTCB.RCV.NXT, tcpHeader.seq+segmentSize-1) &&
|
|
TCPSeqLess(tcpHeader.seq +segmentSize - 1,
|
|
s.sessionTCB.RCV.NXT + s.sessionTCB.RCV.WND));
|
|
}
|
|
return accept;
|
|
}
|
|
|
|
private static void AttemptToEnterESTABLISHED(TcpSession! s,
|
|
ref TcpFormat.TcpHeader tcpHeader)
|
|
{
|
|
TcpSession passiveOwner = s.passiveSession;
|
|
|
|
if (passiveOwner != null)
|
|
{
|
|
bool success = passiveOwner.AddAcceptedSession(s);
|
|
|
|
if (!success)
|
|
{
|
|
// Oops; while this connection was being established
|
|
// we ran out of room in the accept queue. Abort
|
|
// the connection!
|
|
HandleTerminateSession(s,CreateResetSegment(s, false), TcpError.ResourcesExhausted);
|
|
return;
|
|
}
|
|
}
|
|
|
|
s.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! s, TcpSegment nextSegment, TcpError connectError)
|
|
{
|
|
s.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)
|
|
s.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 (s.outQueue.SyncRoot)
|
|
{
|
|
// from now on, every user thread will
|
|
// fail to read/write data
|
|
s.ValidForRead = false;
|
|
s.ValidForWrite = false;
|
|
|
|
s.DrainQueue(s.outQueue);
|
|
if (nextSegment != null)
|
|
s.outQueue.Add(nextSegment);
|
|
|
|
// Don't forget these!
|
|
Monitor.PulseAll(s.outQueue.SyncRoot);
|
|
Core.Instance().SignalOutboundPackets();
|
|
}
|
|
|
|
lock (s.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(s.inQueue.SyncRoot);
|
|
}
|
|
|
|
if (nextSegment == null)
|
|
{
|
|
IProtocol tcpProtocol = s.Protocol;
|
|
Core.Instance().DeregisterSession(tcpProtocol, s);
|
|
}
|
|
|
|
// change the state to close
|
|
s.ChangeState(TCPFSM.CLOSED);
|
|
|
|
// Signal anyone who was waiting for this session to begin or end
|
|
s.connectError = connectError;
|
|
s.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)
|
|
{
|
|
s.closedEvent.Set();
|
|
}
|
|
}
|
|
}
|
|
}
|