1332 lines
49 KiB
C#
1332 lines
49 KiB
C#
// ----------------------------------------------------------------------------
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
///
|
|
// Microsoft Research, Cambridge
|
|
//
|
|
|
|
// #define DEBUG_TCP
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using System.Net.IP;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
|
|
#if !SINGULARITY
|
|
using System.Net;
|
|
#endif
|
|
|
|
using NetStack.Common;
|
|
using NetStack.Protocols;
|
|
using NetStack.Contracts;
|
|
|
|
using Microsoft.Contracts;
|
|
using Microsoft.SingSharp;
|
|
using Microsoft.Singularity;
|
|
using Microsoft.Singularity.Channels;
|
|
|
|
using Microsoft.Singularity.NetStack.Events;
|
|
|
|
using Eventing = Microsoft.Singularity.Eventing;
|
|
|
|
namespace NetStack.Runtime
|
|
{
|
|
/// <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
|
|
{
|
|
/// <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",
|
|
};
|
|
|
|
// 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 ActiveConnectTimeout = 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;
|
|
|
|
// 3s in ms, Per RFC 2988
|
|
private const uint InitialRetransInterval = 3000u;
|
|
|
|
// 200ms, more aggressive than RFC "SHOULD" of 1s
|
|
private const uint MinimumRetransInterval = 200u;
|
|
|
|
// Per-session retransmission state
|
|
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
|
|
|
|
// Error, if any, from the last Connect request
|
|
internal TcpError connectError;
|
|
|
|
// 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;
|
|
|
|
// TCP retransmission timer
|
|
private RJBlack.Timer retransTimer;
|
|
|
|
// Setup / teardown timers
|
|
private RJBlack.Timer connectTimer;
|
|
private RJBlack.Timer shutdownTimer;
|
|
|
|
// Information on the "persist" state (for when the remote host
|
|
// has closed its receive window)
|
|
private 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; // UNAcknowledged send sequence number
|
|
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;
|
|
|
|
// 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();
|
|
ChangeState(TCPFSM.CLOSED);
|
|
}
|
|
|
|
public new void ReInitialize(IProtocol! protocol)
|
|
{
|
|
base.ReInitialize(protocol);
|
|
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.oldStateEnum = TcpStateEnum.Undefined;
|
|
if (!IsClosed) {
|
|
ChangeState(TCPFSM.CLOSED);
|
|
}
|
|
|
|
DestroyConnectTimer();
|
|
DestroyShutdownTimer();
|
|
DestroyPersistTimer();
|
|
|
|
retransInterval = InitialRetransInterval;
|
|
}
|
|
|
|
public bool IsClosed
|
|
{
|
|
get { return this.currentState == TCPFSM.CLOSED; }
|
|
}
|
|
|
|
private void DestroyTimer(ref RJBlack.Timer timer, string timerName)
|
|
{
|
|
if (timer == null) {
|
|
DebugPrint("TCP: Ses{0,3} ({1}) No {2} Timer to Destroy.",
|
|
this.Uid, this.currentState.StateName, timerName);
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
[ Conditional("DEBUG_TCP") ]
|
|
private static void DebugPrint(string format, params object [] args)
|
|
{
|
|
Core.Log(format, args);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
internal void StartConnectTimer()
|
|
{
|
|
ulong timeout;
|
|
|
|
if (passiveSession != null) {
|
|
timeout = PassiveConnectTimeout;
|
|
}
|
|
else {
|
|
timeout = ActiveConnectTimeout;
|
|
}
|
|
|
|
Dispatcher.Callback fun = new Dispatcher.Callback(OnConnectTimeout);
|
|
ulong expiryTime = (ulong)DateTime.UtcNow.Ticks + (timeout * DateTime.TicksPerSecond);
|
|
connectTimer = Core.Instance().TheDispatcher.AddCallback(fun, null, expiryTime);
|
|
}
|
|
|
|
internal NetStatus OnConnectTimeout(Dispatcher.CallbackArgs args)
|
|
{
|
|
// We failed to become established in time. Bail out.
|
|
TcpSessionEventsSource.EventLog.LogTimeout(
|
|
Uid,
|
|
currentState.StateEnum,
|
|
TcpTimeoutType.Connect);
|
|
|
|
Terminate(null, TcpError.Timeout);
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// change the state of this session
|
|
internal void ChangeState(TcpState! newState)
|
|
{
|
|
lock (this.lockHolder)
|
|
{
|
|
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);
|
|
|
|
// Actual State Change
|
|
oldStateEnum = currentState.StateEnum;
|
|
currentState = newState;
|
|
|
|
// New State's Entry Processing
|
|
currentState.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! packet,
|
|
object context)
|
|
{
|
|
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)
|
|
{
|
|
// process it in the current state's context
|
|
returnCode = currentState.OnPacketReceive(this, packet, context);
|
|
}
|
|
|
|
return returnCode;
|
|
}
|
|
|
|
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()
|
|
{
|
|
if (persistTimer != null) {
|
|
DestroyPersistTimer();
|
|
}
|
|
}
|
|
|
|
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.
|
|
//
|
|
TcpSessionEventsSource.EventLog.LogTimeout(
|
|
Uid,
|
|
currentState.StateEnum,
|
|
TcpTimeoutType.Persist);
|
|
|
|
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 (currentState != TCPFSM.CLOSED) {
|
|
// rearm
|
|
StartPersistTimer();
|
|
}
|
|
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
|
|
private TcpSegment GetNextPacket(bool ignoreReceiverWindow)
|
|
{
|
|
// 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)*/ ) {
|
|
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, TimeSpan.Zero); // 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, TimeSpan.Zero); // 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()
|
|
{
|
|
DestroyRetransmitTimer();
|
|
|
|
if (retransmitQ.Count > 0) {
|
|
// TODO: We should use a dynamically-calculated timeout interval
|
|
ulong expirationTime =
|
|
(ulong)DateTime.UtcNow.Ticks +
|
|
(ulong)TimeSpan.FromMilliseconds(retransInterval).Ticks;
|
|
|
|
retransTimer = Core.Instance().TheDispatcher.AddCallback(
|
|
new Dispatcher.Callback(OnRetransmitTimeout),
|
|
null,
|
|
expirationTime);
|
|
#if false
|
|
if (currentState == TCPFSM.CLOSED) {
|
|
DestroyRetransmitTimer(); // TEMP FOR DEBUGGING - REMOVE // TODO
|
|
}
|
|
#endif
|
|
} // else all data has been acknowledged
|
|
}
|
|
|
|
internal void FlushRetransmissions()
|
|
{
|
|
DestroyRetransmitTimer();
|
|
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
|
|
TcpSegment tcpSegmentOrNull;
|
|
while (retransmitQ.Count > 0) {
|
|
TcpSegment! headSeg = (TcpSegment!)retransmitQ[0];
|
|
uint headNextBytesSequenceNumber =
|
|
headSeg.seq + headSeg.GetSegmentLength();
|
|
|
|
// If this head segment is still needed, break out of the loop.
|
|
if (TCPFSM.TCPSeqGreater(headNextBytesSequenceNumber, seqNum)) {
|
|
break;
|
|
}
|
|
|
|
// Make sure the queue is in exact order
|
|
if (retransmitQ.Count > 1) {
|
|
TcpSegment! nextSeg = (TcpSegment!)retransmitQ[1];
|
|
// assert TCPFSM.TCPSeqEQ(headNextBytesSequenceNumber,
|
|
// nextSeg.seq);
|
|
assert TCPFSM.TCPSeqLess(headSeg.seq, nextSeg.seq); // Old, Weak: Remove // TODO
|
|
}
|
|
|
|
// Otherwise, head segment isn't needed anymore. Remove it.
|
|
retransmitQ.RemoveAt(0);
|
|
removed++;
|
|
|
|
// Use this ACK for RTT calculations.
|
|
// Ignore ACKs for retransmitted data.
|
|
if (headSeg.retries == 0) {
|
|
UpdateRTT(headSeg.GetRTT(nowTicks));
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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
|
|
}
|
|
|
|
// 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, TimeSpan timeout)
|
|
{
|
|
// We only concern ourselves with the remote host's receive window in states
|
|
// where we are transmitting
|
|
bool shouldRespectRemoteWindow =
|
|
(currentState != TCPFSM.CLOSED) &&
|
|
(currentState != TCPFSM.LISTEN) &&
|
|
(currentState != TCPFSM.SYN_SENT) &&
|
|
(currentState != 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(false);
|
|
}
|
|
|
|
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)
|
|
{
|
|
TcpSessionEventsSource.EventLog.LogTimeout(
|
|
Uid,
|
|
currentState.StateEnum,
|
|
TcpTimeoutType.Retransmit);
|
|
|
|
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);
|
|
|
|
// 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());
|
|
|
|
// 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);
|
|
|
|
// 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());
|
|
|
|
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)
|
|
{
|
|
return BindLocalEndPointInternal(address, port);
|
|
}
|
|
|
|
public bool BindLocalEndPointInternal(IPv4 address, ushort port)
|
|
{
|
|
lock (this.lockHolder) {
|
|
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're currently more restrictive regarding user
|
|
// interaction (can't change passive to active etc.)
|
|
public bool Connect(IPv4 dstIP, ushort dstPort, out TcpError error)
|
|
{
|
|
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);
|
|
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();
|
|
setupCompleteEvent.Reset();
|
|
|
|
// Check Result; Optional Log; and Return true/false.
|
|
if (currentState != TCPFSM.ESTABLISHED) {
|
|
// The connect failed.
|
|
error = this.connectError;
|
|
DebugPrint("TCP: Ses{0,3} ({1}) Connect: SetupCompleteEvent " +
|
|
"signalled; Result = failed {2}",
|
|
Uid, currentState.StateName, error);
|
|
return false;
|
|
}
|
|
else {
|
|
// The connection is up and running properly.
|
|
error = TcpError.Unknown;
|
|
DebugPrint("TCP: Ses{0,3} ({1}) Connect: SetupCompleteEvent " +
|
|
"signalled; Result = success",
|
|
Uid, currentState.StateName);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private NetStatus OnShutdownTimedout(Dispatcher.CallbackArgs args)
|
|
{
|
|
TcpSessionEventsSource.EventLog.LogTimeout(
|
|
Uid,
|
|
currentState.StateEnum,
|
|
TcpTimeoutType.Shutdown);
|
|
|
|
// No more Mr. Nice Guy
|
|
Abort(TcpError.Timeout);
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
|
|
// close the session
|
|
override public bool Close()
|
|
{
|
|
// Signal that sending is complete; this will start the polite
|
|
// shutdown process. Changes state to FinWait1 or LastAck.
|
|
// Can block.
|
|
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();
|
|
closedEvent.Reset();
|
|
|
|
// Remove the Shutdown Timer.
|
|
DestroyShutdownTimer();
|
|
|
|
// 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 (currentState == 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 (currentState != TCPFSM.CLOSED)
|
|
return false;
|
|
|
|
// User must have previously bound
|
|
if (!haveBound) {
|
|
return false;
|
|
}
|
|
|
|
maxAcceptedSessions = backlog;
|
|
ChangeState(TCPFSM.LISTEN);
|
|
return true;
|
|
}
|
|
|
|
/// <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>
|
|
public TcpSession Accept()
|
|
{
|
|
if (currentState != 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Client informs TcpSession that no more data will be sent.
|
|
/// </summary>
|
|
public void DoneSending()
|
|
{
|
|
lock (this.lockHolder) {
|
|
if (!isValidForWrite) {
|
|
// Nothing to do
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 indicates that this is an ack segment
|
|
// (we never ack an ack segment without data)
|
|
[NotDelayed]
|
|
public TcpSegment(byte[]! buffer, TcpSession! tcpSession, uint seqNum, bool isAck) : base(buffer)
|
|
{
|
|
seq=seqNum;
|
|
retries=0;
|
|
this.isAck=isAck;
|
|
sendTime=0;
|
|
this.SessionContext = tcpSession;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|