diff --git a/PUP/BSP/BSPChannel.cs b/PUP/BSP/BSPChannel.cs
index a817a78..175129c 100644
--- a/PUP/BSP/BSPChannel.cs
+++ b/PUP/BSP/BSPChannel.cs
@@ -9,6 +9,8 @@ using System.Threading.Tasks;
namespace IFS.BSP
{
+ public delegate void BSPChannelDestroyDelegate(BSPChannel channel);
+
///
/// Provides functionality for maintaining/terminating BSP connections, and the transfer of data
/// across said connection.
@@ -19,7 +21,7 @@ namespace IFS.BSP
///
public class BSPChannel
{
- public BSPChannel(PUP rfcPup, UInt32 socketID, BSPProtocol protocolHandler)
+ public BSPChannel(PUP rfcPup, UInt32 socketID)
{
_inputLock = new ReaderWriterLockSlim();
_outputLock = new ReaderWriterLockSlim();
@@ -35,7 +37,7 @@ namespace IFS.BSP
_outputWindow = new List(16);
_outputWindowLock = new ReaderWriterLockSlim();
- _protocolHandler = protocolHandler;
+ _destroyed = false;
// Init IDs, etc. based on RFC PUP
_lastClientRecvPos = _startPos = _recvPos = _sendPos = rfcPup.ID;
@@ -63,11 +65,9 @@ namespace IFS.BSP
// Create our consumer thread for output and kick it off.
_consumerThread = new Thread(OutputConsumerThread);
_consumerThread.Start();
- }
+ }
- public delegate void DestroyDelegate();
-
- public DestroyDelegate OnDestroy;
+ public BSPChannelDestroyDelegate OnDestroy;
///
/// The port we use to talk to the client.
@@ -98,13 +98,10 @@ namespace IFS.BSP
/// the channel has been destroyed.
///
public void Destroy()
- {
- _consumerThread.Abort();
-
- if (OnDestroy != null)
- {
- OnDestroy();
- }
+ {
+ _destroyed = true;
+ _consumerThread.Abort();
+ OnDestroy(this);
}
///
@@ -146,7 +143,7 @@ namespace IFS.BSP
throw new InvalidOperationException("count + offset must be less than or equal to the length of the buffer being read into.");
}
- if (count == 0)
+ if (count == 0 || _destroyed)
{
// Honor requests to read 0 bytes always, since technically 0 bytes are always available.
data = new byte[0];
@@ -210,7 +207,9 @@ namespace IFS.BSP
Log.Write(LogType.Error, LogComponent.BSP, "Timed out waiting for data on read, aborting connection.");
// We timed out waiting for data, abort the connection.
SendAbort("Timeout on read.");
- BSPManager.DestroyChannel(this);
+ Destroy();
+ read = 0;
+ break;
}
}
}
@@ -301,7 +300,7 @@ namespace IFS.BSP
Log.Write(LogType.Error, LogComponent.BSP, "Mark PUP must be 1 byte in length.");
SendAbort("Mark PUP must be 1 byte in length.");
- BSPManager.DestroyChannel(this);
+ Destroy();
return;
}
}
@@ -396,6 +395,11 @@ namespace IFS.BSP
throw new InvalidOperationException("Length must be less than or equal to the size of data.");
}
+ if (_destroyed)
+ {
+ return;
+ }
+
// Add output data to output queue.
// Again, this is really inefficient
for (int i = 0; i < length; i++)
@@ -429,6 +433,11 @@ namespace IFS.BSP
///
public void SendAbort(string message)
{
+ if (_destroyed)
+ {
+ return;
+ }
+
PUP abortPup = new PUP(PupType.Abort, _startPos, _clientConnectionPort, _serverConnectionPort, Helpers.StringToArray(message));
//
@@ -445,6 +454,11 @@ namespace IFS.BSP
///
public void SendMark(byte markType, bool ack)
{
+ if (_destroyed)
+ {
+ return;
+ }
+
PUP markPup = new PUP(ack ? PupType.AMark : PupType.Mark, _sendPos, _clientConnectionPort, _serverConnectionPort, new byte[] { markType });
// Send it.
@@ -689,7 +703,7 @@ namespace IFS.BSP
// Something bad has happened and we don't have that PUP anymore...
Log.Write(LogType.Error, LogComponent.BSP, "Client lost more than a window of data, BSP connection is broken. Aborting.");
SendAbort("Fatal BSP synchronization error.");
- BSPManager.DestroyChannel(this);
+ Destroy();
_outputWindowLock.ExitUpgradeableReadLock();
return;
}
@@ -769,11 +783,9 @@ namespace IFS.BSP
{
Log.Write(LogType.Error, LogComponent.BSP, "Timeout waiting for ACK, aborting connection.");
SendAbort("Client unresponsive.");
- BSPManager.DestroyChannel(this);
+ Destroy();
}
- }
-
- private BSPProtocol _protocolHandler;
+ }
// The byte positions for the input and output streams
private UInt32 _recvPos;
@@ -786,6 +798,8 @@ namespace IFS.BSP
private BSPAck _clientLimits; // The stats from the last ACK we got from the client.
private uint _lastClientRecvPos; // The client's receive position, as indicated by the last ACK pup received.
+ private bool _destroyed; // Set when the channel has been closed.
+
private ReaderWriterLockSlim _inputLock;
private AutoResetEvent _inputWriteEvent;
diff --git a/PUP/BSP/BSPManager.cs b/PUP/BSP/BSPManager.cs
index 21d7de4..b2f0243 100644
--- a/PUP/BSP/BSPManager.cs
+++ b/PUP/BSP/BSPManager.cs
@@ -15,18 +15,34 @@ namespace IFS.BSP
public ushort MaxBytes;
public ushort MaxPups;
public ushort BytesSent;
- }
+ }
public abstract class BSPProtocol : PUPProtocolBase
{
- public abstract void InitializeServerForChannel(BSPChannel channel);
+ public abstract void InitializeServerForChannel(BSPChannel channel);
}
public enum BSPState
{
Unconnected,
Connected
- }
+ }
+
+ public delegate void WorkerExitDelegate(BSPWorkerBase destroyed);
+
+ public abstract class BSPWorkerBase
+ {
+ public BSPWorkerBase(BSPChannel channel)
+ {
+ _channel = channel;
+ }
+
+ public abstract void Terminate();
+
+ public WorkerExitDelegate OnExit;
+
+ protected BSPChannel _channel;
+ }
///
/// Manages active BSP channels and creates new ones as necessary, invoking the associated
@@ -47,14 +63,16 @@ namespace IFS.BSP
_nextSocketID = _startingSocketID;
_activeChannels = new Dictionary();
+
+ _workers = new List(Configuration.MaxWorkers);
}
///
- /// Called when a PUP comes in on a known socket and establishes a new BSP channel.
- /// The associated protocol handler (server) is woken up to service the channel.
+ /// Called when a PUP comes in on a known socket. Establishes a new BSP channel.
+ /// A worker of the appropriate type is woken up to service the channel.
///
///
- public static void EstablishRendezvous(PUP p, BSPProtocol protocolHandler)
+ public static void EstablishRendezvous(PUP p, Type workerType)
{
if (p.Type != PupType.RFC)
{
@@ -63,12 +81,13 @@ namespace IFS.BSP
}
UInt32 socketID = GetNextSocketID();
- BSPChannel newChannel = new BSPChannel(p, socketID, protocolHandler);
- _activeChannels.Add(socketID, newChannel);
+ BSPChannel newChannel = new BSPChannel(p, socketID);
+ newChannel.OnDestroy += OnChannelDestroyed;
+ _activeChannels.Add(socketID, newChannel);
//
- // Initialize the server for this protocol.
- protocolHandler.InitializeServerForChannel(newChannel);
+ // Initialize the worker for this channel.
+ InitializeWorkerForChannel(newChannel, workerType);
// Send RFC response to complete the rendezvous.
@@ -172,8 +191,14 @@ namespace IFS.BSP
///
public static void DestroyChannel(BSPChannel channel)
{
+ // Tell the channel to shut down. It will in turn
+ // notify us when that is complete and we will remove
+ // our references to it. (See OnChannelDestroyed.)
channel.Destroy();
+ }
+ public static void OnChannelDestroyed(BSPChannel channel)
+ {
_activeChannels.Remove(channel.ServerPort.Socket);
}
@@ -218,6 +243,40 @@ namespace IFS.BSP
return next;
}
+ private static void InitializeWorkerForChannel(BSPChannel channel, Type workerType)
+ {
+ if (_workers.Count < Configuration.MaxWorkers)
+ {
+ // Spawn new worker, which starts it running.
+ // It must be a subclass of BSPWorkerBase or this will throw.
+ BSPWorkerBase worker = (BSPWorkerBase)Activator.CreateInstance(workerType, new object[] { channel });
+
+ worker.OnExit += OnWorkerExit;
+ _workers.Add(worker);
+ }
+ else
+ {
+ // TODO: send back "server full" repsonse of some sort.
+ }
+ }
+
+ public static int WorkerCount
+ {
+ get
+ {
+ return _workers.Count();
+ }
+ }
+
+ private static void OnWorkerExit(BSPWorkerBase destroyed)
+ {
+ if (_workers.Contains(destroyed))
+ {
+ _workers.Remove(destroyed);
+ }
+ }
+
+ private static List _workers;
///
/// Map from socket address to BSP channel
diff --git a/PUP/Conf/ifs.cfg b/PUP/Conf/ifs.cfg
new file mode 100644
index 0000000..f946d25
--- /dev/null
+++ b/PUP/Conf/ifs.cfg
@@ -0,0 +1,17 @@
+# ifs.cfg:
+#
+# This file contains configuration parameters for the IFS server.
+# All numbers are in decimal.
+#
+
+# Debug settings
+
+LogTypes = Normal
+LogComponents = All
+
+# Normal configuration
+FTPRoot = c:\ifs\ftp
+CopyDiskRoot = c:\ifs\copydisk
+BootRoot = c:\ifs\boot
+InterfaceType = RAW
+InterfaceName = Ethernet 2
diff --git a/PUP/Configuration.cs b/PUP/Configuration.cs
index 7b8dd7a..f0b940f 100644
--- a/PUP/Configuration.cs
+++ b/PUP/Configuration.cs
@@ -1,33 +1,203 @@
-using System;
+using IFS.Logging;
+using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
+using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace IFS
{
+
+ public class InvalidConfigurationException : Exception
+ {
+ public InvalidConfigurationException(string message) : base(message)
+ {
+
+ }
+ }
+
+
///
/// Encapsulates global server configuration information.
- ///
- /// TODO: read in configuration from a text file.
- /// TODO also: make cross-platform compatible (no hardcoding of path delimiters).
///
public static class Configuration
{
+ static Configuration()
+ {
+ ReadConfiguration();
+
+ //
+ // Ensure that required values were read from the config file. If not,
+ // throw so that startup is aborted.
+ //
+ if (string.IsNullOrWhiteSpace(FTPRoot) || !Directory.Exists(FTPRoot))
+ {
+ throw new InvalidConfigurationException("FTP root path is invalid.");
+ }
+
+ if (string.IsNullOrWhiteSpace(CopyDiskRoot) || !Directory.Exists(CopyDiskRoot))
+ {
+ throw new InvalidConfigurationException("CopyDisk root path is invalid.");
+ }
+
+ if (string.IsNullOrWhiteSpace(BootRoot) || !Directory.Exists(BootRoot))
+ {
+ throw new InvalidConfigurationException("Boot root path is invalid.");
+ }
+
+ if (MaxWorkers < 1)
+ {
+ throw new InvalidConfigurationException("MaxWorkers must be >= 1.");
+ }
+ }
+
+ ///
+ /// The type of interface (UDP or RAW) to communicate over
+ ///
+ public static readonly string InterfaceType;
+
+ ///
+ /// The name of the network interface to use
+ ///
+ public static readonly string InterfaceName;
+
///
/// The root directory for the FTP file store.
///
- public static readonly string FTPRoot = "C:\\ifs\\ftp";
+ public static readonly string FTPRoot;
///
/// The root directory for the CopyDisk file store.
///
- public static readonly string CopyDiskRoot = "C:\\ifs\\copydisk";
+ public static readonly string CopyDiskRoot;
///
/// The root directory for the Boot file store.
///
- public static readonly string BootRoot = "C:\\ifs\\boot";
+ public static readonly string BootRoot;
+ ///
+ /// The maximum number of worker threads for protocol handling.
+ ///
+ public static readonly int MaxWorkers = 256;
+
+ ///
+ /// The components to display logging messages for.
+ ///
+ public static readonly LogComponent LogComponents;
+
+ ///
+ /// The type (Verbosity) of messages to log.
+ ///
+ public static readonly LogType LogTypes;
+
+
+ private static void ReadConfiguration()
+ {
+ using (StreamReader configStream = new StreamReader(Path.Combine("Conf", "ifs.cfg")))
+ {
+ //
+ // Config file consists of text lines containing name / value pairs:
+ // =
+ // Whitespace is ignored.
+ //
+ int lineNumber = 0;
+ while (!configStream.EndOfStream)
+ {
+ lineNumber++;
+ string line = configStream.ReadLine().Trim();
+
+ if (string.IsNullOrEmpty(line))
+ {
+ // Empty line, ignore.
+ continue;
+ }
+
+ if (line.StartsWith("#"))
+ {
+ // Comment to EOL, ignore.
+ continue;
+ }
+
+ // Find the '=' separating tokens and ensure there are just two.
+ string[] tokens = line.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
+
+ if (tokens.Length < 2)
+ {
+ Log.Write(LogType.Warning, LogComponent.Configuration,
+ "ifs.cfg line {0}: Invalid syntax.", lineNumber);
+ continue;
+ }
+
+ string parameter = tokens[0].Trim();
+ string value = tokens[1].Trim();
+
+ // Reflect over the public, static properties in this class and see if the parameter matches one of them
+ // If not, it's an error, if it is then we attempt to coerce the value to the correct type.
+ System.Reflection.FieldInfo[] info = typeof(Configuration).GetFields(BindingFlags.Public | BindingFlags.Static);
+
+ bool bMatch = false;
+ foreach (FieldInfo field in info)
+ {
+ // Case-insensitive compare.
+ if (field.Name.ToLowerInvariant() == parameter.ToLowerInvariant())
+ {
+ bMatch = true;
+
+ //
+ // Switch on the type of the field and attempt to convert the value to the appropriate type.
+ // At this time we support only strings and integers.
+ //
+ try
+ {
+ switch (field.FieldType.Name)
+ {
+ case "Int32":
+ {
+ int v = int.Parse(value);
+ field.SetValue(null, v);
+ }
+ break;
+
+ case "String":
+ {
+ field.SetValue(null, value);
+ }
+ break;
+
+ case "LogType":
+ {
+ field.SetValue(null, Enum.Parse(typeof(LogType), value, true));
+ }
+ break;
+
+ case "LogComponent":
+ {
+ field.SetValue(null, Enum.Parse(typeof(LogComponent), value, true));
+ }
+ break;
+
+ }
+ }
+ catch
+ {
+ Log.Write(LogType.Warning, LogComponent.Configuration,
+ "ifs.cfg line {0}: Value '{1}' is invalid for parameter '{2}'.", lineNumber, value, parameter);
+ }
+ }
+
+ }
+
+
+ if (!bMatch)
+ {
+ Log.Write(LogType.Warning, LogComponent.Configuration,
+ "ifs.cfg line {0}: Unknown configuration parameter '{1}'.", lineNumber, parameter);
+ }
+ }
+ }
+ }
}
}
diff --git a/PUP/CopyDisk/CopyDiskServer.cs b/PUP/CopyDisk/CopyDiskServer.cs
index e68db15..11a972c 100644
--- a/PUP/CopyDisk/CopyDiskServer.cs
+++ b/PUP/CopyDisk/CopyDiskServer.cs
@@ -191,84 +191,60 @@ namespace IFS.CopyDisk
public ushort Length;
public ushort Command;
- }
+ }
- public class CopyDiskServer : BSPProtocol
+ public class CopyDiskWorker : BSPWorkerBase
{
- ///
- /// Called by dispatcher to send incoming data destined for this protocol.
- ///
- ///
- public override void RecvData(PUP p)
- {
- throw new NotImplementedException();
- }
-
- public override void InitializeServerForChannel(BSPChannel channel)
- {
- // Spawn new worker
- // TODO: keep track of workers to allow clean shutdown, management, etc.
- CopyDiskWorker worker = new CopyDiskWorker(channel);
- }
- }
-
- public class CopyDiskWorker
- {
- public CopyDiskWorker(BSPChannel channel)
+ public CopyDiskWorker(BSPChannel channel) : base(channel)
{
// Register for channel events
channel.OnDestroy += OnChannelDestroyed;
_running = true;
- _workerThread = new Thread(new ParameterizedThreadStart(CopyDiskWorkerThreadInit));
- _workerThread.Start(channel);
+ _workerThread = new Thread(new ThreadStart(CopyDiskWorkerThreadInit));
+ _workerThread.Start();
}
- private void OnChannelDestroyed()
+ public override void Terminate()
{
- // Tell the thread to exit and give it a short period to do so...
- _running = false;
-
- Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Asking CopyDisk worker thread to exit...");
- _workerThread.Join(1000);
-
- if (_workerThread.IsAlive)
- {
- Logging.Log.Write(LogType.Verbose, LogComponent.CopyDisk, "CopyDisk worker thread did not exit, terminating.");
- _workerThread.Abort();
- }
+ ShutdownWorker();
}
- private void CopyDiskWorkerThreadInit(object obj)
+ private void OnChannelDestroyed(BSPChannel channel)
{
- BSPChannel channel = (BSPChannel)obj;
+ ShutdownWorker();
+ }
+ private void CopyDiskWorkerThreadInit()
+ {
//
// Run the worker thread.
// If anything goes wrong, log the exception and tear down the BSP connection.
//
try
{
- CopyDiskWorkerThread(channel);
+ CopyDiskWorkerThread();
}
catch(Exception e)
{
if (!(e is ThreadAbortException))
{
Logging.Log.Write(LogType.Error, LogComponent.CopyDisk, "CopyDisk worker thread terminated with exception '{0}'.", e.Message);
- channel.SendAbort("Server encountered an error.");
+ _channel.SendAbort("Server encountered an error.");
+
+ OnExit(this);
}
}
}
- private void CopyDiskWorkerThread(BSPChannel channel)
+ private void CopyDiskWorkerThread()
{
// TODO: enforce state (i.e. reject out-of-order block types.)
while (_running)
{
// Retrieve length of this block (in bytes):
- int length = channel.ReadUShort() * 2;
+ int length = _channel.ReadUShort() * 2;
// Sanity check that length is a reasonable value.
if (length > 2048)
@@ -278,11 +254,11 @@ namespace IFS.CopyDisk
}
// Retrieve type
- CopyDiskBlock blockType = (CopyDiskBlock)channel.ReadUShort();
+ CopyDiskBlock blockType = (CopyDiskBlock)_channel.ReadUShort();
// Read rest of block starting at offset 4 (so deserialization works)
byte[] data = new byte[length];
- channel.Read(ref data, data.Length - 4, 4);
+ _channel.Read(ref data, data.Length - 4, 4);
Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Copydisk block type is {0}", blockType);
@@ -296,7 +272,7 @@ namespace IFS.CopyDisk
// Send the response:
VersionYesNoBlock vbOut = new VersionYesNoBlock(CopyDiskBlock.Version, vbIn.Code, "LCM IFS CopyDisk of 26-Jan-2016");
- channel.Send(Serializer.Serialize(vbOut));
+ _channel.Send(Serializer.Serialize(vbOut));
}
break;
@@ -318,7 +294,7 @@ namespace IFS.CopyDisk
// Send a "Yes" response back.
//
VersionYesNoBlock yes = new VersionYesNoBlock(CopyDiskBlock.Yes, 0, "Come on in, the water's fine.");
- channel.Send(Serializer.Serialize(yes));
+ _channel.Send(Serializer.Serialize(yes));
}
break;
@@ -342,7 +318,7 @@ namespace IFS.CopyDisk
{
// Invalid name, return No reponse.
VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Invalid unit name.");
- channel.Send(Serializer.Serialize(no));
+ _channel.Send(Serializer.Serialize(no));
}
else
{
@@ -361,14 +337,14 @@ namespace IFS.CopyDisk
// Send a "HereAreDiskParams" response indicating success.
//
HereAreDiskParamsBFSBlock diskParams = new HereAreDiskParamsBFSBlock(_pack.Geometry);
- channel.Send(Serializer.Serialize(diskParams));
+ _channel.Send(Serializer.Serialize(diskParams));
}
catch
{
// If we fail for any reason, return a "No" response.
// TODO: can we be more helpful here?
VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Image could not be opened.");
- channel.Send(Serializer.Serialize(no));
+ _channel.Send(Serializer.Serialize(no));
}
}
}
@@ -391,7 +367,7 @@ namespace IFS.CopyDisk
{
// Invalid name, return No reponse.
VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Invalid unit name or image already exists.");
- channel.Send(Serializer.Serialize(no));
+ _channel.Send(Serializer.Serialize(no));
}
else
{
@@ -406,7 +382,7 @@ namespace IFS.CopyDisk
// Send a "HereAreDiskParams" response indicating success.
//
HereAreDiskParamsBFSBlock diskParams = new HereAreDiskParamsBFSBlock(_pack.Geometry);
- channel.Send(Serializer.Serialize(diskParams));
+ _channel.Send(Serializer.Serialize(diskParams));
}
}
break;
@@ -440,13 +416,13 @@ namespace IFS.CopyDisk
_endAddress > _pack.MaxAddress)
{
VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnknownCommand, "Transfer parameters are invalid.");
- channel.Send(Serializer.Serialize(no));
+ _channel.Send(Serializer.Serialize(no));
}
else
{
// We're OK. Save the parameters and send a Yes response.
VersionYesNoBlock yes = new VersionYesNoBlock(CopyDiskBlock.Yes, 0, "You are cleared for launch.");
- channel.Send(Serializer.Serialize(yes));
+ _channel.Send(Serializer.Serialize(yes));
//
// And send the requested range of pages if this is a Retrieve operation
@@ -458,7 +434,7 @@ namespace IFS.CopyDisk
{
DiabloDiskSector sector = _pack.GetSector(i);
HereIsDiskPageBlock block = new HereIsDiskPageBlock(sector.Header, sector.Label, sector.Data);
- channel.Send(Serializer.Serialize(block), false /* do not flush */);
+ _channel.Send(Serializer.Serialize(block), false /* do not flush */);
if ((i % 100) == 0)
{
@@ -468,7 +444,7 @@ namespace IFS.CopyDisk
// Send "EndOfTransfer" block to finish the transfer.
EndOfTransferBlock endTransfer = new EndOfTransferBlock(0);
- channel.Send(Serializer.Serialize(endTransfer));
+ _channel.Send(Serializer.Serialize(endTransfer));
Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Send done.");
}
@@ -484,7 +460,7 @@ namespace IFS.CopyDisk
{
if (_currentAddress > _endAddress)
{
- channel.SendAbort("Invalid address for page.");
+ _channel.SendAbort("Invalid address for page.");
_running = false;
break;
}
@@ -552,7 +528,7 @@ namespace IFS.CopyDisk
Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Sending error summary...");
// No data in block. Send list of errors we encountered. (There should always be none since we're perfect and have no disk errors.)
HereAreErrorsBFSBlock errorBlock = new HereAreErrorsBFSBlock(0, 0);
- channel.Send(Serializer.Serialize(errorBlock));
+ _channel.Send(Serializer.Serialize(errorBlock));
}
break;
@@ -560,9 +536,34 @@ namespace IFS.CopyDisk
Log.Write(LogType.Warning, LogComponent.CopyDisk, "Unhandled CopyDisk block {0}", blockType);
break;
}
- }
+ }
+
+ if (OnExit != null)
+ {
+ OnExit(this);
+ }
}
+ private void ShutdownWorker()
+ {
+ // Tell the thread to exit and give it a short period to do so...
+ _running = false;
+
+ Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Asking CopyDisk worker thread to exit...");
+ _workerThread.Join(1000);
+
+ if (_workerThread.IsAlive)
+ {
+ Logging.Log.Write(LogType.Verbose, LogComponent.CopyDisk, "CopyDisk worker thread did not exit, terminating.");
+ _workerThread.Abort();
+
+ if (OnExit != null)
+ {
+ OnExit(this);
+ }
+ }
+ }
+
///
/// Builds a relative path to the directory that holds the disk images.
///
diff --git a/PUP/Entrypoint.cs b/PUP/Entrypoint.cs
index 0185912..20bc8b9 100644
--- a/PUP/Entrypoint.cs
+++ b/PUP/Entrypoint.cs
@@ -2,6 +2,8 @@
using IFS.CopyDisk;
using IFS.FTP;
using IFS.Transport;
+using PcapDotNet.Core;
+using PcapDotNet.Core.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -14,40 +16,94 @@ namespace IFS
public class Entrypoint
{
static void Main(string[] args)
- {
-
- List ifaces = EthernetInterface.EnumerateDevices();
+ {
+ PrintHerald();
- Console.WriteLine("available interfaces are:");
- foreach(EthernetInterface i in ifaces)
- {
- Console.WriteLine(String.Format("{0} - address {1}, desc {2} ", i.Name, i.MacAddress, i.Description));
- }
+ RegisterProtocols();
- NetworkInterface[] netfaces = NetworkInterface.GetAllNetworkInterfaces();
+ RegisterInterface();
+ // This runs forever, or until the user tells us to exit.
+ RunCommandPrompt();
+ }
+
+ private static void PrintHerald()
+ {
+ Console.WriteLine("LCM IFS v0.1, 4/19/2016.");
+ Console.WriteLine();
+ }
+
+ private static void RegisterProtocols()
+ {
// Set up protocols:
// Connectionless
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Gateway Information", 2, ConnectionType.Connectionless, new GatewayInformationProtocol()));
PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Misc Services", 0x4, ConnectionType.Connectionless, new MiscServicesProtocol()));
- PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Echo", 0x5, ConnectionType.Connectionless, new EchoProtocol()));
+ PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Echo", 0x5, ConnectionType.Connectionless, new EchoProtocol()));
// RTP/BSP based:
- PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("CopyDisk", 0x15 /* 25B */, ConnectionType.BSP, new CopyDiskServer()));
- PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("FTP", 0x3, ConnectionType.BSP, new FTPServer()));
+ PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("CopyDisk", 0x15 /* 25B */, ConnectionType.BSP, typeof(CopyDiskWorker)));
+ PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("FTP", 0x3, ConnectionType.BSP, typeof(FTPWorker)));
// Breath Of Life
- BreathOfLife breathOfLifeServer = new BreathOfLife();
+ _breathOfLifeServer = new BreathOfLife();
+ }
+ private static void RegisterInterface()
+ {
+ bool bFound = false;
- // TODO: MAKE THIS CONFIGURABLE.
- PUPProtocolDispatcher.Instance.RegisterInterface(netfaces[0].Description);
-
- while (true)
+ switch (Configuration.InterfaceType.ToLowerInvariant())
{
- System.Threading.Thread.Sleep(100);
+ case "udp":
+ // Find matching network interface
+ {
+ NetworkInterface[] ifaces = NetworkInterface.GetAllNetworkInterfaces();
+ foreach(NetworkInterface iface in ifaces)
+ {
+ if (iface.Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
+ {
+ PUPProtocolDispatcher.Instance.RegisterUDPInterface(iface);
+ bFound = true;
+ break;
+ }
+ }
+ }
+ break;
+
+ case "raw":
+ // Find matching RAW interface
+ {
+ foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
+ {
+ if (device.GetNetworkInterface().Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
+ {
+ PUPProtocolDispatcher.Instance.RegisterRAWInterface(device);
+ bFound = true;
+ break;
+ }
+ }
+ }
+ break;
+ }
+
+ // Not found.
+ if (!bFound)
+ {
+ throw new InvalidConfigurationException("The specified network interface is invalid.");
}
}
+
+ private static void RunCommandPrompt()
+ {
+ while (true)
+ {
+ Console.Write(">>>");
+ string command = Console.ReadLine();
+ }
+ }
+
+ private static BreathOfLife _breathOfLifeServer;
}
}
diff --git a/PUP/FTP/FTPServer.cs b/PUP/FTP/FTPServer.cs
index 2815f2d..e5f0f26 100644
--- a/PUP/FTP/FTPServer.cs
+++ b/PUP/FTP/FTPServer.cs
@@ -77,57 +77,33 @@ namespace IFS.FTP
public byte Code;
public string Message;
- }
+ }
- public class FTPServer : BSPProtocol
+ public class FTPWorker : BSPWorkerBase
{
- ///
- /// Called by dispatcher to send incoming data destined for this protocol.
- ///
- ///
- public override void RecvData(PUP p)
- {
- throw new NotImplementedException();
- }
-
- public override void InitializeServerForChannel(BSPChannel channel)
- {
- // Spawn new worker
- FTPWorker ftpWorker = new FTPWorker(channel);
- }
- }
-
- public class FTPWorker
- {
- public FTPWorker(BSPChannel channel)
+ public FTPWorker(BSPChannel channel) : base(channel)
{
// Register for channel events
channel.OnDestroy += OnChannelDestroyed;
_running = true;
- _workerThread = new Thread(new ParameterizedThreadStart(FTPWorkerThreadInit));
- _workerThread.Start(channel);
+ _workerThread = new Thread(new ThreadStart(FTPWorkerThreadInit));
+ _workerThread.Start();
}
- private void OnChannelDestroyed()
- {
- // Tell the thread to exit and give it a short period to do so...
- _running = false;
-
- Log.Write(LogType.Verbose, LogComponent.FTP, "Asking FTP worker thread to exit...");
- _workerThread.Join(1000);
-
- if (_workerThread.IsAlive)
- {
- Logging.Log.Write(LogType.Verbose, LogComponent.FTP, "FTP worker thread did not exit, terminating.");
- _workerThread.Abort();
- }
+ public override void Terminate()
+ {
+ ShutdownWorker();
}
- private void FTPWorkerThreadInit(object obj)
+ private void OnChannelDestroyed(BSPChannel channel)
{
- _channel = (BSPChannel)obj;
+ ShutdownWorker();
+ }
+
+ private void FTPWorkerThreadInit()
+ {
//
// Run the worker thread.
// If anything goes wrong, log the exception and tear down the BSP connection.
@@ -142,6 +118,8 @@ namespace IFS.FTP
{
Log.Write(LogType.Error, LogComponent.FTP, "FTP worker thread terminated with exception '{0}'.", e.Message);
_channel.SendAbort("Server encountered an error.");
+
+ OnExit(this);
}
}
}
@@ -244,7 +222,9 @@ namespace IFS.FTP
Log.Write(LogType.Warning, LogComponent.FTP, "Unhandled FTP command {0}.", command);
break;
}
- }
+ }
+
+ OnExit(this);
}
private FTPCommand ReadNextCommandWithData(out byte[] data)
@@ -797,7 +777,22 @@ namespace IFS.FTP
_channel.SendMark((byte)FTPCommand.EndOfCommand, true);
}
- private BSPChannel _channel;
+ private void ShutdownWorker()
+ {
+ // Tell the thread to exit and give it a short period to do so...
+ _running = false;
+
+ Log.Write(LogType.Verbose, LogComponent.FTP, "Asking FTP worker thread to exit...");
+ _workerThread.Join(1000);
+
+ if (_workerThread.IsAlive)
+ {
+ Logging.Log.Write(LogType.Verbose, LogComponent.FTP, "FTP worker thread did not exit, terminating.");
+ _workerThread.Abort();
+
+ OnExit(this);
+ }
+ }
private Thread _workerThread;
private bool _running;
diff --git a/PUP/IFS.csproj b/PUP/IFS.csproj
index dd73410..82818ea 100644
--- a/PUP/IFS.csproj
+++ b/PUP/IFS.csproj
@@ -104,6 +104,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/PUP/Logging/Log.cs b/PUP/Logging/Log.cs
index dae89a5..8fe42de 100644
--- a/PUP/Logging/Log.cs
+++ b/PUP/Logging/Log.cs
@@ -24,7 +24,8 @@ namespace IFS.Logging
EFTP = 0x200,
BootServer = 0x400,
UDP = 0x800,
-
+
+ Configuration = 0x1000,
All = 0x7fffffff
}
@@ -48,10 +49,9 @@ namespace IFS.Logging
public static class Log
{
static Log()
- {
- // TODO: make configurable
- _components = LogComponent.All;
- _type = LogType.All;
+ {
+ _components = Configuration.LogComponents;
+ _type = Configuration.LogTypes;
//_logStream = new StreamWriter("log.txt");
}
@@ -62,7 +62,6 @@ namespace IFS.Logging
set { _components = value; }
}
-#if LOGGING_ENABLED
///
/// Logs a message without specifying type/severity for terseness;
/// will not log if Type has been set to None.
@@ -91,18 +90,6 @@ namespace IFS.Logging
}
}
}
-#else
- public static void Write(LogComponent component, string message, params object[] args)
- {
-
- }
-
- public static void Write(LogType type, LogComponent component, string message, params object[] args)
- {
-
- }
-
-#endif
private static LogComponent _components;
private static LogType _type;
diff --git a/PUP/PUPProtocolBase.cs b/PUP/PUPProtocolBase.cs
index 0eeebfa..7494491 100644
--- a/PUP/PUPProtocolBase.cs
+++ b/PUP/PUPProtocolBase.cs
@@ -21,6 +21,16 @@ namespace IFS
Socket = socket;
ConnectionType = connectionType;
ProtocolImplementation = implementation;
+ WorkerType = null;
+ }
+
+ public PUPProtocolEntry(string friendlyName, UInt32 socket, ConnectionType connectionType, Type workerType)
+ {
+ FriendlyName = friendlyName;
+ Socket = socket;
+ ConnectionType = connectionType;
+ WorkerType = workerType;
+ ProtocolImplementation = null;
}
///
@@ -39,6 +49,8 @@ namespace IFS
public ConnectionType ConnectionType;
public PUPProtocolBase ProtocolImplementation;
+
+ public Type WorkerType;
}
///
diff --git a/PUP/PUPProtocolDispatcher.cs b/PUP/PUPProtocolDispatcher.cs
index bbebb64..5fb69de 100644
--- a/PUP/PUPProtocolDispatcher.cs
+++ b/PUP/PUPProtocolDispatcher.cs
@@ -11,6 +11,7 @@ using System.Threading.Tasks;
using PcapDotNet.Base;
using System.Net.NetworkInformation;
+using PcapDotNet.Core;
namespace IFS
{
@@ -35,16 +36,21 @@ namespace IFS
get { return _instance; }
}
- public void RegisterInterface(string description)
+ public void RegisterRAWInterface(LivePacketDevice iface)
{
- // TODO: support multiple interfaces (for gateway routing, for example.)
- // TODO: support configuration options for backend.
- //Ethernet enet = new Ethernet(i.Description);
+ Ethernet enet = new Ethernet(iface);
- UDPEncapsulation udp = new UDPEncapsulation(description);
- _pupPacketInterface = udp as IPupPacketInterface;
- _rawPacketInterface = udp as IRawPacketInterface;
+ _pupPacketInterface = enet;
+ _rawPacketInterface = enet;
+ _pupPacketInterface.RegisterReceiveCallback(OnPupReceived);
+ }
+ public void RegisterUDPInterface(NetworkInterface iface)
+ {
+ UDPEncapsulation udp = new UDPEncapsulation(iface);
+
+ _pupPacketInterface = udp;
+ _rawPacketInterface = udp;
_pupPacketInterface.RegisterReceiveCallback(OnPupReceived);
}
@@ -118,7 +124,7 @@ namespace IFS
Log.Write(LogType.Verbose, LogComponent.PUP, "Dispatching PUP (source {0}, dest {1}) to BSP protocol for {0}.", pup.SourcePort, pup.DestinationPort, entry.FriendlyName);
//entry.ProtocolImplementation.RecvData(pup);
- BSPManager.EstablishRendezvous(pup, (BSPProtocol)entry.ProtocolImplementation);
+ BSPManager.EstablishRendezvous(pup, entry.WorkerType);
}
}
else if (BSPManager.ChannelExistsForSocket(pup))
diff --git a/PUP/Transport/Ethernet.cs b/PUP/Transport/Ethernet.cs
index 51fc4f9..993c88b 100644
--- a/PUP/Transport/Ethernet.cs
+++ b/PUP/Transport/Ethernet.cs
@@ -11,43 +11,19 @@ using PcapDotNet.Packets;
using PcapDotNet.Packets.Ethernet;
using IFS.Logging;
using System.IO;
+using System.Net.NetworkInformation;
+using System.Threading;
namespace IFS.Transport
{
- public struct EthernetInterface
- {
- public EthernetInterface(string name, string description, MacAddress macAddress)
- {
- Name = name;
- Description = description;
- MacAddress = macAddress;
- }
-
- public static List EnumerateDevices()
- {
- List interfaces = new List();
-
- foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
- {
- interfaces.Add(new EthernetInterface(device.Name, device.Description, device.GetMacAddress()));
- }
-
- return interfaces;
- }
-
- public string Name;
- public string Description;
- public MacAddress MacAddress;
- }
-
///
/// Defines interface "to the metal" (raw ethernet frames) which may wrap the underlying transport (for example, winpcap)
///
public class Ethernet : IPupPacketInterface, IRawPacketInterface
{
- public Ethernet(string ifaceName)
+ public Ethernet(LivePacketDevice iface)
{
- AttachInterface(ifaceName);
+ _interface = iface;
// Set up maps
_pupToEthernetMap = new Dictionary(256);
@@ -60,7 +36,10 @@ namespace IFS.Transport
// Now that we have a callback we can start receiving stuff.
Open(false /* not promiscuous */, int.MaxValue);
- BeginReceive();
+
+ // Kick off the receiver thread, this will never return or exit.
+ Thread receiveThread = new Thread(new ThreadStart(BeginReceive));
+ receiveThread.Start();
}
public void Send(PUP p)
@@ -241,27 +220,7 @@ namespace IFS.Transport
// Not a PUP, Discard the packet. We will not log this, so as to keep noise down.
//Log.Write(LogLevel.DroppedPacket, "Not a PUP. Dropping.");
}
- }
-
- private void AttachInterface(string ifaceName)
- {
- _interface = null;
-
- // Find the specified device by name
- foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
- {
- if (device.Description == ifaceName)
- {
- _interface = device;
- break;
- }
- }
-
- if (_interface == null)
- {
- throw new InvalidOperationException("Requested interface not found.");
- }
- }
+ }
private void Open(bool promiscuous, int timeout)
{
diff --git a/PUP/Transport/UDP.cs b/PUP/Transport/UDP.cs
index bbc4501..b2b838a 100644
--- a/PUP/Transport/UDP.cs
+++ b/PUP/Transport/UDP.cs
@@ -15,7 +15,7 @@ namespace IFS.Transport
///
public class UDPEncapsulation : IPupPacketInterface, IRawPacketInterface
{
- public UDPEncapsulation(string interfaceName)
+ public UDPEncapsulation(NetworkInterface iface)
{
// Try to set up UDP client.
try
@@ -23,28 +23,13 @@ namespace IFS.Transport
_udpClient = new UdpClient(_udpPort, AddressFamily.InterNetwork);
_udpClient.Client.Blocking = true;
_udpClient.EnableBroadcast = true;
- _udpClient.MulticastLoopback = false;
+ _udpClient.MulticastLoopback = false;
//
// Grab the broadcast address for the interface so that we know what broadcast address to use
// for our UDP datagrams.
- //
- NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
-
- IPInterfaceProperties props = null;
- foreach (NetworkInterface nic in nics)
- {
- if (nic.Description.ToLowerInvariant() == interfaceName.ToLowerInvariant())
- {
- props = nic.GetIPProperties();
- break;
- }
- }
-
- if (props == null)
- {
- throw new InvalidOperationException(String.Format("No interface matching description '{0}' was found.", interfaceName));
- }
+ //
+ IPInterfaceProperties props = iface.GetIPProperties();
foreach (UnicastIPAddressInformation unicast in props.UnicastAddresses)
{
@@ -60,16 +45,16 @@ namespace IFS.Transport
if (_broadcastEndpoint == null)
{
- throw new InvalidOperationException(String.Format("No IPV4 network information was found for interface '{0}'.", interfaceName));
+ throw new InvalidOperationException(String.Format("No IPV4 network information was found for interface '{0}'.", iface.Name));
}
}
catch (Exception e)
{
Log.Write(LogType.Error, LogComponent.UDP,
- "Error configuring UDP socket {0} for use with ContrAlto on interface {1}. Ensure that the selected network interface is valid, configured properly, and that nothing else is using this port.",
+ "Error configuring UDP socket {0} for use with IFS on interface {1}. Ensure that the selected network interface is valid, configured properly, and that nothing else is using this port.",
_udpPort,
- interfaceName);
+ iface.Name);
Log.Write(LogType.Error, LogComponent.UDP,
"Error was '{0}'.",