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

778 lines
26 KiB
C#
Raw Normal View History

2008-03-05 09:52:00 -05:00
///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////
/**
* Microsoft Research, Cambridge
* author: Yaron Weinsberg, Richard Black
*/
// #define DEBUG_CORE
using NetStack.Common;
using System;
using System.Collections;
using System.Diagnostics;
using System.Threading;
using System.Net.IP;
using Drivers.Net;
using NetStack.NetDrivers;
using NetStack.Protocols;
using Microsoft.Singularity;
namespace NetStack.Runtime
{
/**
* This is the heart of the networking stack.
* - A single thread inside the core handles
* packet reception, Qos demultiplexing and
* later on multiplexing on a specific protocol queue.
* - The thread also handles user send & receive in order
* to avoid reentrancy deadlocks etc.
* - The Runtime uses an asynchronous IO in order to
* avoid locking and denial of service.
* - The Core's thread, never invokes user's callbacks
* directly, the user can poll for a message or get
* his thread to sleep, waiting for a new message event.
*/
public class Core : INetModule
{
private bool activated = false;
#if THREAD_POOL
private const int workerThreads = 16;
private const int maxWorkItems = workerThreads * 128;
private NetStackThreadPool! threadPool;
#endif
// the single core object
protected static Core! instance = new Core();
// disable default lazy creation (beforefieldinit)
static Core() {}
private Core()
{
worker = null;
stopped = false;
protocols = new SortedList();
packetTypes = new PacketTypes();
adapters = new ArrayList();
adapterNextId = 0;
theDispatcher = new Dispatcher();
muxTable = new ArrayList();
sessions = new SessionsTable();
outboundPacketsEvent = new AutoResetEvent(false);
inboundPacketsEvent = new AutoResetEvent(false);
#if THREAD_POOL
threadPool =
new NetStackThreadPool(workerThreads, maxWorkItems);
#endif
base();
// NB The demux is initialized when the first adapter is
// registered as it depends on the Core Instance being
// valid and it's not within this constructor.
// theDemux = null;
}
// get the single core instance
public static Core! Instance()
{
return instance;
}
#if THREAD_POOL
public NetStackThreadPool! ThreadPool {
get { return threadPool; }
}
#endif
[ System.Diagnostics.Conditional("DEBUG_CORE") ]
private static void DebugPrint(string format, params object[] args)
{
DebugStub.Print("Core: {0}",
__arglist(String.Format(format, args))
);
}
// create a session of a specific protocol
public Session CreateSession(string protocolName)
{
IProtocol p = GetProtocolByName(protocolName);
if (p == null) {
return null;
}
return (p.CreateSession());
}
// register a new session in the runtime
// notice that multiple user threads can register new
// sessions (multiple writers).
// In the Meanwhile the internal thread can scan the
// sessions in order to HandleOutgoingSessions
internal void RegisterSession(IProtocol! p, Session s)
{
lock (sessions.SyncRoot)
{
if (!sessions.Contains(p)) {
ArrayList l = new ArrayList();
l.Add(s);
sessions[p] = l;
}
else {
ArrayList l = (ArrayList!)sessions[p];
lock (l.SyncRoot) {
if (!l.Contains(s)) {
l.Add(s);
}
}
}
}
}
// Deregister a session
internal void DeregisterSession(IProtocol! p, Session s)
{
lock (sessions.SyncRoot) {
if (!sessions.Contains(p))
Core.Panic("Error: Can't deregister a session for an unknown protocol!");
else {
ArrayList l = (ArrayList!)sessions[p];
lock (l.SyncRoot) {
l.Remove(s);
}
}
}
}
private static int FindFreePort(ArrayList sessions)
{
const int PortMin = 1024;
const int PortMax = 65535;
if (sessions == null || sessions.Count == 0)
return PortMin;
int lastPort = PortMin;
foreach (Session! s in sessions) {
if (s.LocalPort - 1 > lastPort)
return s.LocalPort - 1;
if (lastPort > PortMin)
lastPort = s.LocalPort;
}
lastPort++;
if (lastPort > PortMax)
return -1;
return lastPort;
}
internal bool ChangeSessionLocalPort(Session! s, int newPort)
{
ArrayList tcpSessions = Core.Instance().GetSessions(s.Protocol);
// If no sessions registered yet, just do it...
if (tcpSessions == null) {
if (newPort == 0) {
newPort = FindFreePort(tcpSessions);
}
s.SetLocalPort((ushort) newPort);
return true;
}
// Take care to acquire the global sessions lock before the
// more specific lock for the TCP sessions list
lock (sessions.SyncRoot) {
lock (tcpSessions.SyncRoot) {
if (newPort == 0) {
// Find FreePort
newPort = FindFreePort(tcpSessions);
}
if (newPort < 0)
return false;
// Deregister session
Core.Instance().DeregisterSession(s.Protocol, s);
// Reregister session
s.SetLocalPort((ushort) newPort);
Core.Instance().RegisterSession(s.Protocol, s);
return true;
}
}
}
internal ArrayList GetSessions(IProtocol! protocol)
{
return (ArrayList)sessions[protocol];
}
public int GetSessionCount(IProtocol! protocol)
{
ArrayList tmp = (ArrayList)sessions[protocol];
if (tmp != null) return tmp.Count;
return 0;
}
private bool HandleSessionsOutboundQueue(Session! s)
{
DebugPrint("HandleSession {0} paused = {1} queued {2}\n",
s.Uid, s.OutQueuePaused, s.outQueue.Count);
// Session may be paused if waiting on reception
// event, e.g. ARP response triggered by first
// outbound packet.
if (s.OutQueuePaused == true) {
return false;
}
NetPacket packet = s.GetPacket(s.outQueue, false, 0);
if (packet == null) {
return false;
}
if (packet.Mux == null)
packet.Mux = s.BoundMux;
s.Protocol.OnProtocolSend(packet);
// NOTE Special case; if this is a TCP session
// and we just transmitted the very last packet it will
// ever send, help out with the shutdown process by
// signalling this session closed.
TcpSession tcpS = s as TcpSession;
if ((tcpS != null) &&
(tcpS.stateContext == TCPFSM.CLOSED) &&
(tcpS.outQueue.Count == 0))
{
// That was the very last TCP frame for this session
tcpS.closedEvent.Set();
}
return true;
}
private bool IsAnyMuxBackLogged()
{
foreach (Multiplexer! m in this.muxTable) {
if (m.IsBackLogged) {
DebugPrint("BackLogged\n");
return true;
}
}
return false;
}
private int GetMaxOutgoingPackets()
{
int n = ((Multiplexer!)this.muxTable[0]).FreeBufferSpace;
for (int i = 1; i < this.muxTable.Count; i++) {
n = Math.Min(n,
((Multiplexer!)this.muxTable[i]).FreeBufferSpace);
}
return n;
}
static Random picker = new Random();
// handle outgoing queues...
// we handle outgoing queues of user sessions in two cases:
// 1. periodically using a timer
// 2. after handling received packets
internal NetStatus HandleOutgoingQueues(Dispatcher.CallbackArgs unused)
{
lock (sessions.SyncRoot)
{
DebugPrint("++++ OUTGOING QUEUE PASS ++++\n");
int todo = GetMaxOutgoingPackets();
DebugPrint("Driving Sessions (mux avail = {0})\n",
GetMaxOutgoingPackets());
DebugPrint("Mux max packets = {0}\n", todo);
bool morePackets = true;
while (todo > 0 && morePackets) {
morePackets = false;
// TODO: Shuffle order
// sessions are visited for some concept of
// fairness. This probably requires
// redesigning The protocol session storage
// because it's currently a hashtable and
// this does not give a particularly
// effective means to shuffle / rotate
// entries.
foreach (IProtocol! p in sessions.Keys) {
ArrayList! x = (ArrayList!)sessions[p];
int limit = x.Count;
int offset = picker.Next(x.Count);
for (int i = 0; i < limit; i++) {
Session! s = (Session!)x[(i + offset) % limit];
if (HandleSessionsOutboundQueue(s)) {
if (--todo == 0)
goto DoneDrivingQueues;
morePackets |= true;
}
}
}
// To be really drive the outbound queue
// todo = GetMaxOutgoingPackets();
}
DoneDrivingQueues:
DebugPrint("Driving Mux (mux avail = {0})\n",
GetMaxOutgoingPackets());
for (int i = this.muxTable.Count - 1; i >= 0; i--) {
Multiplexer! m = (Multiplexer!)this.muxTable[i];
m.PushSendBuffer();
}
DebugPrint("Done Driving Mux (mux avail = {0})\n",
GetMaxOutgoingPackets());
DebugPrint("---- OUTGOING QUEUE PASS ----\n");
if (morePackets) {
// Make sure we wake up again
SignalOutboundPackets();
}
}
return NetStatus.Code.RT_OK;
}
// INetModule interface
// ---------------------
// get the module's name
public string ModuleName { get { return "Core"; } }
// get the modules version
public ushort ModuleVersion { get { return 0x01; } } // 0.1
public bool Initialize(ProtocolParams args)
{
return true;
}
// start module activity, module specific.
// it is called by the runtime
public bool StartModule()
{
if (stopped) {
worker.Start();
stopped = false;
}
return true;
}
// stop module activity, module specific.
// it is called by the runtime
public bool StopModule()
{
if (!stopped) {
// Signal stop to dispatcher since worker thread
// is running dispatcher.
theDispatcher.Stop();
worker.Join();
stopped = true;
}
return true;
}
// destroy module, free unmanaged resources if any.
// it is called by the runtime
public bool DestroyModule()
{
StopModule();
return true;
}
// ------------------------------------------------------
// Core's data
// ------------------------------------------------------
// Sorted List of adapters <adapter, adapterDetails>
private ArrayList! adapters;
// Id number of next adapter added
private int adapterNextId;
// the protocol list
private SortedList! protocols;
// the packet types list
public PacketTypes packetTypes;
// out internal thread
protected Thread worker;
// should the thread stop?
protected bool stopped;
// the dispatcher instance, implements the event driven logic
protected Dispatcher! theDispatcher;
internal Dispatcher! TheDispatcher { get { return theDispatcher; } }
// the demux object, intercepts packets as they come and
// classifies them into flows
protected DeMultiplexer theDemux;
internal DeMultiplexer! TheDemux { get { return theDemux; } }
private ArrayList muxTable;
// the runtime active sessions
protected SessionsTable sessions;
internal SessionsTable Sessions { get { return sessions; } }
// This event is used to signal that there are outbound packets
private AutoResetEvent outboundPacketsEvent;
private AutoResetEvent inboundPacketsEvent;
// ------------------------------------------------------
// Core specific methods
// ------------------------------------------------------
internal class AdapterArgs : Dispatcher.CallbackArgs
{
internal IAdapter adapter;
internal string deviceName;
internal int fwQueueSize;
internal AdapterArgs(IAdapter theAdapter,
string theDeviceName,
int theQueueSize)
{
adapter = theAdapter;
deviceName = theDeviceName;
fwQueueSize = theQueueSize;
}
}
private NetStatus RegisterAdapterInternal(Dispatcher.CallbackArgs args)
{
AdapterArgs aa = (AdapterArgs!)args;
if (theDemux == null) {
theDemux = new DeMultiplexer();
}
Core.Log("Registering {0}", aa.deviceName);
adapters.Add(new AdapterInfo(aa.adapter, aa.deviceName));
Multiplexer mux = new Multiplexer(new Queue(aa.fwQueueSize),
aa.adapter);
muxTable.Add(mux);
theDispatcher.AddCallback(
new Dispatcher.Callback(mux.OnAdapterSendComplete),
null, aa.adapter.GetWriteHandle()
);
theDispatcher.AddCallback(
new Dispatcher.Callback(theDemux.OnAdapterReceive),
new DemuxAdapterArgs(aa.adapter), aa.adapter.GetReadHandle()
);
Core.Log("Registered adapter: {0}", aa.deviceName);
return NetStatus.Code.RT_OK;
}
private NetStatus
DeregisterAdapterInternal(Dispatcher.CallbackArgs args)
{
AdapterArgs aa = (AdapterArgs!)args;
IAdapter ad = aa.adapter;
// XXX IP specific hack - should be replaced by a
// message to each module or some such.
IPModule ip = (IPModule)protocols["IP"];
if (ip != null) {
ip.HostConfiguration.Bindings.Flush(ad);
}
theDispatcher.RemoveCallback(ad.GetReadHandle());
theDispatcher.RemoveCallback(ad.GetWriteHandle());
foreach (AdapterInfo! ai in adapters) {
if (ai.Adapter == ad) {
adapters.Remove(ad);
break;
}
}
foreach (Multiplexer! m in muxTable) {
if (m.Adapter == ad) {
muxTable.Remove(m);
}
}
Core.Log("Deregistered adapter: {0}", aa.deviceName);
return NetStatus.Code.RT_OK;
}
public void RegisterAdapter(IAdapter ad,
string nsName,
int fwQueueSize)
{
theDispatcher.AddCallback(
new Dispatcher.Callback(this.RegisterAdapterInternal),
new AdapterArgs(ad, nsName, fwQueueSize)
);
}
public void DeregisterAdapter(IAdapter ad)
{
theDispatcher.AddCallback(
new Dispatcher.Callback(this.DeregisterAdapterInternal),
new AdapterArgs(ad, null, 0)
);
}
public IAdapter GetAdapterByDeviceName(string deviceName)
{
foreach (AdapterInfo! ai in adapters) {
if (ai.DeviceName == deviceName) {
return ai.Adapter;
}
}
return null;
}
/// <summary>
/// Get collection of AdapterInfo instances associated
/// with adapters.
/// </summary>
public ICollection! GetAdapterInfoCollection()
{
return adapters;
}
// return the mux for the given adapter
internal Multiplexer GetMuxForAdapter(IAdapter ad)
{
foreach (Multiplexer! m in muxTable) {
if (m.Adapter == ad) {
return m;
}
}
return null;
}
public bool RegisterProtocol(IProtocol! protocol)
{
try {
protocols[protocol.ModuleName] = protocol;
return true;
}
catch (ArgumentException)
{
return false;
}
}
public bool DeregisterProtocol(IProtocol! protocol)
{
IProtocol found = protocols[protocol.ModuleName] as IProtocol;
if (found == protocol) {
protocols.Remove(protocol.ModuleName);
return true;
}
return false;
}
// get a protocol reference identified by name
public IProtocol GetProtocolByName(string protocolName)
{
if (protocolName == null)
return null;
return protocols[protocolName] as IProtocol;
}
/**
* Dispatch an incoming packets
* this method actually drives the
* packet through the stack's modules.
*/
private NetStatus DispatchPacket(NetPacket! pkt)
{
// look at the ethernet headers...
EthernetAddress src, dst;
ushort prot = 0;
if (EthernetFormat.Read(pkt, out src, out dst, out prot)) {
IProtocol protocol = packetTypes.GetHandler(prot);
if (protocol != null) {
NetStatus res = protocol.OnProtocolReceive(pkt);
if (res == NetStatus.Code.PROTOCOL_PROCESSING) {
return NetStatus.Code.RT_OK;
}
if (res == NetStatus.Code.PROTOCOL_PANIC) {
Panic(String.Format("Protocol {0} panicked!!!",
protocol.ModuleName));
// if the protocol has finished with the packet, return it!
}
}
}
// no handler, return the packet
theDemux.TakeFreePacket(pkt);
return NetStatus.Code.RT_DROP_NO_HANDLER;
}
// get the protocol interface from an ID
// (ignore packet level protocols)
internal IProtocol GetProtocolFromID(byte prot)
{
IProtocol p = null;
// checkout the protocol list for this one
foreach (string retProtName in protocols.Keys) {
p = (IProtocol!)protocols[retProtName];
ushort pID = p.GetProtocolID();
if (EthernetFormat.IsEthernetProtocol(pID))
continue;
if (prot == (byte)pID)
break;
}
return p;
}
public static void Panic(string reason)
{
Core.Log("NetStack Panic: {0}", reason);
DebugStub.Break();
#if !SINGULARITY
Environment.Exit(1);
#endif
}
public static void Panic(string format, params object[] args)
{
Core.Panic(String.Format(format, args));
}
public static void Log(string message)
{
DebugStub.Print("NetStack: {0}\n", __arglist(message));
}
public static void Log(string format, params object[] args)
{
Core.Log(String.Format(format, args));
}
public static void LogData(byte []! Data)
{
LogData(Data, 0, Data.Length);
}
public static void LogData(byte []! data, int offset, int length)
{
length = Math.Min(length, data.Length);
for (int i = offset; i < length; i += 16) {
DebugStub.Print("{0:x4} ", __arglist(i));
int n = Math.Min(16, length - i) + i;
for (int j = i; j < n; j++)
DebugStub.Print("{0:x2} ", __arglist(data[j]));
for (int j = n; j != i + 16; j++)
DebugStub.Print(" ");
DebugStub.Print(" ");
for (int j = i; j < n; j++) {
char c = '.';
if (data[j] > 31 && data[j] < 128)
c = (char)data[j];
DebugStub.Print("{0}", __arglist(c));
}
DebugStub.Print("\n");
}
}
// the core's activation routine, beware...
// (this is a go/no-go method)
public void Activate()
{
Core.Log("NetStack Core Runtime Active...");
// since all the protocols are initialized
// start them up (start module)
foreach (string protName in protocols.Keys) {
IProtocol protocol = (IProtocol!)protocols[protName];
if (protocol.StartModule() == false) {
Core.Panic("Module {0} failed to start, exit.",
protocol.ModuleName);
}
}
// register a callback to handle outgoing queues
theDispatcher.AddCallback(
new Dispatcher.Callback(this.HandleOutgoingQueues),
null, outboundPacketsEvent
);
// register a callback to handle incoming queue
theDispatcher.AddCallback(
new Dispatcher.Callback(this.HandleIncomingQueue),
null, inboundPacketsEvent
);
// create the worker
// now, pass control to the dispatcher...
worker = new Thread(new ThreadStart(theDispatcher.Execute));
worker.Start();
activated = true;
}
public void ProvisionDemux(int freePacketCount, int packetSize)
{
if (activated == true)
Core.Panic("Cannot provision demux after Core.Activate()");
theDemux = new DeMultiplexer();
}
private NetStatus HandleIncomingQueue(Dispatcher.CallbackArgs unused)
{
const int PacketsToHandleBeforeYield = 25;
int n = PacketsToHandleBeforeYield;
while (n-- > 0 && theDemux.PacketsReady) {
NetPacket p = theDemux.GetReceivedPacket();
if (p == null) {
break;
}
DispatchPacket(p);
}
if (theDemux.PacketsReady) {
SignalInboundPackets();
}
return NetStatus.Code.RT_OK;
}
// Call this to indicate that outbound packets are waiting
public void SignalOutboundPackets()
{
outboundPacketsEvent.Set();
}
// Call this to indicate that outbound packets are waiting
public void SignalInboundPackets()
{
inboundPacketsEvent.Set();
}
}
}