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

1332 lines
49 KiB
C#
Raw Normal View History

2008-11-17 18:29:00 -05:00
// ----------------------------------------------------------------------------
2008-03-05 09:52:00 -05:00
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
2008-11-17 18:29:00 -05:00
// ----------------------------------------------------------------------------
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
///
// Microsoft Research, Cambridge
//
2008-03-05 09:52:00 -05:00
// #define DEBUG_TCP
using System;
using System.Collections;
using System.Diagnostics;
2008-11-17 18:29:00 -05:00
using System.Net.IP;
using System.Runtime.CompilerServices;
using System.Threading;
2008-03-05 09:52:00 -05:00
#if !SINGULARITY
using System.Net;
#endif
2008-11-17 18:29:00 -05:00
using NetStack.Common;
2008-03-05 09:52:00 -05:00
using NetStack.Protocols;
using NetStack.Contracts;
2008-11-17 18:29:00 -05:00
2008-03-05 09:52:00 -05:00
using Microsoft.Contracts;
using Microsoft.SingSharp;
using Microsoft.Singularity;
using Microsoft.Singularity.Channels;
2008-11-17 18:29:00 -05:00
using Microsoft.Singularity.NetStack.Events;
using Eventing = Microsoft.Singularity.Eventing;
2008-03-05 09:52:00 -05:00
namespace NetStack.Runtime
{
2008-11-17 18:29:00 -05:00
/// <summary>
/// The 'TCP' specialization of the Session class.
/// </summary>
public class TcpSession : Session // BUGBUG: REMOVE locks after having the incoming packets send to the TcpSession thread. // TODO
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
/// <summary>
/// Kinds of Timeout that are used in TCP.
/// </summary>
public enum TcpTimeoutType {
Unknown = 0,
Connect,
Persist,
Shutdown,
Retransmit,
};
/// <summary>
/// Names corresponding to Enums, above (reflection missing).
/// </summary>
internal static readonly string[] TcpTimeoutTypeNames = new string[] {
"Unknown",
"Connect",
"Persist",
"Shutdown",
"Retransmit",
};
2008-03-05 09:52:00 -05:00
// 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
2008-11-17 18:29:00 -05:00
private const int ActiveConnectTimeout = 15;
2008-03-05 09:52:00 -05:00
// 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;
2008-11-17 18:29:00 -05:00
// 3s in ms, Per RFC 2988
private const uint InitialRetransInterval = 3000u;
2008-03-05 09:52:00 -05:00
// 200ms, more aggressive than RFC "SHOULD" of 1s
2008-11-17 18:29:00 -05:00
private const uint MinimumRetransInterval = 200u;
2008-03-05 09:52:00 -05:00
// Per-session retransmission state
2008-11-17 18:29:00 -05:00
private uint retransInterval = InitialRetransInterval;
private uint srtt = InitialRetransInterval;
private uint rttvar = 0u;
/// <summary>Object used to Lock the entire TcpSession.</summary>
/// <remarks>"this" is not used to as it is visible.</remarks>
private object lockHolder = new object();
// States of the session, current and former.
internal TcpStateEnum oldStateEnum; // our previous (FSM) state's enumeration.
internal TcpState currentState; // our current (FSM) state
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Error, if any, from the last Connect request
internal TcpError connectError;
2008-03-05 09:52:00 -05:00
// 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;
2008-11-17 18:29:00 -05:00
// TCP retransmission timer
private RJBlack.Timer retransTimer;
2008-03-05 09:52:00 -05:00
// Setup / teardown timers
2008-11-17 18:29:00 -05:00
private RJBlack.Timer connectTimer;
private RJBlack.Timer shutdownTimer;
2008-03-05 09:52:00 -05:00
// Information on the "persist" state (for when the remote host
// has closed its receive window)
2008-11-17 18:29:00 -05:00
private RJBlack.Timer persistTimer;
2008-03-05 09:52:00 -05:00
// 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;
2008-11-17 18:29:00 -05:00
public bool ValidForRead
{
get { return isValidForRead; }
set { isValidForRead = value; }
}
public bool ValidForWrite
{
get { return isValidForWrite;}
set {isValidForWrite=value;}
}
2008-03-05 09:52:00 -05:00
// TCB
public struct TCB
{
public SNDValues SND;
public RCVValues RCV;
// send parameters
public struct SNDValues
{
2008-11-17 18:29:00 -05:00
public uint UNA; // UNAcknowledged send sequence number
2008-03-05 09:52:00 -05:00
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)
2008-11-17 18:29:00 -05:00
: base(p, TxQSize, RcvQSize)
2008-03-05 09:52:00 -05:00
{
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;
2008-11-17 18:29:00 -05:00
// Assign the undifferentiated state (the parent of the
// specialized states) and then change the state to CLOSED.
this.oldStateEnum = TcpStateEnum.Undefined;
this.currentState = TcpState.InstanceOfUndefined();
2008-03-05 09:52:00 -05:00
ChangeState(TCPFSM.CLOSED);
}
2008-11-17 18:29:00 -05:00
public new void ReInitialize(IProtocol! protocol)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
base.ReInitialize(protocol);
2008-03-05 09:52:00 -05:00
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
2008-11-17 18:29:00 -05:00
this.oldStateEnum = TcpStateEnum.Undefined;
2008-03-05 09:52:00 -05:00
if (!IsClosed) {
ChangeState(TCPFSM.CLOSED);
}
2008-11-17 18:29:00 -05:00
DestroyConnectTimer();
DestroyShutdownTimer();
DestroyPersistTimer();
2008-03-05 09:52:00 -05:00
retransInterval = InitialRetransInterval;
}
public bool IsClosed
{
2008-11-17 18:29:00 -05:00
get { return this.currentState == TCPFSM.CLOSED; }
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
private void DestroyTimer(ref RJBlack.Timer timer, string timerName)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
if (timer == null) {
DebugPrint("TCP: Ses{0,3} ({1}) No {2} Timer to Destroy.",
this.Uid, this.currentState.StateName, timerName);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
if (timer != null) { // BUGBUG: Lock done on "timers" in RemoveTimeoutCallback. Is there a nulling race? // TODO
bool removeWorked =
Core.Instance().TheDispatcher.RemoveTimeoutCallback(timer);
DebugPrint("TCP: Ses{0,3} ({1}) Destroy {2} Timer {3}.",
this.Uid,
this.currentState.StateName,
timerName,
(removeWorked) ? "Worked" : "Failed");
timer = null;
}
}
internal void DestroyConnectTimer()
{
DestroyTimer(ref connectTimer, "Connect");
}
internal void DestroyPersistTimer()
{
DestroyTimer(ref persistTimer, "Persist");
}
internal void DestroyShutdownTimer()
{
DestroyTimer(ref shutdownTimer, "Shutdown");
}
internal void DestroyRetransmitTimer()
{
DestroyTimer(ref retransTimer, "Retransmit");
2008-03-05 09:52:00 -05:00
}
[ Conditional("DEBUG_TCP") ]
private static void DebugPrint(string format, params object [] args)
{
Core.Log(format, args);
}
2008-11-17 18:29:00 -05:00
public void LogStateChangeContractCall(TcpSessionEventsSource.TcpSessionContractEntrypoints entrypoint)
{
TcpSessionEventsSource.EventLog.LogSessionStateChangeContractCall(
Uid,
currentState.StateEnum,
entrypoint);
}
public void LogDataTransferContractCall(TcpSessionEventsSource.TcpSessionContractEntrypoints entrypoint)
{
TcpSessionEventsSource.EventLog.LogSessionDataTransferContractCall(
Uid,
currentState.StateEnum,
entrypoint);
}
public void LogQueryContractCall(TcpSessionEventsSource.TcpSessionContractEntrypoints entrypoint)
{
TcpSessionEventsSource.EventLog.LogSessionQueryContractCall(
Uid,
currentState.StateEnum,
entrypoint);
}
public void LogInfoContractCall(TcpSessionEventsSource.TcpSessionContractEntrypoints entrypoint)
{
TcpSessionEventsSource.EventLog.LogSessionInfoContractCall(
Uid,
currentState.StateEnum,
entrypoint);
}
2008-03-05 09:52:00 -05:00
internal void StartConnectTimer()
{
ulong timeout;
2008-11-17 18:29:00 -05:00
if (passiveSession != null) {
2008-03-05 09:52:00 -05:00
timeout = PassiveConnectTimeout;
}
2008-11-17 18:29:00 -05:00
else {
timeout = ActiveConnectTimeout;
2008-03-05 09:52:00 -05:00
}
Dispatcher.Callback fun = new Dispatcher.Callback(OnConnectTimeout);
ulong expiryTime = (ulong)DateTime.UtcNow.Ticks + (timeout * DateTime.TicksPerSecond);
2008-11-17 18:29:00 -05:00
connectTimer = Core.Instance().TheDispatcher.AddCallback(fun, null, expiryTime);
2008-03-05 09:52:00 -05:00
}
internal NetStatus OnConnectTimeout(Dispatcher.CallbackArgs args)
{
// We failed to become established in time. Bail out.
2008-11-17 18:29:00 -05:00
TcpSessionEventsSource.EventLog.LogTimeout(
Uid,
currentState.StateEnum,
TcpTimeoutType.Connect);
2008-03-05 09:52:00 -05:00
Terminate(null, TcpError.Timeout);
return NetStatus.Code.PROTOCOL_OK;
}
2008-11-17 18:29:00 -05:00
internal void InitializeServerSession(uint recvSequence, uint window)
{
this.InitializeSession(recvSequence,
(uint)DateTime.UtcNow.Ticks,
window);
}
internal void InitializeSession(uint recvSequence,
uint sendSequence,
uint window)
{
sessionTCB.RCV.IRS = recvSequence;
sessionTCB.RCV.NXT = recvSequence + 1;
sessionTCB.SND.ISS = sendSequence;
sessionTCB.SND.UNA = sendSequence;
sessionTCB.SND.NXT = sendSequence + 1;
sessionTCB.SND.NextSeq = sendSequence + 1;
sessionTCB.SND.WND = window;
sessionTCB.RCV.WND = TcpFormat.TCP_MSS;
}
2008-03-05 09:52:00 -05:00
// change the state of this session
internal void ChangeState(TcpState! newState)
{
2008-11-17 18:29:00 -05:00
lock (this.lockHolder)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
assert(currentState != null);
// Log upcoming State Change
TcpSessionEventsSource.EventLog.LogSessionStateChange(
Uid,
currentState.StateEnum,
newState.StateEnum);
#if false
TcpSessionEvents.EventLog.TcpSessionStateChangeEvent(
(ushort)Uid,
(TcpSessionEvents.TcpSessionState)currentState.StateEnum,
(TcpSessionEvents.TcpSessionState)newState.StateEnum);
#endif
// Old State's Exit Processing
currentState.OnStateLeave(this, newState);
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Actual State Change
oldStateEnum = currentState.StateEnum;
currentState = newState;
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// New State's Entry Processing
currentState.OnStateEnter(this);
2008-03-05 09:52:00 -05:00
}
}
// 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,
2008-11-17 18:29:00 -05:00
NetPacket! packet,
object context)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
assert(context != null);
assert(currentState != null);
NetStatus returnCode;
// Log "Received Packet" Event.
TcpSessionEventsSource.EventLog.LogReceivedPacket(
Uid,
currentState.StateEnum,
((TcpFormat.TcpHeader)context).res2_flags,
(uint) packet.Available);
// Perform State-specific Packet Receive
// handling (and potential StateChange) under lock.
lock (this.lockHolder)
{
2008-03-05 09:52:00 -05:00
// process it in the current state's context
2008-11-17 18:29:00 -05:00
returnCode = currentState.OnPacketReceive(this, packet, context);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
return returnCode;
2008-03-05 09:52:00 -05:00
}
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()
{
2008-11-17 18:29:00 -05:00
if (persistTimer != null) {
DestroyPersistTimer();
}
2008-03-05 09:52:00 -05:00
}
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)
{
//
2008-11-17 18:29:00 -05:00
// NOTE: This is a hack. A proper TCP stack is supposed to
2008-03-05 09:52:00 -05:00
// 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.
//
2008-11-17 18:29:00 -05:00
TcpSessionEventsSource.EventLog.LogTimeout(
Uid,
currentState.StateEnum,
TcpTimeoutType.Persist);
2008-03-05 09:52:00 -05:00
TcpSegment seg = null;
if (retransmitQ.Count > 0) {
// Use the oldest unacknowledged packet to probe
seg = (TcpSegment)retransmitQ[0];
2008-11-17 18:29:00 -05:00
}
else {
2008-03-05 09:52:00 -05:00
// 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;
}
2008-11-17 18:29:00 -05:00
if (currentState != TCPFSM.CLOSED) {
2008-03-05 09:52:00 -05:00
// rearm
StartPersistTimer();
}
return NetStatus.Code.PROTOCOL_OK;
}
private TcpSegment GetNextPacket(bool ignoreReceiverWindow)
{
2008-11-17 18:29:00 -05:00
// No Packets if the OutQueue is Empty OR IF the Session is Closed.
// BUGBUG: Need some way to prevent Closed Sessions from retransmitting. // TODO
if ((outQueue.Count == 0) /*|| (currentState == TCPFSM.CLOSED)*/ ) {
2008-03-05 09:52:00 -05:00
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.
2008-11-17 18:29:00 -05:00
return (TcpSegment)base.GetPacket(outQueue, false, TimeSpan.Zero); // non blocking
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
}
else {
2008-03-05 09:52:00 -05:00
// The head packet is *not* a retransmission. Make sure we
// have room to to move it to the retransmission queue.
2008-11-17 18:29:00 -05:00
if (retransmitQ.Count < retransmitQ.Capacity) {
2008-03-05 09:52:00 -05:00
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
2008-11-17 18:29:00 -05:00
TcpSegment! seg = (TcpSegment!)base.GetPacket(outQueue, false, TimeSpan.Zero); // non blocking
2008-03-05 09:52:00 -05:00
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();
}
2008-11-17 18:29:00 -05:00
}
else if (segSize == 0) {
2008-03-05 09:52:00 -05:00
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()
{
2008-11-17 18:29:00 -05:00
DestroyRetransmitTimer();
2008-03-05 09:52:00 -05:00
if (retransmitQ.Count > 0) {
// TODO: We should use a dynamically-calculated timeout interval
2008-11-17 18:29:00 -05:00
ulong expirationTime =
(ulong)DateTime.UtcNow.Ticks +
(ulong)TimeSpan.FromMilliseconds(retransInterval).Ticks;
2008-03-05 09:52:00 -05:00
retransTimer = Core.Instance().TheDispatcher.AddCallback(
2008-11-17 18:29:00 -05:00
new Dispatcher.Callback(OnRetransmitTimeout),
null,
expirationTime);
#if false
if (currentState == TCPFSM.CLOSED) {
DestroyRetransmitTimer(); // TEMP FOR DEBUGGING - REMOVE // TODO
}
#endif
2008-03-05 09:52:00 -05:00
} // else all data has been acknowledged
}
internal void FlushRetransmissions()
{
2008-11-17 18:29:00 -05:00
DestroyRetransmitTimer();
2008-03-05 09:52:00 -05:00
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;
2008-11-17 18:29:00 -05:00
}
else {
2008-03-05 09:52:00 -05:00
// Second or subsequent measurement. Per RFC 2988
2008-11-17 18:29:00 -05:00
uint abs_srtt_meas = (srtt > measurement)
? srtt - measurement : measurement - srtt;
2008-03-05 09:52:00 -05:00
rttvar = ((rttvar * 3) / 4) + (abs_srtt_meas / 4);
srtt = ((7 * srtt) / 8) + (measurement / 8);
newInterval = srtt + (4 * rttvar);
}
2008-11-17 18:29:00 -05:00
this.retransInterval = (newInterval < MinimumRetransInterval)
? MinimumRetransInterval : newInterval;
2008-03-05 09:52:00 -05:00
}
// 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
2008-11-17 18:29:00 -05:00
TcpSegment tcpSegmentOrNull;
2008-03-05 09:52:00 -05:00
while (retransmitQ.Count > 0) {
TcpSegment! headSeg = (TcpSegment!)retransmitQ[0];
2008-11-17 18:29:00 -05:00
uint headNextBytesSequenceNumber =
headSeg.seq + headSeg.GetSegmentLength();
// If this head segment is still needed, break out of the loop.
if (TCPFSM.TCPSeqGreater(headNextBytesSequenceNumber, seqNum)) {
break;
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Make sure the queue is in exact order
2008-03-05 09:52:00 -05:00
if (retransmitQ.Count > 1) {
TcpSegment! nextSeg = (TcpSegment!)retransmitQ[1];
2008-11-17 18:29:00 -05:00
// assert TCPFSM.TCPSeqEQ(headNextBytesSequenceNumber,
// nextSeg.seq);
assert TCPFSM.TCPSeqLess(headSeg.seq, nextSeg.seq); // Old, Weak: Remove // TODO
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
// Otherwise, head segment isn't needed anymore. Remove it.
retransmitQ.RemoveAt(0);
removed++;
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Use this ACK for RTT calculations.
// Ignore ACKs for retransmitted data.
if (headSeg.retries == 0) {
UpdateRTT(headSeg.GetRTT(nowTicks));
2008-03-05 09:52:00 -05:00
}
}
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);
2008-11-17 18:29:00 -05:00
if (hasFirstUnacked == false) {
Core.Log("TCP: Ses{0,3} ({1}) RETRANSMIT QUEUE SEQUENCE " +
"NUMBER FAILURE; FirstSegmentStart,End = {2}, {3};"
+ "Unacknowledged = {4}.",
this.Uid,
this.currentState.StateName,
headSeg.seq,
headSeg.seq + headSeg.GetSegmentLength() - 1,
sessionTCB.SND.UNA);
}
//assert hasFirstUnacked; // BUGBUG: Must reactivate before Sprint End // TODO
2008-03-05 09:52:00 -05:00
}
// 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.
2008-11-17 18:29:00 -05:00
override internal NetPacket GetPacket(ArrayList! q, bool toBlock, TimeSpan timeout)
2008-03-05 09:52:00 -05:00
{
// We only concern ourselves with the remote host's receive window in states
// where we are transmitting
bool shouldRespectRemoteWindow =
2008-11-17 18:29:00 -05:00
(currentState != TCPFSM.CLOSED) &&
(currentState != TCPFSM.LISTEN) &&
(currentState != TCPFSM.SYN_SENT) &&
(currentState != TCPFSM.SYN_RECVD);
2008-03-05 09:52:00 -05:00
// 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
2008-11-17 18:29:00 -05:00
}
else {
2008-03-05 09:52:00 -05:00
StopPersistTimer();
2008-11-17 18:29:00 -05:00
return GetNextPacket(false);
2008-03-05 09:52:00 -05:00
}
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)
{
2008-11-17 18:29:00 -05:00
TcpSessionEventsSource.EventLog.LogTimeout(
Uid,
currentState.StateEnum,
TcpTimeoutType.Retransmit);
2008-03-05 09:52:00 -05:00
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) {
2008-11-17 18:29:00 -05:00
// TODO: make this an assert
2008-03-05 09:52:00 -05:00
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);
2008-11-17 18:29:00 -05:00
}
else {
2008-03-05 09:52:00 -05:00
// 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)
{
2008-11-17 18:29:00 -05:00
if (!ValidForWrite) {
2008-03-05 09:52:00 -05:00
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;
2008-11-17 18:29:00 -05:00
while (mssCount != 0) {
2008-03-05 09:52:00 -05:00
// 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);
2008-11-17 18:29:00 -05:00
// Send to Event Log.
TcpSessionEventsSource.EventLog.LogSendingPacket(
Uid,
(currentState == null) ? TcpStateEnum.Undefined
: currentState.StateEnum,
/*((TcpFormat.TcpHeader)context).res2_flags,*/ 0, // BUGBUG: Get Transmit Flags. // TODO
(uint) seg.GetSegmentLength());
2008-03-05 09:52:00 -05:00
// the next segment sequence
segSequence += TcpFormat.TCP_MSS;
readIndex += TcpFormat.TCP_MSS;
base.PutPacket(outQueue, seg, true);
mssCount--;
}
2008-11-17 18:29:00 -05:00
if (mssResidue != 0) {
2008-03-05 09:52:00 -05:00
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);
2008-11-17 18:29:00 -05:00
// Send to Event Log.
TcpSessionEventsSource.EventLog.LogSendingPacket(
Uid,
(currentState == null) ? TcpStateEnum.Undefined
: currentState.StateEnum,
/*((TcpFormat.TcpHeader)context).res2_flags,*/ 0, // BUGBUG: Get Transmit Flags. // TODO
(uint) seg.GetSegmentLength());
2008-03-05 09:52:00 -05:00
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)
{
2008-11-17 18:29:00 -05:00
return BindLocalEndPointInternal(address, port);
}
public bool BindLocalEndPointInternal(IPv4 address, ushort port)
{
lock (this.lockHolder) {
haveBound = true;
SetRemoteEndPoint(IPv4.Broadcast, 0);
return SetLocalEndPoint(address, port);
}
2008-03-05 09:52:00 -05:00
}
// the method is used to make a session active (i.e., active open)
// TBC: manage local ports automatically
2008-11-17 18:29:00 -05:00
// we're currently more restrictive regarding user
// interaction (can't change passive to active etc.)
2008-03-05 09:52:00 -05:00
public bool Connect(IPv4 dstIP, ushort dstPort, out TcpError error)
{
2008-11-17 18:29:00 -05:00
DebugPrint("TCP: Ses{0,3} ({1}) Connect: {2:x8}/{3}",
Uid, currentState.StateName, dstIP, dstPort);
if (currentState != TCPFSM.CLOSED) {
DebugPrint("TCP: Ses{0,3} ({1}) Connect: Failed because " +
"session already connected (not in '{2}' state)",
Uid,
currentState.StateName,
TCPFSM.CLOSED.StateName);
2008-03-05 09:52:00 -05:00
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
2008-11-17 18:29:00 -05:00
if (!haveBound) {
2008-03-05 09:52:00 -05:00
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);
2008-11-17 18:29:00 -05:00
2008-03-05 09:52:00 -05:00
// provide a default error
this.connectError = TcpError.Unknown;
// block the user until the session is ready
setupCompleteEvent.WaitOne();
2008-11-17 18:29:00 -05:00
setupCompleteEvent.Reset();
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Check Result; Optional Log; and Return true/false.
if (currentState != TCPFSM.ESTABLISHED) {
2008-03-05 09:52:00 -05:00
// The connect failed.
error = this.connectError;
2008-11-17 18:29:00 -05:00
DebugPrint("TCP: Ses{0,3} ({1}) Connect: SetupCompleteEvent " +
"signalled; Result = failed {2}",
Uid, currentState.StateName, error);
2008-03-05 09:52:00 -05:00
return false;
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// The connection is up and running properly.
error = TcpError.Unknown;
2008-11-17 18:29:00 -05:00
DebugPrint("TCP: Ses{0,3} ({1}) Connect: SetupCompleteEvent " +
"signalled; Result = success",
Uid, currentState.StateName);
2008-03-05 09:52:00 -05:00
return true;
}
}
private NetStatus OnShutdownTimedout(Dispatcher.CallbackArgs args)
{
2008-11-17 18:29:00 -05:00
TcpSessionEventsSource.EventLog.LogTimeout(
Uid,
currentState.StateEnum,
TcpTimeoutType.Shutdown);
2008-03-05 09:52:00 -05:00
// No more Mr. Nice Guy
Abort(TcpError.Timeout);
return NetStatus.Code.PROTOCOL_OK;
}
// close the session
override public bool Close()
{
2008-11-17 18:29:00 -05:00
// Signal that sending is complete; this will start the polite
// shutdown process. Changes state to FinWait1 or LastAck.
// Can block.
2008-03-05 09:52:00 -05:00
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);
2008-11-17 18:29:00 -05:00
shutdownTimer =
Core.Instance().TheDispatcher.AddCallback(fun, null, expiryTime);
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Wait while we complete the shutdown,
// drain the outbound queue, etc.
2008-03-05 09:52:00 -05:00
closedEvent.WaitOne();
2008-11-17 18:29:00 -05:00
closedEvent.Reset();
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Remove the Shutdown Timer.
DestroyShutdownTimer();
2008-03-05 09:52:00 -05:00
// 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();
2008-11-17 18:29:00 -05:00
if (currentState == TCPFSM.CLOSED) {
2008-03-05 09:52:00 -05:00
// Signal anyone waiting, for good measure.
setupCompleteEvent.Set();
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
// 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)
{
2008-11-17 18:29:00 -05:00
if (currentState != TCPFSM.CLOSED)
2008-03-05 09:52:00 -05:00
return false;
// User must have previously bound
2008-11-17 18:29:00 -05:00
if (!haveBound) {
2008-03-05 09:52:00 -05:00
return false;
}
maxAcceptedSessions = backlog;
ChangeState(TCPFSM.LISTEN);
return true;
}
2008-11-17 18:29:00 -05:00
/// <summary>
/// Block until a new connection is available,
/// accept it, and return a new TcpSession.
/// </summary>
/// <remarks>
/// The TcpSession (this) must be in the "Listen" state.
/// </remarks>
2008-03-05 09:52:00 -05:00
public TcpSession Accept()
{
2008-11-17 18:29:00 -05:00
if (currentState != TCPFSM.LISTEN)
2008-03-05 09:52:00 -05:00
return null;
2008-11-17 18:29:00 -05:00
TcpSession tcpSession = null;
2008-03-05 09:52:00 -05:00
// block the user until a session is available
2008-11-17 18:29:00 -05:00
lock (acceptSessionMonitor) {
while (acceptedSessions.Count == 0) {
2008-03-05 09:52:00 -05:00
Monitor.Wait(acceptSessionMonitor);
2008-11-17 18:29:00 -05:00
}
2008-03-05 09:52:00 -05:00
tcpSession = (TcpSession)acceptedSessions[0];
acceptedSessions.RemoveAt(0);
}
return tcpSession;
}
// Returns false if our queue is full
internal bool AddAcceptedSession(TcpSession newSession)
{
2008-11-17 18:29:00 -05:00
lock (acceptSessionMonitor) {
if (AcceptQueueIsFull()) {
2008-03-05 09:52:00 -05:00
return false;
}
2008-11-17 18:29:00 -05:00
else {
2008-03-05 09:52:00 -05:00
acceptedSessions.Add(newSession);
Monitor.PulseAll(acceptSessionMonitor);
return true;
}
}
}
// Indicate whether there are queued sessions waiting to be Accept()ed
public int GetNumWaitingListenSessions()
{
2008-11-17 18:29:00 -05:00
lock (acceptSessionMonitor) {
2008-03-05 09:52:00 -05:00
return acceptedSessions.Count;
}
}
// Determine whether the accept queue is full or not
internal bool AcceptQueueIsFull()
{
return GetNumWaitingListenSessions() >= maxAcceptedSessions;
}
2008-11-17 18:29:00 -05:00
/// <summary>
/// Client informs TcpSession that no more data will be sent.
/// </summary>
2008-03-05 09:52:00 -05:00
public void DoneSending()
{
2008-11-17 18:29:00 -05:00
lock (this.lockHolder) {
if (!isValidForWrite) {
// Nothing to do
return;
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
isValidForWrite = false;
switch (currentState.StateEnum) {
case TcpStateEnum.Established:
// This side first to Close, goto FinWait1
TCPFSM.SendFin(this, true); // can block
ChangeState(TCPFSM.FIN_WAIT1);
break;
case TcpStateEnum.CloseWait:
// Other side first to Close, goto LastAck
TCPFSM.SendFin(this, true); // can block
ChangeState(TCPFSM.LAST_ACK);
break;
default:
// We're in some transitory setup or teardown
// state; just abort the connection.
Abort(TcpError.Closed);
break;
}
2008-03-05 09:52:00 -05:00
}
}
// 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;
}
2008-11-17 18:29:00 -05:00
// the isAck indicates that this is an ack segment
// (we never ack an ack segment without data)
2008-03-05 09:52:00 -05:00
[NotDelayed]
2008-11-17 18:29:00 -05:00
public TcpSegment(byte[]! buffer, TcpSession! tcpSession, uint seqNum, bool isAck) : base(buffer)
2008-03-05 09:52:00 -05:00
{
seq=seqNum;
retries=0;
this.isAck=isAck;
sendTime=0;
2008-11-17 18:29:00 -05:00
this.SessionContext = tcpSession;
2008-03-05 09:52:00 -05:00
}
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;
}
}
}