1
0
mirror of https://github.com/livingcomputermuseum/IFS.git synced 2026-03-04 18:53:50 +00:00

Cleanup: Changed how worker threads are created and managed, added configuration file (ifs.cfg) and fixed up network interface selection.

This commit is contained in:
Josh Dersch
2016-04-19 17:00:09 -07:00
parent 66d7736472
commit 217388ebda
13 changed files with 516 additions and 252 deletions

View File

@@ -9,6 +9,8 @@ using System.Threading.Tasks;
namespace IFS.BSP
{
public delegate void BSPChannelDestroyDelegate(BSPChannel channel);
/// <summary>
/// Provides functionality for maintaining/terminating BSP connections, and the transfer of data
/// across said connection.
@@ -19,7 +21,7 @@ namespace IFS.BSP
/// </summary>
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<PUP>(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;
/// <summary>
/// The port we use to talk to the client.
@@ -98,13 +98,10 @@ namespace IFS.BSP
/// the channel has been destroyed.
/// </summary>
public void Destroy()
{
_consumerThread.Abort();
if (OnDestroy != null)
{
OnDestroy();
}
{
_destroyed = true;
_consumerThread.Abort();
OnDestroy(this);
}
/// <summary>
@@ -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
/// <param name="message"></param>
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
/// <param name="ack"></param>
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;

View File

@@ -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;
}
/// <summary>
/// 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<uint, BSPChannel>();
_workers = new List<BSPWorkerBase>(Configuration.MaxWorkers);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="p"></param>
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
/// <param name="channel"></param>
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<BSPWorkerBase> _workers;
/// <summary>
/// Map from socket address to BSP channel

17
PUP/Conf/ifs.cfg Normal file
View File

@@ -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

View File

@@ -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)
{
}
}
/// <summary>
/// Encapsulates global server configuration information.
///
/// TODO: read in configuration from a text file.
/// TODO also: make cross-platform compatible (no hardcoding of path delimiters).
/// </summary>
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.");
}
}
/// <summary>
/// The type of interface (UDP or RAW) to communicate over
/// </summary>
public static readonly string InterfaceType;
/// <summary>
/// The name of the network interface to use
/// </summary>
public static readonly string InterfaceName;
/// <summary>
/// The root directory for the FTP file store.
/// </summary>
public static readonly string FTPRoot = "C:\\ifs\\ftp";
public static readonly string FTPRoot;
/// <summary>
/// The root directory for the CopyDisk file store.
/// </summary>
public static readonly string CopyDiskRoot = "C:\\ifs\\copydisk";
public static readonly string CopyDiskRoot;
/// <summary>
/// The root directory for the Boot file store.
/// </summary>
public static readonly string BootRoot = "C:\\ifs\\boot";
public static readonly string BootRoot;
/// <summary>
/// The maximum number of worker threads for protocol handling.
/// </summary>
public static readonly int MaxWorkers = 256;
/// <summary>
/// The components to display logging messages for.
/// </summary>
public static readonly LogComponent LogComponents;
/// <summary>
/// The type (Verbosity) of messages to log.
/// </summary>
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:
// <Name>=<Value>
// 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);
}
}
}
}
}
}

View File

@@ -191,84 +191,60 @@ namespace IFS.CopyDisk
public ushort Length;
public ushort Command;
}
}
public class CopyDiskServer : BSPProtocol
public class CopyDiskWorker : BSPWorkerBase
{
/// <summary>
/// Called by dispatcher to send incoming data destined for this protocol.
/// </summary>
/// <param name="p"></param>
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);
}
}
}
/// <summary>
/// Builds a relative path to the directory that holds the disk images.
/// </summary>

View File

@@ -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<EthernetInterface> 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;
}
}

View File

@@ -77,57 +77,33 @@ namespace IFS.FTP
public byte Code;
public string Message;
}
}
public class FTPServer : BSPProtocol
public class FTPWorker : BSPWorkerBase
{
/// <summary>
/// Called by dispatcher to send incoming data destined for this protocol.
/// </summary>
/// <param name="p"></param>
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;

View File

@@ -104,6 +104,9 @@
<Content Include="Conf\bootdirectory.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="Conf\ifs.cfg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="Conf\hosts.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View File

@@ -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
/// <summary>
/// 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;

View File

@@ -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;
}
/// <summary>
@@ -39,6 +49,8 @@ namespace IFS
public ConnectionType ConnectionType;
public PUPProtocolBase ProtocolImplementation;
public Type WorkerType;
}
/// <summary>

View File

@@ -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))

View File

@@ -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<EthernetInterface> EnumerateDevices()
{
List<EthernetInterface> interfaces = new List<EthernetInterface>();
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;
}
/// <summary>
/// Defines interface "to the metal" (raw ethernet frames) which may wrap the underlying transport (for example, winpcap)
/// </summary>
public class Ethernet : IPupPacketInterface, IRawPacketInterface
{
public Ethernet(string ifaceName)
public Ethernet(LivePacketDevice iface)
{
AttachInterface(ifaceName);
_interface = iface;
// Set up maps
_pupToEthernetMap = new Dictionary<byte, MacAddress>(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)
{

View File

@@ -15,7 +15,7 @@ namespace IFS.Transport
/// </summary>
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}'.",