572 lines
18 KiB
C#
572 lines
18 KiB
C#
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Microsoft Research, Cambridge
|
|
* author: Yaron Weinsberg, Richard Black
|
|
*/
|
|
|
|
using NetStack.Common;
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Threading;
|
|
using System.Collections;
|
|
|
|
using Microsoft.Singularity;
|
|
using Microsoft.Singularity.Channels;
|
|
|
|
#if !SINGULARITY
|
|
using System.Net;
|
|
#endif
|
|
|
|
using System.Net.IP;
|
|
using Drivers.Net;
|
|
|
|
namespace NetStack.Runtime
|
|
{
|
|
/**
|
|
* This is the base class for all sessions.
|
|
* The session represent an instance of a specific
|
|
* protocol and is usually embedded in a higher level
|
|
* library abstraction.
|
|
*/
|
|
public class Session : IDisposable
|
|
{
|
|
private static uint uids = 0;
|
|
private uint uid = uids++;
|
|
|
|
public uint Uid { get { return uid; } }
|
|
|
|
// the session identifiers
|
|
private IPv4 localAddress;
|
|
private ushort localPort;
|
|
private IPv4 remoteAddress;
|
|
private ushort remotePort;
|
|
|
|
private bool disposed;
|
|
|
|
private IProtocol! protocol; // the session's protocol
|
|
|
|
private Multiplexer mux;
|
|
|
|
// the session's queues
|
|
internal ArrayList! outQueue;
|
|
private int outQueueCapacity;
|
|
|
|
internal ArrayList! inQueue;
|
|
private int inQueueCapacity;
|
|
|
|
private bool outQueuePaused; // used to pause outbound queue while
|
|
// awaiting ARP response.
|
|
|
|
// get some info
|
|
public IPv4 RemoteAddress { get { return remoteAddress; } }
|
|
public ushort RemotePort { get { return remotePort; } }
|
|
|
|
public IPv4 LocalAddress { get { return localAddress; } }
|
|
public ushort LocalPort { get { return localPort; } }
|
|
|
|
public IProtocol! Protocol { get { return protocol; } }
|
|
|
|
public Session(IProtocol! protocol, int outQueueSize, int inQueueSize)
|
|
{
|
|
this.protocol = protocol;
|
|
this.outQueue = new ArrayList(outQueueSize);
|
|
this.inQueue = new ArrayList(inQueueSize);
|
|
this.outQueueCapacity = outQueueSize;
|
|
this.inQueueCapacity = inQueueSize;
|
|
this.outQueuePaused = false;
|
|
this.disposed = false;
|
|
base();
|
|
}
|
|
|
|
~Session()
|
|
{
|
|
if (!disposed)
|
|
Dispose();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (!disposed)
|
|
{
|
|
Core.Instance().DeregisterSession(protocol, this);
|
|
DrainQueue(inQueue);
|
|
DrainQueue(outQueue);
|
|
}
|
|
disposed = true;
|
|
}
|
|
|
|
public void ReInitialize(IProtocol! protocol)
|
|
{
|
|
Core.Instance().DeregisterSession(protocol, this);
|
|
|
|
this.protocol = protocol;
|
|
this.outQueuePaused = false;
|
|
this.disposed = false;
|
|
this.localAddress = IPv4.Zero;
|
|
this.remoteAddress = IPv4.Zero;
|
|
this.localPort = 0;
|
|
this.remotePort = 0;
|
|
this.disposed = false;
|
|
DrainQueue(this.outQueue);
|
|
DrainQueue(this.inQueue);
|
|
}
|
|
|
|
internal void DrainQueue(ArrayList! queue)
|
|
{
|
|
lock (queue.SyncRoot) {
|
|
if (queue == this.inQueue) {
|
|
while (queue.Count > 0) {
|
|
NetPacket! p = (NetPacket!)queue[0];
|
|
queue.Remove(0);
|
|
Core.Instance().TheDemux.TakeFreePacket(p);
|
|
}
|
|
}
|
|
else {
|
|
queue.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool OutQueuePaused
|
|
{
|
|
get { return outQueuePaused; }
|
|
set { outQueuePaused = value; }
|
|
}
|
|
|
|
[ Conditional("DEBUG_SESSION") ]
|
|
private static void DebugPrint(string format, params object [] args)
|
|
{
|
|
Core.Log("Session: ");
|
|
Core.Log(format, args);
|
|
}
|
|
|
|
// Multiplexer, if set packets are sent via multiplexer
|
|
public Multiplexer BoundMux
|
|
{
|
|
get { return mux; }
|
|
set { mux = value; }
|
|
}
|
|
|
|
internal void SetRemoteEndPoint(IPv4 remoteAddress, ushort remotePort)
|
|
{
|
|
this.remoteAddress = remoteAddress;
|
|
this.remotePort = remotePort;
|
|
}
|
|
|
|
public bool IsLocalAddress(IPv4 address)
|
|
{
|
|
IPModule ipModule = Core.Instance().GetProtocolByName("IP")
|
|
as IPModule;
|
|
if (ipModule == null)
|
|
{
|
|
return false;
|
|
}
|
|
return ipModule.HostConfiguration.IsLocalAddress(address);
|
|
}
|
|
|
|
internal bool SetLocalEndPoint(IPv4 address, ushort port)
|
|
{
|
|
if (address != IPv4.Zero && !IsLocalAddress(address))
|
|
{
|
|
// Invalid address
|
|
return false;
|
|
}
|
|
|
|
if (address == IPv4.Zero &&
|
|
remoteAddress != IPv4.Zero)
|
|
{
|
|
IPModule ipModule = (IPModule!)Core.Instance().GetProtocolByName("IP");
|
|
RouteEntry e = ipModule.HostConfiguration.RoutingTable.Lookup(remoteAddress);
|
|
|
|
if (e != null)
|
|
{
|
|
address = e.InterfaceAddress;
|
|
DebugPrint("local address now {0}\n", address);
|
|
}
|
|
else
|
|
{
|
|
DebugPrint("No route for {0}\n", remoteAddress);
|
|
}
|
|
}
|
|
|
|
localAddress = address;
|
|
if (this.LocalPort != port) {
|
|
Core.Instance().ChangeSessionLocalPort(this, port);
|
|
DebugPrint("Local port {0} {1}\n", port, localPort);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method should only be called by Core.ChangeSessionLocalPort
|
|
/// which co-ordinates port allocations.
|
|
/// </summary>
|
|
internal void SetLocalPort(ushort newPort)
|
|
{
|
|
this.localPort = newPort;
|
|
}
|
|
// Is this session valid for users to read from?
|
|
// This can become false for TCP sessions when the
|
|
// remote entity closes their half of the duplex
|
|
// connection
|
|
internal virtual bool IsSessionValidForUserRead()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Is this session valid for users to write to?
|
|
// This can become false for TCP sessions when
|
|
// the user has closed the local side of the duplex
|
|
// connection
|
|
internal virtual bool IsSessionValidForUserWrite()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// get a packet from a queue
|
|
// the session may be shared in user land (so we need the sync)
|
|
// since the core also use this version we pass a bool to differentiate between
|
|
// stack calls/user calls
|
|
// we let the user read packets if exist event though the session
|
|
// is closing (for instance a web server sends the data and disconnect)
|
|
protected NetPacket DequeuePacket(ArrayList! queue,
|
|
int timeoutMillis)
|
|
{
|
|
Debug.Assert(queue == inQueue || outQueuePaused == false);
|
|
|
|
NetPacket packet = null;
|
|
TimeSpan timeout;
|
|
|
|
// Negative millis means forever.
|
|
if (timeoutMillis < 0)
|
|
{
|
|
timeout = TimeSpan.Infinite;
|
|
}
|
|
else
|
|
{
|
|
timeout = TimeSpan.FromMilliseconds(timeoutMillis);
|
|
}
|
|
|
|
lock (queue.SyncRoot)
|
|
{
|
|
if ((timeoutMillis == 0) && queue.Count == 0)
|
|
{
|
|
// Don't sleep
|
|
return null;
|
|
}
|
|
|
|
while (queue.Count == 0)
|
|
{
|
|
if (Monitor.Wait(queue.SyncRoot, timeout) == false)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
packet = (NetPacket)queue[0];
|
|
queue.RemoveAt(0);
|
|
Monitor.PulseAll(queue.SyncRoot);
|
|
Debug.Assert(packet != null);
|
|
return packet;
|
|
}
|
|
}
|
|
|
|
private int GetQueueCapacity(ArrayList! queue)
|
|
{
|
|
// Work around for lack of abstract in queues (grrr!)
|
|
DebugStub.Assert(queue == this.outQueue || queue == this.inQueue);
|
|
if (queue == this.outQueue) {
|
|
return this.outQueueCapacity;
|
|
}
|
|
else {
|
|
return this.inQueueCapacity;
|
|
}
|
|
}
|
|
|
|
// put a packet in a Queue and notify waiters
|
|
// return false if the packet was thrown
|
|
protected bool EnqueuePacket(ArrayList! queue,
|
|
NetPacket! packet,
|
|
bool toBlock)
|
|
{
|
|
lock (queue.SyncRoot)
|
|
{
|
|
if (queue.Count >= queue.Capacity && !toBlock) {
|
|
return false;
|
|
}
|
|
|
|
int queueCapacity = GetQueueCapacity(queue);
|
|
while (queue.Count >= queueCapacity)
|
|
/* Potential fix to stop accepting users packets until
|
|
destination is ARPed */
|
|
/*|| (queue == this.outQueue && this.outQueuePaused) */
|
|
{
|
|
// Note In TcpSessions the queue length may
|
|
// be greater than queueCapacity because of
|
|
// retransmits. Retransmits are inserted at
|
|
// the head of the sessions outbound queue
|
|
// and we don't want any more packets
|
|
// enqueued until we've drained below the
|
|
// desired water level.
|
|
Monitor.Wait(queue.SyncRoot);
|
|
}
|
|
|
|
// Enqueue packet then set packet session
|
|
// context. The context can be used to block
|
|
// the sessions outqueue while waiting for
|
|
// events such as ARP
|
|
// (IPModule.BlockAssociatedSession does this
|
|
// for instance)
|
|
queue.Add(packet);
|
|
packet.SessionContext = this;
|
|
|
|
Monitor.PulseAll(queue.SyncRoot);
|
|
}
|
|
|
|
if (queue == this.outQueue) {
|
|
// Signal to the core that outbound queues need
|
|
// servicing...
|
|
Core.Instance().SignalOutboundPackets();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Determine whether the queue is empty without sleeping
|
|
internal bool QueueIsEmpty(ArrayList! queue)
|
|
{
|
|
lock (queue.SyncRoot)
|
|
{
|
|
return queue.Count == 0;
|
|
}
|
|
}
|
|
|
|
protected bool PollQueueNotEmpty(ArrayList! queue, int timeoutMillis)
|
|
{
|
|
if (timeoutMillis < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DateTime startTime = DateTime.Now;
|
|
TimeSpan timeout = TimeSpan.FromMilliseconds(timeoutMillis);
|
|
TimeSpan remain = timeout;
|
|
bool result = false;
|
|
|
|
if (Monitor.TryEnter(queue.SyncRoot, remain) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
remain = startTime + timeout - DateTime.Now;
|
|
|
|
while (remain >= TimeSpan.Zero && result == false)
|
|
{
|
|
Monitor.PulseAll(queue.SyncRoot);
|
|
|
|
if (Monitor.Wait(queue.SyncRoot, remain) == false)
|
|
{
|
|
// Timed-out waiting for lock
|
|
return false;
|
|
}
|
|
|
|
result = (queue.Count > 0);
|
|
remain = startTime + timeout - DateTime.Now;
|
|
}
|
|
|
|
Monitor.PulseAll(queue.SyncRoot);
|
|
Monitor.Exit(queue.SyncRoot);
|
|
return result;
|
|
}
|
|
|
|
protected bool PollQueueNotFull(ArrayList! queue, int timeoutMillis)
|
|
{
|
|
if (timeoutMillis < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DateTime startTime = DateTime.Now;
|
|
TimeSpan timeout = TimeSpan.FromMilliseconds(timeoutMillis);
|
|
TimeSpan remain = timeout;
|
|
bool result = false;
|
|
|
|
if (Monitor.TryEnter(queue.SyncRoot, remain) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
remain = startTime + timeout - DateTime.Now;
|
|
|
|
while (remain >= TimeSpan.Zero && result == false)
|
|
{
|
|
Monitor.PulseAll(queue.SyncRoot);
|
|
if (Monitor.Wait(queue.SyncRoot, remain) == false)
|
|
{
|
|
// Timed-out waiting for lock
|
|
return false;
|
|
}
|
|
result = (queue.Count < queue.Capacity);
|
|
remain = startTime + timeout - DateTime.Now;
|
|
}
|
|
|
|
Monitor.PulseAll(queue.SyncRoot);
|
|
Monitor.Exit(queue.SyncRoot);
|
|
return result;
|
|
}
|
|
|
|
// this method is called by the Core to handle outgoing queues, should
|
|
// be override by concrete sessions if necessary
|
|
virtual internal NetPacket GetPacket(ArrayList! queue,
|
|
bool toBlock,
|
|
int timeoutMillis)
|
|
{
|
|
return DequeuePacket(queue, timeoutMillis);
|
|
}
|
|
|
|
virtual internal bool PutPacket(ArrayList! queue,
|
|
NetPacket! packet,
|
|
bool toBlock)
|
|
{
|
|
return (EnqueuePacket(queue, packet, toBlock));
|
|
}
|
|
|
|
virtual internal NetStatus OnReceive(object sender, NetPacket! pkt, object ctx)
|
|
{
|
|
return NetStatus.Code.PROTOCOL_OK;
|
|
}
|
|
|
|
//--------------------------------------------
|
|
// User interface
|
|
//--------------------------------------------
|
|
|
|
virtual public byte[] PollCopyData(int timeoutMillis)
|
|
{
|
|
// If we're out of packets and there will never be anymore,
|
|
// don't try to sleep for any!
|
|
if (QueueIsEmpty(inQueue) && !IsSessionValidForUserRead()) {
|
|
return null;
|
|
}
|
|
// else return the enqueued packets
|
|
|
|
NetPacket netPacket = DequeuePacket(inQueue, timeoutMillis);
|
|
|
|
if (netPacket != null) {
|
|
DebugStub.Assert(netPacket.AdapterContext != null);
|
|
byte [] userData = netPacket.ToUser();
|
|
Core.Instance().TheDemux.TakeFreePacket(netPacket);
|
|
return userData;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public byte[] in ExHeap PollData(int timeoutMillis)
|
|
{
|
|
// Duplicate of PollData, but copies to shared heap buffer.
|
|
if (!IsSessionValidForUserRead()) {
|
|
return null;
|
|
}
|
|
// else return the enqueued packets
|
|
|
|
NetPacket netPacket = DequeuePacket(inQueue, timeoutMillis);
|
|
|
|
if (netPacket != null) {
|
|
byte []! in ExHeap userData =
|
|
Bitter.FromByteArray((!)netPacket.GetRawData(),
|
|
netPacket.Position,
|
|
netPacket.Available);
|
|
Core.Instance().TheDemux.TakeFreePacket(netPacket);
|
|
return userData;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public byte[] in ExHeap ReadData()
|
|
{
|
|
return PollData(Timeout.Infinite);
|
|
}
|
|
|
|
// read data, if no data is ready - blocks
|
|
virtual public byte[] ReadCopyData()
|
|
{
|
|
return PollCopyData(Timeout.Infinite);
|
|
}
|
|
|
|
// write a data to a session,
|
|
// if the queue has no room - blocks
|
|
// return the number of written data
|
|
// TBC: enforce data size limits
|
|
// should be implemented by specific sessions
|
|
// to create protocol specific packets
|
|
virtual public int WriteData(byte[]! data)
|
|
{
|
|
if (!IsSessionValidForUserWrite())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
byte[] pktData = new byte[data.Length];
|
|
Array.Copy(data, pktData, data.Length);
|
|
NetPacket pkt = new NetPacket(pktData);
|
|
|
|
if (false == EnqueuePacket(outQueue, pkt, true))
|
|
return -1;
|
|
|
|
return data.Length;
|
|
}
|
|
|
|
// Perform a graceful shutdown (nonblocking)
|
|
virtual public bool Close()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Perform an immediate, hard shutdown
|
|
virtual public bool Abort()
|
|
{
|
|
// By default, assume there is no distinction
|
|
// between shutdown types
|
|
return Close();
|
|
}
|
|
|
|
public class LocalPortComparer : IComparer
|
|
{
|
|
public int Compare (Object left, Object right)
|
|
{
|
|
Session lhs = left as Session;
|
|
Session rhs = right as Session;
|
|
if (lhs == null)
|
|
{
|
|
return (rhs == null) ? 0 : -1;
|
|
}
|
|
if (rhs == null)
|
|
{
|
|
return +1;
|
|
}
|
|
if (lhs.LocalPort < rhs.LocalPort)
|
|
{
|
|
return -1;
|
|
}
|
|
if (lhs.LocalPort > rhs.LocalPort)
|
|
{
|
|
return +1;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// single sessions table.
|
|
// hashed by the protocol interface
|
|
public class SessionsTable : Hashtable
|
|
{
|
|
}
|
|
}
|