singrdk/base/Services/NetStack/Runtime/TCPSession.cs

1117 lines
40 KiB
C#

///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////
/**
* Microsoft Research, Cambridge
* author: Yaron Weinsberg, Richard Black
*/
// #define DEBUG_TCP
using NetStack.Common;
using System;
using System.Threading;
using System.Collections;
using System.Diagnostics;
#if !SINGULARITY
using System.Net;
#endif
using System.Net.IP;
using Drivers.Net;
using NetStack.Protocols;
using NetStack.Contracts;
using Microsoft.Contracts;
using Microsoft.SingSharp;
using Microsoft.SingSharp.Runtime;
using Microsoft.Singularity;
using Microsoft.Singularity.Channels;
namespace NetStack.Runtime
{
/**
* This the TCP session object
*/
public class TcpSession : Session
{
// the session's transmit q size (num of packets)
public const int TxQSize=100;
// the session's receive q size (num of packets)
public const int RcvQSize=100;
// the maximum number of retries to send before giving up
private const int MaxRetries = 5;
// the number of seconds to wait for an active connect to succeed
private const int ConnectTimeout = 15;
// the number of seconds to wait for a passive connect to succeed
private const int PassiveConnectTimeout = 5;
// the number of seconds between probes of a remote host that has
// closed its receive window
private const int PersistTimeout = 2;
// the number of seconds to wait for a graceful shutdown to complete
private const int PoliteShutdownTimeout = 10;
// TCP retransmission state
private RJBlack.Timer retransTimer;
private const uint InitialRetransInterval = 3000; // 3s in ms, Per RFC 2988
// 200ms, more aggressive than RFC "SHOULD" of 1s
private const uint MinimumRetransInterval = 200;
// Per-session retransmission state
private uint retransInterval = InitialRetransInterval, srtt = InitialRetransInterval, rttvar = 0; // All in ms
internal TcpState stateContext; // our FSM state
internal TcpState oldState; // our previous state
internal TcpError connectError; // the result of the Connect request
// accepted session list (TcpSessions)
private ArrayList acceptedSessions;
private int maxAcceptedSessions;
// a monitor for the accepted session list
private object acceptSessionMonitor;
// the passive session (owner) of this session
// (applied to passive sessions)
internal TcpSession passiveSession;
// the retransmit queue
private const int RetransmitQSize = 100;
private ArrayList retransmitQ;
// Setup / teardown timers
RJBlack.Timer setupTimer;
RJBlack.Timer shutdownTimer;
// Information on the "persist" state (for when the remote host
// has closed its receive window)
RJBlack.Timer persistTimer;
// An event that gets set when initial establishment either
// succeeds or times out
internal System.Threading.ManualResetEvent setupCompleteEvent;
// An event that gets set when we're done shutting down this session
internal System.Threading.ManualResetEvent closedEvent;
// is this session is valid for USERS to read/write data
protected bool isValidForRead, isValidForWrite;
// Whether or not BindLocalEndPoint() has been called
bool haveBound = false;
public bool ValidForRead { get { return isValidForRead;} set {isValidForRead=value;}}
public bool ValidForWrite { get { return isValidForWrite;} set {isValidForWrite=value;}}
// TCB
public struct TCB
{
public SNDValues SND;
public RCVValues RCV;
// send parameters
public struct SNDValues
{
public uint UNA; // send unacknowledges
public uint NXT; // send seqnum that will be transmitted
public uint WND; // send window
public uint UP; // send urgent pointer
public uint WL1; // segment seq number used for last window update
public uint WL2; // segment ack number used for last window update
public uint ISS; // initial send sequence number
public uint NextSeq; // next sequence number to use when packetizing
// (not the same as NXT)
}
// receive parameters
public struct RCVValues
{
public uint NXT; // receive next
public uint WND; // receive window
public uint UP; // receive urgent pointer
public uint IRS; // initial receive sequence number
}
}
// the session's TCB
internal TCB sessionTCB;
[NotDelayed]
public TcpSession(IProtocol! p)
: base(p,TxQSize,RcvQSize)
{
sessionTCB = new TCB();
sessionTCB.SND.WND = TcpFormat.TCP_MSS;
sessionTCB.RCV.WND = TcpFormat.TCP_MSS;
retransmitQ = new ArrayList(RetransmitQSize);
acceptedSessions = new ArrayList();
maxAcceptedSessions = 0; // Changed by Listen()
acceptSessionMonitor = new object();
setupCompleteEvent = new System.Threading.ManualResetEvent(false);
closedEvent = new System.Threading.ManualResetEvent(false);
passiveSession = null;
// at first the session is valid (user can interact with it)
isValidForRead = true;
isValidForWrite = true;
// create and initialize the init state
this.oldState = null;
ChangeState(TCPFSM.CLOSED);
}
public new void ReInitialize(IProtocol! p)
{
base.ReInitialize(p);
sessionTCB = new TCB();
sessionTCB.SND.WND = TcpFormat.TCP_MSS;
sessionTCB.RCV.WND = TcpFormat.TCP_MSS;
maxAcceptedSessions = 0;
passiveSession = null;
isValidForRead = true;
isValidForWrite = true;
DrainQueue(outQueue);
DrainQueue(inQueue);
retransmitQ.Clear();
acceptedSessions.Clear();
setupCompleteEvent.Reset();
closedEvent.Reset();
// create and initialize the init state
this.oldState = null;
if (!IsClosed) {
ChangeState(TCPFSM.CLOSED);
}
DestroyTimer(ref setupTimer);
DestroyTimer(ref shutdownTimer);
DestroyTimer(ref persistTimer);
retransInterval = InitialRetransInterval;
}
public bool IsClosed
{
get { return this.stateContext == TCPFSM.CLOSED; }
}
private void DestroyTimer(ref RJBlack.Timer t)
{
if (t != null) {
Core.Instance().TheDispatcher.RemoveTimeoutCallback(t);
}
t = null;
}
[ Conditional("DEBUG_TCP") ]
private static void DebugPrint(string format, params object [] args)
{
Core.Log("TCP: ");
Core.Log(format, args);
}
internal void StartConnectTimer()
{
ulong timeout;
if (passiveSession != null)
{
timeout = PassiveConnectTimeout;
}
else
{
timeout = ConnectTimeout;
}
Dispatcher.Callback fun = new Dispatcher.Callback(OnConnectTimeout);
ulong expiryTime = (ulong)DateTime.UtcNow.Ticks + (timeout * DateTime.TicksPerSecond);
setupTimer = Core.Instance().TheDispatcher.AddCallback(fun, null, expiryTime);
}
internal NetStatus OnConnectTimeout(Dispatcher.CallbackArgs args)
{
// We failed to become established in time. Bail out.
Terminate(null, TcpError.Timeout);
return NetStatus.Code.PROTOCOL_OK;
}
// change the state of this session
internal void ChangeState(TcpState! newState)
{
if (stateContext != null)
{
oldState = stateContext;
stateContext.OnStateLeave(this, newState);
}
// If we've become Established, stop the connect timer if there
// is one ticking
if (newState == TCPFSM.ESTABLISHED)
{
DestroyTimer(ref setupTimer);
}
if (newState == TCPFSM.CLOSED)
{
// Stop retransmitting
DestroyTimer(ref retransTimer);
}
stateContext = newState;
newState.OnStateEnter(this);
}
// the message is dispatched to the sessions. the sender
// is the protocol and the context is session specific
// (i.e., TCP can pass the TCP header to avoid
// processing it again at the session instance)
public delegate NetStatus OnPacketReceive(object sender,
NetPacket! packet,
object context);
// this is the state's delegate for handling the
// protocol triggered event
// the object parameter will be set to IProtocol interface
internal override NetStatus OnReceive(object sender,
NetPacket! pkt,
object ctx)
{
DebugPrint("Packet received.");
if (stateContext != null) {
// process it in the current state's context
DebugPrint("Packet received: State->{0}",
stateContext.GetStateName());
return (stateContext.OnPacketReceive(this, pkt, ctx));
}
return NetStatus.Code.PROTOCOL_DROP_ERROR;
}
private void StartPersistTimer()
{
Dispatcher.Callback fun = new Dispatcher.Callback(OnPersistTimeout);
ulong expiryTime = (ulong)DateTime.UtcNow.Ticks + (PersistTimeout * DateTime.TicksPerSecond);
persistTimer = Core.Instance().TheDispatcher.AddCallback(fun, null, expiryTime);
}
internal void StopPersistTimer()
{
DestroyTimer(ref persistTimer);
}
internal bool InPersistState()
{
return persistTimer != null;
}
internal uint FreeRemoteWindowBytes()
{
// The portion of the remote window that is available is the most recently
// advertised window size minus any outstanding unacknowledged data
return sessionTCB.SND.WND - (sessionTCB.SND.NXT - sessionTCB.SND.UNA);
}
// Called when SND.WND is updated
internal void HandleWindowUpdate()
{
uint newWindow = sessionTCB.SND.WND;
if (InPersistState() && (newWindow > 0)) {
// The remote receive window just reopened
StopPersistTimer();
}
// The window update may have made it newly possible to transmit
// queued data.
if ((FreeRemoteWindowBytes() > 0) &&
(outQueue.Count > 0)) {
Core.Instance().SignalOutboundPackets();
}
}
private NetStatus OnPersistTimeout(Dispatcher.CallbackArgs timeoutArg)
{
//
// NOTE This is a hack. A proper TCP stack is supposed to
// transmit a packet consisting of just one byte when probing the
// remote host to see if it has reopened its receive window.
// However, we prepacketize data, so we don't have that option.
// Instead, we probe using full packets.
//
TcpSegment seg = null;
if (retransmitQ.Count > 0) {
// Use the oldest unacknowledged packet to probe
seg = (TcpSegment)retransmitQ[0];
} else {
// Nothing in the retransmit queue; probe using the next
// normal packet. This will transition the packet to the
// retransmission queue.
seg = GetNextPacket(true /*ignore window*/ );
}
if (seg != null) {
seg.Mux = BoundMux;
NetStatus err = Protocol.OnProtocolSend(seg);
assert err == NetStatus.Code.PROTOCOL_OK;
}
if (stateContext != TCPFSM.CLOSED) {
// rearm
StartPersistTimer();
}
return NetStatus.Code.PROTOCOL_OK;
}
private TcpSegment GetNextPacket()
{
return GetNextPacket(false);
}
private TcpSegment GetNextPacket(bool ignoreReceiverWindow)
{
if (outQueue.Count == 0) {
return null;
}
lock (outQueue.SyncRoot) {
// recheck after lock
if (outQueue.Count == 0) {
return null;
}
if (((TcpSegment!)outQueue[0]).retries > 0) {
// Special case: the head packet is a retransmission. No special work.
return (TcpSegment)base.GetPacket(outQueue, false, 0); // non blocking
} else {
// The head packet is *not* a retransmission. Make sure we
// have room to to move it to the retransmission queue.
if (retransmitQ.Count < retransmitQ.Capacity)
{
TcpSegment nextSegment = (TcpSegment)outQueue[0];
assert nextSegment != null;
uint segSize = nextSegment.GetSegmentLength();
if ((!ignoreReceiverWindow) && (segSize > FreeRemoteWindowBytes())) {
return null; // Don't overrun the receiver
}
// Call the base class to dequeue the packet in an orderly way
TcpSegment! seg = (TcpSegment!)base.GetPacket(outQueue, false, 0); // non blocking
assert seg == nextSegment;
if (!seg.isAck) {
// save it for RTT adjustments
seg.sendTime = (ulong)DateTime.UtcNow.Ticks;
retransmitQ.Add(seg);
// Make sure the retransmitQ stays sorted
if (retransmitQ.Count > 1) {
TcpSegment! previousTail = (TcpSegment!)retransmitQ[retransmitQ.Count - 2];
assert TCPFSM.TCPSeqGreater(seg.seq, previousTail.seq);
}
// Kick off the retransmit timer if we are first
if (retransTimer == null) {
RestartRetransTimer();
}
} else if (segSize == 0) {
segSize = 1; // ACKs take up one segment number
}
// Advance the NXT counter since we're about to put this
// segment on the wire
sessionTCB.SND.NXT = seg.seq + segSize;
return seg;
}
}
}
return null;
}
// NB: call *after* removing or adding items to the retransmitQ
internal void RestartRetransTimer()
{
DestroyTimer(ref retransTimer);
if (retransmitQ.Count > 0) {
ulong nowTime = (ulong)DateTime.UtcNow.Ticks;
// TODO: We should use a dynamically-calculated timeout interval
ulong t = nowTime + (ulong)TimeSpan.FromMilliseconds(retransInterval).Ticks;
retransTimer = Core.Instance().TheDispatcher.AddCallback(
new Dispatcher.Callback(OnRetransmitTimeout), null, t);
} // else all data has been acknowledged
}
internal void FlushRetransmissions()
{
DestroyTimer(ref retransTimer);
retransmitQ.Clear();
}
private void UpdateRTT(uint measurement)
{
const uint MaxCredibleMeasurement = 10000; // 10s in ms
uint newInterval;
if (measurement > MaxCredibleMeasurement) {
// Garbage
return;
}
if (retransInterval == InitialRetransInterval) {
// We have never set the session RTT.
srtt = measurement;
rttvar = srtt / 2;
newInterval = measurement * 2;
} else {
// Second or subsequent measurement. Per RFC 2988
uint abs_srtt_meas = srtt > measurement ? srtt - measurement : measurement - srtt;
rttvar = ((rttvar * 3) / 4) + (abs_srtt_meas / 4);
srtt = ((7 * srtt) / 8) + (measurement / 8);
newInterval = srtt + (4 * rttvar);
}
this.retransInterval = newInterval < MinimumRetransInterval ? MinimumRetransInterval : newInterval;
}
// Process a remote acknowledgement of data up to the given sequence number
internal void ACKThrough(uint seqNum)
{
ulong nowTicks = (ulong)DateTime.UtcNow.Ticks;
int removed = 0;
// Pop packets off the retransmitQ through the acked seqnum
while (retransmitQ.Count > 0) {
TcpSegment! headSeg = (TcpSegment!)retransmitQ[0];
if (retransmitQ.Count > 1) {
TcpSegment! nextSeg = (TcpSegment!)retransmitQ[1];
// Make sure the queue is in order
assert TCPFSM.TCPSeqLess(headSeg.seq, nextSeg.seq);
}
// If the head segment is fully acknowledged, pop it
if (TCPFSM.TCPSeqLEQ(headSeg.seq + headSeg.GetSegmentLength(), seqNum)) {
retransmitQ.RemoveAt(0);
removed++;
// Use this ACK for RTT calculations.
// Ignore ACKs for retransmitted data.
if (headSeg.retries == 0) {
UpdateRTT(headSeg.GetRTT(nowTicks));
}
} else {
// Out of packets to pop
break;
}
}
if (removed > 0) {
RestartRetransTimer();
}
// else this ACK didn't acknowledge any new data
// INVARIANT: the head of the retransmit queue must contain
// the first unacked seqnum.
if (retransmitQ.Count > 0) {
TcpSegment! headSeg = (TcpSegment!)retransmitQ[0];
bool hasFirstUnacked =
TCPFSM.TCPSeqLEQ(headSeg.seq, sessionTCB.SND.UNA) &&
TCPFSM.TCPSeqGEQ(headSeg.seq + headSeg.GetSegmentLength(), sessionTCB.SND.UNA);
assert hasFirstUnacked;
}
// We may have paused transmission so as to not overrun the receiver.
// Poke the netstack core to be sure we get serviced if we have data
// to send.
if ((FreeRemoteWindowBytes() > 0) &&
(outQueue.Count > 0)) {
Core.Instance().SignalOutboundPackets();
}
}
// we need to override GetPacket. We transmit the packet
// and put it in the retransmit queue until we get an ack.
// (we only do it for data segments including SYN which counts for one)
// if a timer expired before ack, we retransmit it until we give up.
override internal NetPacket GetPacket(ArrayList! q, bool toBlock, int timeout)
{
// We only concern ourselves with the remote host's receive window in states
// where we are transmitting
bool shouldRespectRemoteWindow =
(stateContext != TCPFSM.CLOSED) &&
(stateContext != TCPFSM.LISTEN) &&
(stateContext != TCPFSM.SYN_SENT) &&
(stateContext != TCPFSM.SYN_RECVD);
// There needs to be at least one packet-worth of space in the send-window for us
// to be sure we won't overrun the remote host.
if (shouldRespectRemoteWindow && (sessionTCB.SND.WND == 0)) {
// Make sure the persist timer is ticking
if (!InPersistState()) {
StartPersistTimer();
} // else already in the persist state
} else {
StopPersistTimer();
return GetNextPacket();
}
return null;
}
private void PriorityEnqueuePacket(ArrayList! queue, NetPacket! packet)
{
lock (queue.SyncRoot) {
// This may increase the capacity of the queue. We probably want
// watermark limit for user additions to the queue and not worry about
// internal additions to the queue.
queue.Insert(0, packet);
}
// Poke the core to service our queue
Core.Instance().SignalOutboundPackets();
}
// Handler for TCP timeouts
internal NetStatus OnRetransmitTimeout(Dispatcher.CallbackArgs timeoutArg)
{
if (!InPersistState()) {
// Retransmit the oldest unacknowledged packet
assert retransmitQ.Count > 0;
TcpSegment! oldest = (TcpSegment!)retransmitQ[0];
++oldest.retries;
if (oldest.retries >= MaxRetries) {
// Give up
Abort(TcpError.Timeout);
return NetStatus.Code.PROTOCOL_OK;
}
// INVARIANT: the head of the retransmit queue must contain
// the first unacked seqnum
if (retransmitQ.Count > 0) {
// TODO make this an assert
TcpSegment! headSeg = (TcpSegment!)retransmitQ[0];
bool hasFirstUnacked =
TCPFSM.TCPSeqLEQ(headSeg.seq, sessionTCB.SND.UNA) &&
TCPFSM.TCPSeqGreater(headSeg.seq + headSeg.GetSegmentLength(), sessionTCB.SND.UNA);
assert hasFirstUnacked;
}
PriorityEnqueuePacket(outQueue, oldest);
} else {
// we're in the persist state and retransmissions are suspended.
}
// Back off!
retransInterval = retransInterval * 2;
RestartRetransTimer();
return NetStatus.Code.PROTOCOL_OK;
}
internal override bool IsSessionValidForUserRead()
{
return isValidForRead;
}
internal override bool IsSessionValidForUserWrite()
{
return isValidForWrite;
}
// data can be still available on a non-valid session
public bool IsDataAvailable()
{
return (inQueue.Count>0);
}
// Callback type for packetizing data
private delegate void CopyDataDelegate(byte[]! intoArray, int sourceOffset,
int destOffset, int length);
// Helper delegate for dealing with GC data
private class GCDataCopier
{
private byte[] gcData;
public GCDataCopier(byte[] gcData)
{
this.gcData = gcData;
}
public void CopyData(byte[]! intoArray, int sourceOffset,
int destOffset, int length)
{
if (sourceOffset + length > gcData.Length) {
throw new Exception("Overrun of GC data helper");
}
Array.Copy(gcData, sourceOffset, intoArray,
destOffset, length);
}
}
// Helper class for dealing with ExHeap data
private class ExHeapDataCopier
{
VContainer<byte> exHeapData;
public ExHeapDataCopier([Claims] byte[]! in ExHeap exHeapData)
{
this.exHeapData = new VContainer<byte>(exHeapData);
}
public void CopyData(byte[]! intoArray, int sourceOffset,
int destOffset, int length)
{
if (this.exHeapData == null) {
throw new Exception("ExHeapDataCopier used after Destroy()");
}
byte[]! in ExHeap exHeapData = this.exHeapData.Acquire();
try {
if (sourceOffset + length > exHeapData.Length) {
throw new Exception("Overrun of ExHeap data helper");
}
Bitter.ToByteArray(exHeapData, sourceOffset, length,
intoArray, destOffset);
}
finally {
this.exHeapData.Release(exHeapData);
}
}
public void Destroy()
{
// Explicitly discard our ExHeap object
byte[]! in ExHeap data = this.exHeapData.Acquire();
delete data;
this.exHeapData = null;
}
}
public int WriteData([Claims] byte[]! in ExHeap data)
{
int dataLength = data.Length;
ExHeapDataCopier helper = new ExHeapDataCopier(data);
int retval = InternalWrite(new CopyDataDelegate(helper.CopyData), dataLength);
// Make sure the ExHeap block gets thrown away immediately to
// reduce pressure on the finalizer thread
helper.Destroy();
return retval;
}
override public int WriteData(byte[]! data)
{
GCDataCopier helper = new GCDataCopier(data);
return InternalWrite(new CopyDataDelegate(helper.CopyData), data.Length);
}
// here we create the segments from the data
// The user is blocked until we have more room.
// we return -1 if we can't write (session is not established)
// TBC: according to the spec, when TCP is about to send it, if there is not
// enough space at the peer receive buffer it can split it
// to several smaller segments.
private int InternalWrite(CopyDataDelegate! dataCopier, int dataSize)
{
if (!ValidForWrite)
{
return -1;
}
// This is the number of full packets to send
uint mssCount = (uint)(dataSize / TcpFormat.TCP_MSS);
// This is the size of the last (non-full) packet.
uint mssResidue = (uint)(dataSize % TcpFormat.TCP_MSS);
int readIndex = 0;
uint segSequence = sessionTCB.SND.NextSeq;
const int baseFrameSize = EthernetFormat.Size + IPFormat.Size + TcpFormat.Size;
while (mssCount!=0)
{
// create a TCP segment without options
// handle the data first
byte[] pktData = new byte[baseFrameSize + TcpFormat.TCP_MSS];
dataCopier(pktData, readIndex, baseFrameSize, TcpFormat.TCP_MSS);
TcpFormat.WriteTcpSegment(pktData,this.LocalPort,
this.RemotePort, sessionTCB.RCV.NXT,
segSequence, TcpFormat.TCP_MSS,
this.LocalAddress,
this.RemoteAddress,
TcpFormat.TCP_MSS,
true,false,false,false,false);
TcpSegment seg = new TcpSegment(pktData, this,
segSequence, false);
// the next segment sequence
segSequence += TcpFormat.TCP_MSS;
readIndex += TcpFormat.TCP_MSS;
base.PutPacket(outQueue, seg, true);
mssCount--;
}
if (mssResidue!=0)
{
byte[] pktData = new byte[baseFrameSize + mssResidue];
dataCopier(pktData, readIndex, baseFrameSize, (int)mssResidue);
TcpFormat.WriteTcpSegment(pktData,
this.LocalPort,
this.RemotePort,
sessionTCB.RCV.NXT,
segSequence, TcpFormat.TCP_MSS,
this.LocalAddress,
this.RemoteAddress,
(ushort)mssResidue,
true,false,false,false,false);
TcpSegment seg = new TcpSegment(pktData, this,
segSequence, false);
segSequence += mssResidue;
base.PutPacket(outQueue,seg,true);
}
sessionTCB.SND.NextSeq = segSequence;
// since we always send it all...
return dataSize;
}
public bool BindLocalEndPoint(IPv4 address, ushort port)
{
haveBound = true;
SetRemoteEndPoint(IPv4.Broadcast, 0);
return SetLocalEndPoint(address, port);
}
// the method is used to make a session active (i.e., active open)
// TBC: manage local ports automatically
// we currently more restrictive regarding user
// interaction (can't change passive to active etc)
public bool Connect(IPv4 dstIP, ushort dstPort, out TcpError error)
{
DebugPrint("Connect: {0:x8}/{1}", dstIP, dstPort);
if (stateContext!=TCPFSM.CLOSED) {
DebugPrint("Connect: Failed FSM Closed", dstIP, dstPort);
error = TcpError.AlreadyConnected;
return false;
}
// init the session's parameters
SetRemoteEndPoint(dstIP, dstPort);
// Set the local endpoint to "don't care" if the user
// hasn't called BindLocalEndPoint() previously
if (!haveBound)
{
SetLocalEndPoint(IPv4.Any, 0);
}
sessionTCB.RCV = new TcpSession.TCB.RCVValues();
sessionTCB.SND = new TcpSession.TCB.SNDValues();
DrainQueue(outQueue);
DrainQueue(inQueue);
retransmitQ.Clear();
// change this session state to SYNSENT.
// a SYN message will be sent to the destination
ChangeState(TCPFSM.SYN_SENT);
// provide a default error
this.connectError = TcpError.Unknown;
// block the user until the session is ready
setupCompleteEvent.WaitOne();
DebugPrint("Connect: SetupCompleteEvent signalled");
if (stateContext != TCPFSM.ESTABLISHED)
{
// The connect failed.
error = this.connectError;
DebugPrint("Connect: failed {0}", error);
return false;
}
else
{
// The connection is up and running properly.
error = TcpError.Unknown;
DebugPrint("Connect: success");
return true;
}
}
private NetStatus OnShutdownTimedout(Dispatcher.CallbackArgs args)
{
// No more Mr. Nice Guy
Abort(TcpError.Timeout);
return NetStatus.Code.PROTOCOL_OK;
}
// close the session
override public bool Close()
{
// Signal that we're done sending; this will start the polite
// shutdown process.
DoneSending();
// Start a timer to make sure we don't wait for the shutdown
// forever. TODO: we should use a value passed in by the
// caller for the timeout rather than hard-coding it.
Dispatcher.Callback fun = new Dispatcher.Callback(OnShutdownTimedout);
ulong expiryTime = (ulong)DateTime.UtcNow.Ticks +
(PoliteShutdownTimeout * DateTime.TicksPerSecond);
shutdownTimer = Core.Instance().TheDispatcher.AddCallback(fun, null, expiryTime);
// Wait while we complete the shutdown, drain the outbound
// queue, etc.
closedEvent.WaitOne();
// Quash the timer in case it's still ticking
if (Core.Instance().TheDispatcher.RemoveTimeoutCallback(shutdownTimer)) {
Tracing.Log(Tracing.Debug, "Tcp quash timer successful.");
}
else {
Tracing.Log(Tracing.Debug, "Tcp quash timer already expired.");
}
shutdownTimer = null;
// After a Close() completes, pending data isn't available anymore!
DrainQueue(inQueue);
return true;
}
// hard-shutdown
override public bool Abort()
{
return Abort(TcpError.Unknown);
}
public bool Abort(TcpError error)
{
// Abort our connection with a RST segment
Terminate(TCPFSM.CreateResetSegment(this, true), error);
return true;
}
private void Terminate(TcpSegment finalPacket, TcpError error)
{
StopPersistTimer();
if (stateContext == TCPFSM.CLOSED)
{
// Signal anyone waiting, for good measure.
setupCompleteEvent.Set();
}
else
{
// This will set the setup-complete event as a side effect
TCPFSM.HandleTerminateSession(this, finalPacket, error);
}
}
// passively open a session
public bool Listen(int backlog)
{
if (stateContext != TCPFSM.CLOSED)
return false;
// User must have previously bound
if (!haveBound)
{
return false;
}
maxAcceptedSessions = backlog;
ChangeState(TCPFSM.LISTEN);
return true;
}
// start accepting new clients
// will block until a new client connection is established
public TcpSession Accept()
{
if (stateContext != TCPFSM.LISTEN)
return null;
TcpSession tcpSession=null;
// block the user until a session is available
lock (acceptSessionMonitor)
{
while (acceptedSessions.Count==0)
Monitor.Wait(acceptSessionMonitor);
tcpSession = (TcpSession)acceptedSessions[0];
acceptedSessions.RemoveAt(0);
}
return tcpSession;
}
// Returns false if our queue is full
internal bool AddAcceptedSession(TcpSession newSession)
{
lock (acceptSessionMonitor)
{
if (AcceptQueueIsFull())
{
return false;
}
else
{
acceptedSessions.Add(newSession);
Monitor.PulseAll(acceptSessionMonitor);
return true;
}
}
}
// Indicate whether there are queued sessions waiting to be Accept()ed
public int GetNumWaitingListenSessions()
{
lock (acceptSessionMonitor)
{
return acceptedSessions.Count;
}
}
// Determine whether the accept queue is full or not
internal bool AcceptQueueIsFull()
{
return GetNumWaitingListenSessions() >= maxAcceptedSessions;
}
// Indicate that we're done sending
public void DoneSending()
{
if (!isValidForWrite)
{
// Nothing to do
return;
}
isValidForWrite = false;
if (stateContext == TCPFSM.ESTABLISHED)
{
// Transition to FIN_WAIT1 since we are the first
// side to close
TCPFSM.SendFin(this, true); // can block
ChangeState(TCPFSM.FIN_WAIT1);
}
else if (stateContext == TCPFSM.CLOSE_WAIT)
{
// The other side closed first; transition to
// LAST_ACK instead
TCPFSM.SendFin(this, true); // blocks
ChangeState(TCPFSM.LAST_ACK);
}
else
{
// We're in some transitory setup or teardown
// state; just abort the connection.
Abort(TcpError.Closed);
}
}
// Indicate that we're done receiving
public void DoneReceiving()
{
ValidForRead = false;
}
}
// a TCP segment
public class TcpSegment : NetPacket
{
// segment identifier
internal uint seq; // the segment sequence number
internal uint retries; // number of retransmit retries
internal bool isAck; // is it an ack segment (no retrans for ack!)
internal ulong sendTime; // used to dynamically adjust the RTT
// create a TcpSegment, add room for lower level protocols
public TcpSegment(byte[]! buffer) : base(buffer)
{
seq=0;
retries=0;
isAck=false;
sendTime=0;
}
// the isAck indicate that this is an ack segment
// (we never ack an ack segments without data)
[NotDelayed]
public TcpSegment(byte[]! buffer, TcpSession! owner, uint seqNum, bool isAck) : base(buffer)
{
seq=seqNum;
retries=0;
this.isAck=isAck;
sendTime=0;
this.SessionContext = owner;
}
public TcpSession owner
{
// owner was a field in TcpSegment, but it mirrors field
// in NetPacket and one we now use for unblocking after
// the ARP response comes back.
get { return this.SessionContext as TcpSession; }
set { this.SessionContext = value; }
}
// return the TCP data segment size
public uint GetSegmentLength()
{
return ((uint)(base.Length - EthernetFormat.Size - IPFormat.Size - TcpFormat.Size));
}
public uint GetRTT(ulong receiveTime)
{
ulong deltaTime = (receiveTime>sendTime ? receiveTime-sendTime : 0 );
return (uint)(TimeSpan.FromTicks((long)deltaTime)).Milliseconds;
}
}
}