diff --git a/PUP/Authentication.cs b/PUP/Authentication.cs
index b97184d..4c33e94 100644
--- a/PUP/Authentication.cs
+++ b/PUP/Authentication.cs
@@ -9,8 +9,6 @@ using System.Threading.Tasks;
namespace IFS
{
-
-
///
/// Provides very (very) rudimentary security.
/// Exposes facilities for user authentication (via password) and
@@ -20,11 +18,14 @@ namespace IFS
/// (or deal with the security issues that would entail)
/// so IFS usernames/passwords are completely separate entities from Windows auth
/// and access is controlled very coarsely. (More fine-grained ACLs are really overkill for the
- /// use-cases we need for IFS).
+ /// use-cases we need for IFS, at least at this time.)
///
/// Accounts are split into two categories: Users and Administrators.
/// Users can read any file, but can only write files in their home directory.
/// Administrators can read/write files in any directory.
+ ///
+ /// The concept of a "guest" account is provided -- this user has no home directory and has read-only
+ /// access only to specifically marked public directories.
///
public static class Authentication
{
@@ -51,8 +52,7 @@ namespace IFS
}
public static UserToken Authenticate(string userName, string password)
- {
-
+ {
//
// Look up the user
//
@@ -212,6 +212,7 @@ namespace IFS
///
/// Given a full user name (i.e. username.HOST), returns only the username portion.
+ /// (Given just a username, returns it unchanged.)
///
///
///
diff --git a/PUP/BSP/BSPChannel.cs b/PUP/BSP/BSPChannel.cs
index b83d81c..b0be368 100644
--- a/PUP/BSP/BSPChannel.cs
+++ b/PUP/BSP/BSPChannel.cs
@@ -1,4 +1,5 @@
-using IFS.Logging;
+using IFS.Gateway;
+using IFS.Logging;
using System;
using System.Collections.Generic;
@@ -111,11 +112,11 @@ namespace IFS.BSP
public void End(PUP p)
{
PUP endReplyPup = new PUP(PupType.EndReply, p.ID, _clientConnectionPort, _serverConnectionPort, new byte[0]);
- PUPProtocolDispatcher.Instance.SendPup(endReplyPup);
+ Router.Instance.SendPup(endReplyPup);
// "The receiver of the End PUP responds by returning an EndReply Pup with matching ID and then
// _dallying_ up to some reasonably long timeout interval (say, 10 seconds) in order to respond to
- // a retransmitted End Pup should its initial EndReply be lost. If and when the dallying end of the
+ // a retransmitted End Pup should its initial EndReply be lost. If and when the dallying end of the
// stream connection receives its EndReply, it may immediately self destruct."
// TODO: actually make this happen...
@@ -123,6 +124,7 @@ namespace IFS.BSP
///
/// Reads data from the channel (i.e. from the client). Will block if not all the requested data is available.
+ /// If a Mark byte is encountered, will return a short read.
///
///
public int Read(ref byte[] data, int count)
@@ -305,18 +307,8 @@ namespace IFS.BSP
}
}
- // If we are over our high watermark, we will drop the data (and not send an ACK even if requested).
- // Clients should be honoring the limits we set in the RFC packets.
_inputLock.EnterUpgradeableReadLock();
-
- /*
- if (_inputQueue.Count + dataPUP.Contents.Length > MaxBytes)
- {
- Log.Write(LogLevel.Error, "Queue larger than {0} bytes, dropping.");
- _inputLock.ExitUpgradeableReadLock();
- return;
- } */
-
+
// Sanity check on expected position from sender vs. received data on our end.
// If they don't match then we've lost a packet somewhere.
if (dataPUP.ID != _recvPos)
@@ -421,7 +413,7 @@ namespace IFS.BSP
}
// Send the data.
- PUP dataPup = new PUP(flush? PupType.AData : PupType.Data, _sendPos, _clientConnectionPort, _serverConnectionPort, chunk);
+ PUP dataPup = new PUP(flush ? PupType.AData : PupType.Data, _sendPos, _clientConnectionPort, _serverConnectionPort, chunk);
SendDataPup(dataPup);
}
}
@@ -444,7 +436,7 @@ namespace IFS.BSP
// Send this directly, do not wait for the client to be ready (since it may be wedged, and we don't expect anyone to actually notice
// this anyway).
//
- PUPProtocolDispatcher.Instance.SendPup(abortPup);
+ Router.Instance.SendPup(abortPup);
}
///
@@ -503,7 +495,7 @@ namespace IFS.BSP
PUP ackPup = new PUP(PupType.Ack, _recvPos, _clientConnectionPort, _serverConnectionPort, Serializer.Serialize(ack));
- PUPProtocolDispatcher.Instance.SendPup(ackPup);
+ Router.Instance.SendPup(ackPup);
Log.Write(LogType.Verbose, LogComponent.BSP, "ACK sent.");
}
@@ -551,7 +543,7 @@ namespace IFS.BSP
// Send an empty AData PUP to keep the connection alive and to update the client data stats.
//
PUP aData = new PUP(PupType.AData, _sendPos, _clientConnectionPort, _serverConnectionPort, new byte[0]);
- PUPProtocolDispatcher.Instance.SendPup(aData);
+ Router.Instance.SendPup(aData);
}
///
@@ -656,7 +648,7 @@ namespace IFS.BSP
// Send it!
//
_sendPos += (uint)nextPup.Contents.Length;
- PUPProtocolDispatcher.Instance.SendPup(nextPup);
+ Router.Instance.SendPup(nextPup);
Log.Write(LogType.Verbose, LogComponent.BSP, "Sent data PUP. Current position is {0}, output window count is {1}", _sendPos, _outputWindow.Count);
diff --git a/PUP/BSP/BSPManager.cs b/PUP/BSP/BSPManager.cs
index 8074c98..b63a7bc 100644
--- a/PUP/BSP/BSPManager.cs
+++ b/PUP/BSP/BSPManager.cs
@@ -1,4 +1,5 @@
-using IFS.Logging;
+using IFS.Gateway;
+using IFS.Logging;
using System;
using System.Collections.Generic;
using System.IO;
@@ -23,39 +24,39 @@ namespace IFS.BSP
{
public BSPWorkerBase(BSPChannel channel)
{
- _channel = channel;
+ Channel = channel;
}
public abstract void Terminate();
public WorkerExitDelegate OnExit;
- protected BSPChannel _channel;
+ public BSPChannel Channel;
}
///
- /// Manages active BSP channels and creates new ones as necessary, invoking the associated
- /// protocol handlers.
+ /// Manages active BSP channels and creates new ones as necessary, invoking the protocol handler
+ /// associated with the socket.
+ ///
/// Dispatches PUPs to the appropriate BSP channel.
///
public static class BSPManager
{
static BSPManager()
- {
- //
- // Initialize the socket ID counter; we start with a
- // number beyond the range of well-defined sockets.
- // For each new BSP channel that gets opened, we will
- // increment this counter to ensure that each channel gets
- // a unique ID. (Well, until we wrap around...)
- //
- _nextSocketID = _startingSocketID;
-
+ {
_activeChannels = new Dictionary();
_workers = new List(Configuration.MaxWorkers);
}
+ public static void Shutdown()
+ {
+ foreach(BSPWorkerBase worker in _workers)
+ {
+ worker.Terminate();
+ }
+ }
+
///
/// 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.
@@ -68,8 +69,8 @@ namespace IFS.BSP
Log.Write(LogType.Error, LogComponent.RTP, "Expected RFC pup, got {0}", p.Type);
return;
}
-
- UInt32 socketID = GetNextSocketID();
+
+ UInt32 socketID = SocketIDGenerator.GetNextSocketID();
BSPChannel newChannel = new BSPChannel(p, socketID);
newChannel.OnDestroy += OnChannelDestroyed;
_activeChannels.Add(socketID, newChannel);
@@ -78,7 +79,7 @@ namespace IFS.BSP
// Initialize the worker for this channel.
InitializeWorkerForChannel(newChannel, workerType);
- // Send RFC response to complete the rendezvous.
+ // Send RFC response to complete the rendezvous:
// Modify the destination port to specify our network
PUPPort sourcePort = p.DestinationPort;
@@ -89,7 +90,7 @@ namespace IFS.BSP
"Establishing Rendezvous, ID {0}, Server port {1}, Client port {2}.",
p.ID, newChannel.ServerPort, newChannel.ClientPort);
- PUPProtocolDispatcher.Instance.SendPup(rfcResponse);
+ Router.Instance.SendPup(rfcResponse);
}
///
@@ -203,6 +204,20 @@ namespace IFS.BSP
_activeChannels.Remove(channel.ServerPort.Socket);
}
+ public static List EnumerateActiveWorkers()
+ {
+ return _workers;
+
+ }
+
+ public static int WorkerCount
+ {
+ get
+ {
+ return _workers.Count();
+ }
+ }
+
///
/// Finds the appropriate channel for the given PUP.
///
@@ -218,31 +233,7 @@ namespace IFS.BSP
{
return null;
}
- }
-
- ///
- /// Generates a unique Socket ID.
- ///
- ///
- private static UInt32 GetNextSocketID()
- {
- UInt32 next = _nextSocketID;
-
- _nextSocketID++;
-
- //
- // Handle the wrap around case (which we're very unlikely to
- // ever hit, but why not do the right thing).
- // Start over at the initial ID. This is very unlikely to
- // collide with any pending channels.
- //
- if(_nextSocketID < _startingSocketID)
- {
- _nextSocketID = _startingSocketID;
- }
-
- return next;
- }
+ }
private static void InitializeWorkerForChannel(BSPChannel channel, Type workerType)
{
@@ -260,15 +251,7 @@ namespace IFS.BSP
// Send an Abort with an informative message.
channel.SendAbort("IFS Server full, try again later.");
}
- }
-
- public static int WorkerCount
- {
- get
- {
- return _workers.Count();
- }
- }
+ }
private static void OnWorkerExit(BSPWorkerBase destroyed)
{
@@ -284,8 +267,5 @@ namespace IFS.BSP
/// Map from socket address to BSP channel
///
private static Dictionary _activeChannels;
-
- private static UInt32 _nextSocketID;
- private static readonly UInt32 _startingSocketID = 0x1000;
}
}
diff --git a/PUP/Boot/BootServer.cs b/PUP/Boot/BootServer.cs
index a3e221d..e51d0a5 100644
--- a/PUP/Boot/BootServer.cs
+++ b/PUP/Boot/BootServer.cs
@@ -29,6 +29,7 @@ namespace IFS.Boot
{
_numberToNameTable = new Dictionary();
+ // TODO: fix hardcoded path
using (StreamReader sr = new StreamReader("Conf\\bootdirectory.txt"))
{
int lineNumber = 0;
@@ -66,7 +67,7 @@ namespace IFS.Boot
catch
{
Log.Write(LogType.Warning, LogComponent.BootServer,
- "bootdirectory.txt line {0}: Invalid syntax: '{1}' is not a valid integer value.", lineNumber, tokens[0]);
+ "bootdirectory.txt line {0}: Invalid syntax: '{1}' is not a valid octal integer value.", lineNumber, tokens[0]);
continue;
}
diff --git a/PUP/Boot/BreathOfLife.cs b/PUP/Boot/BreathOfLife.cs
index 145383b..9d7e034 100644
--- a/PUP/Boot/BreathOfLife.cs
+++ b/PUP/Boot/BreathOfLife.cs
@@ -1,4 +1,5 @@
-using IFS.Logging;
+using IFS.Gateway;
+using IFS.Logging;
using System;
using System.Collections.Generic;
@@ -22,6 +23,11 @@ namespace IFS
_bolThread.Start();
}
+ public void Shutdown()
+ {
+ _bolThread.Abort();
+ }
+
private void BreathOfLifeThread()
{
while (true)
@@ -29,7 +35,7 @@ namespace IFS
//
// Send BOL
//
- PUPProtocolDispatcher.Instance.Send(_bolPacket, DirectoryServices.Instance.LocalHost, _bolAddress, _bolPacketType);
+ Router.Instance.Send(_bolPacket, DirectoryServices.Instance.LocalHost, _bolAddress, _bolPacketType);
Log.Write(LogType.Verbose, LogComponent.BreathOfLife, "Breath Of Life packet sent.");
@@ -54,7 +60,7 @@ namespace IFS
///
/// The gold-standard BOL packet, containing the Alto ethernet bootstrap code.
- /// Note that this does not contain padding for the ethernet header, the dispatcher adds those two words.
+ /// Note that this does not contain padding for the ethernet header, the router adds those two words.
///
private byte[] _bolPacket =
{
diff --git a/PUP/Configuration.cs b/PUP/Configuration.cs
index 4d5346f..8c4c4c5 100644
--- a/PUP/Configuration.cs
+++ b/PUP/Configuration.cs
@@ -198,7 +198,6 @@ namespace IFS
field.SetValue(null, Enum.Parse(typeof(LogComponent), value, true));
}
break;
-
}
}
catch
@@ -207,10 +206,8 @@ namespace IFS
"ifs.cfg line {0}: Value '{1}' is invalid for parameter '{2}'.", lineNumber, value, parameter);
}
}
-
}
-
if (!bMatch)
{
Log.Write(LogType.Warning, LogComponent.Configuration,
diff --git a/PUP/Console/Console.cs b/PUP/Console/Console.cs
index 7fa584a..19e6a6e 100644
--- a/PUP/Console/Console.cs
+++ b/PUP/Console/Console.cs
@@ -145,7 +145,8 @@ namespace IFS.IfsConsole
public void Run()
{
- while (true)
+ bool exit = false;
+ while (!exit)
{
try
{
@@ -154,7 +155,7 @@ namespace IFS.IfsConsole
if (command != String.Empty)
{
- ExecuteLine(command);
+ exit = ExecuteLine(command);
}
}
catch (Exception e)
@@ -164,8 +165,10 @@ namespace IFS.IfsConsole
}
}
- private void ExecuteLine(string line)
- {
+ private bool ExecuteLine(string line)
+ {
+ bool exit = false;
+
// Comments start with "#"
if (line.StartsWith("#"))
{
@@ -180,17 +183,19 @@ namespace IFS.IfsConsole
{
// Not a command.
Console.WriteLine("Invalid command.");
-
}
else
{
- InvokeConsoleMethod(methods, args);
+ exit = InvokeConsoleMethod(methods, args);
}
}
+
+ return exit;
}
- private void InvokeConsoleMethod(List methods, string[] args)
+ private bool InvokeConsoleMethod(List methods, string[] args)
{
+ bool exit = false;
MethodInfo method = null;
//
@@ -306,8 +311,7 @@ namespace IFS.IfsConsole
//
object instance = GetInstanceFromMethod(method);
- method.Invoke(instance, invokeParams);
-
+ return (bool)method.Invoke(instance, invokeParams);
}
private object GetInstanceFromMethod(MethodInfo method)
diff --git a/PUP/Console/ConsoleCommands.cs b/PUP/Console/ConsoleCommands.cs
index 66135c2..86329eb 100644
--- a/PUP/Console/ConsoleCommands.cs
+++ b/PUP/Console/ConsoleCommands.cs
@@ -27,16 +27,18 @@ namespace IFS.IfsConsole
}
[ConsoleFunction("show users", "Displays the current user database")]
- private void ShowUsers()
+ private bool ShowUsers()
{
foreach(UserToken user in Authentication.EnumerateUsers())
{
Console.WriteLine("{0}: ({1}) - {2},{3}", user.UserName, user.Privileges, user.FullName, user.HomeDirectory);
}
+
+ return false;
}
[ConsoleFunction("show user", "Displays information for the specified user")]
- private void ShowUser(string username)
+ private bool ShowUser(string username)
{
UserToken user = Authentication.GetUser(username);
@@ -48,10 +50,12 @@ namespace IFS.IfsConsole
{
Console.WriteLine("{0}: ({1}) - {2},{3}", user.UserName, user.Privileges, user.FullName, user.HomeDirectory);
}
+
+ return false;
}
[ConsoleFunction("set password", "Sets the password for the specified user")]
- private void SetPassword(string username, string newPassword)
+ private bool SetPassword(string username)
{
UserToken user = Authentication.GetUser(username);
@@ -61,12 +65,27 @@ namespace IFS.IfsConsole
}
else
{
- Authentication.SetPassword(username, newPassword);
+ Console.Write("Enter new password:");
+ string newPassword = Console.ReadLine();
+
+ Console.Write("Confirm new password:");
+ string confPassword = Console.ReadLine();
+
+ if (newPassword != confPassword)
+ {
+ Console.WriteLine("Passwords do not match, password not changed.");
+ }
+ else
+ {
+ Authentication.SetPassword(username, newPassword);
+ }
}
+
+ return false;
}
- [ConsoleFunction("add user", "Adds a new user", " [User|Admin] ")]
- private void AddUser(string username, string newPassword, string privileges, string fullName, string homeDir)
+ [ConsoleFunction("add user", "Adds a new user account", " [User|Admin] ")]
+ private bool AddUser(string username, string newPassword, string privileges, string fullName, string homeDir)
{
IFSPrivileges privs = IFSPrivileges.ReadOnly;
@@ -89,6 +108,51 @@ namespace IFS.IfsConsole
{
Console.WriteLine("User already exists.");
}
+
+ return false;
+ }
+
+ [ConsoleFunction("remove user", "Removes an existing user account", "")]
+ private bool RemoveUser(string username)
+ {
+ if (Authentication.RemoveUser(username))
+ {
+ Console.WriteLine("User removed.");
+ }
+ else
+ {
+ Console.WriteLine("User could not be removed.");
+ }
+
+ return false;
+ }
+
+ [ConsoleFunction("show active servers", "Displays active server statistics.", "")]
+ private bool ShowServers()
+ {
+ List workers = BSP.BSPManager.EnumerateActiveWorkers();
+
+ Console.WriteLine("BSP Channels:");
+ foreach(BSP.BSPWorkerBase w in workers)
+ {
+ Console.WriteLine("{0} - Client port {1}, Server port {2}", w.GetType(), w.Channel.ClientPort, w.Channel.ServerPort);
+ }
+
+ IEnumerable channels = EFTP.EFTPManager.EnumerateActiveChannels();
+
+ Console.WriteLine("EFTP Channels:");
+ foreach (EFTP.EFTPChannel c in channels)
+ {
+ Console.WriteLine("EFTP - Client port {0}, Server port {1}", c.ClientPort, c.ServerPort);
+ }
+
+ return false;
+ }
+
+ [ConsoleFunction("quit", "Terminates the IFS process", "")]
+ private bool Quit()
+ {
+ return true;
}
private static ConsoleCommands _instance;
diff --git a/PUP/CopyDisk/CopyDiskServer.cs b/PUP/CopyDisk/CopyDiskServer.cs
index 275a4bb..9332d2d 100644
--- a/PUP/CopyDisk/CopyDiskServer.cs
+++ b/PUP/CopyDisk/CopyDiskServer.cs
@@ -235,7 +235,7 @@ namespace IFS.CopyDisk
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);
}
@@ -248,7 +248,7 @@ namespace IFS.CopyDisk
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)
@@ -258,11 +258,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);
@@ -276,7 +276,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;
@@ -298,7 +298,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));
}
else
{
@@ -306,7 +306,7 @@ namespace IFS.CopyDisk
// Send a "No" response back indicating the login failure.
//
VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.IllegalOrIncorrectPassword, "Invalid username or password.");
- _channel.Send(Serializer.Serialize(no), true);
+ Channel.Send(Serializer.Serialize(no), true);
}
}
break;
@@ -331,7 +331,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
{
@@ -350,14 +350,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));
}
}
}
@@ -380,7 +380,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
{
@@ -395,7 +395,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;
@@ -427,7 +427,7 @@ namespace IFS.CopyDisk
if (_userToken.Privileges != IFSPrivileges.ReadWrite)
{
VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitWriteProtected, "You do not have permission to store disk images.");
- _channel.Send(Serializer.Serialize(no));
+ Channel.Send(Serializer.Serialize(no));
break;
}
}
@@ -440,13 +440,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 +458,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 +468,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 +484,7 @@ namespace IFS.CopyDisk
{
if (_currentAddress > _endAddress)
{
- _channel.SendAbort("Invalid address for page.");
+ Channel.SendAbort("Invalid address for page.");
_running = false;
break;
}
@@ -552,7 +552,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;
diff --git a/PUP/DirectoryServices.cs b/PUP/DirectoryServices.cs
index 70a57e2..c56ad34 100644
--- a/PUP/DirectoryServices.cs
+++ b/PUP/DirectoryServices.cs
@@ -34,8 +34,7 @@ namespace IFS
{
private DirectoryServices()
{
- // Get our host address; for now just hardcode it.
- // TODO: need to define config files, etc.
+ // Get our host address from the configuration database.
_localHost = new HostAddress((byte)Configuration.ServerNetwork, (byte)Configuration.ServerHost);
// Load in hosts table from hosts file.
diff --git a/PUP/EFTP/EFTPChannel.cs b/PUP/EFTP/EFTPChannel.cs
index 4af01bf..5481848 100644
--- a/PUP/EFTP/EFTPChannel.cs
+++ b/PUP/EFTP/EFTPChannel.cs
@@ -1,4 +1,5 @@
-using IFS.Logging;
+using IFS.Gateway;
+using IFS.Logging;
using System;
using System.Collections.Generic;
@@ -32,6 +33,11 @@ namespace IFS.EFTP
get { return _serverConnectionPort; }
}
+ public PUPPort ClientPort
+ {
+ get { return _clientConnectionPort; }
+ }
+
public delegate void DestroyDelegate();
public DestroyDelegate OnDestroy;
@@ -81,7 +87,7 @@ namespace IFS.EFTP
// Send the data.
PUP dataPup = new PUP(PupType.EFTPData, _sendPos, _clientConnectionPort, _serverConnectionPort, chunk);
- PUPProtocolDispatcher.Instance.SendPup(dataPup);
+ Router.Instance.SendPup(dataPup);
// Await an ACK. We will retry several times and resend as necessary.
int retry = 0;
@@ -95,7 +101,7 @@ namespace IFS.EFTP
else
{
// timeout: resend the PUP and wait for an ACK again.
- PUPProtocolDispatcher.Instance.SendPup(dataPup);
+ Router.Instance.SendPup(dataPup);
}
}
@@ -135,7 +141,7 @@ namespace IFS.EFTP
public void SendEnd()
{
PUP endPup = new PUP(PupType.EFTPEnd, _sendPos, _clientConnectionPort, _serverConnectionPort, new byte[0]);
- PUPProtocolDispatcher.Instance.SendPup(endPup);
+ Router.Instance.SendPup(endPup);
// Await an ack
_outputAckEvent.WaitOne(EFTPAckTimeoutPeriod);
@@ -144,7 +150,7 @@ namespace IFS.EFTP
// Send another end to close things off.
endPup = new PUP(PupType.EFTPEnd, _sendPos, _clientConnectionPort, _serverConnectionPort, new byte[0]);
- PUPProtocolDispatcher.Instance.SendPup(endPup);
+ Router.Instance.SendPup(endPup);
}
public void RecvData(PUP p)
@@ -186,7 +192,7 @@ namespace IFS.EFTP
// Send this directly, do not wait for the client to be ready (since it may be wedged, and we don't expect anyone to actually notice
// this anyway).
//
- PUPProtocolDispatcher.Instance.SendPup(abortPup);
+ Router.Instance.SendPup(abortPup);
*/
}
diff --git a/PUP/EFTP/EFTPManager.cs b/PUP/EFTP/EFTPManager.cs
index c111219..1942545 100644
--- a/PUP/EFTP/EFTPManager.cs
+++ b/PUP/EFTP/EFTPManager.cs
@@ -19,21 +19,20 @@ namespace IFS.EFTP
{
static EFTPManager()
{
- //
- // Initialize the socket ID counter; we start with a
- // number beyond the range of well-defined sockets.
- // For each new EFTP channel that gets opened, we will
- // increment this counter to ensure that each channel gets
- // a unique ID. (Well, until we wrap around...)
- //
- _nextSocketID = _startingSocketID;
-
_activeChannels = new Dictionary();
-
}
+
+ public static void Shutdown()
+ {
+ foreach(EFTPChannel channel in _activeChannels.Values)
+ {
+ channel.Destroy();
+ }
+ }
+
public static void SendFile(PUPPort destination, Stream data)
{
- UInt32 socketID = GetNextSocketID();
+ UInt32 socketID = SocketIDGenerator.GetNextSocketID();
EFTPChannel newChannel = new EFTPChannel(destination, socketID);
_activeChannels.Add(socketID, newChannel);
@@ -107,6 +106,11 @@ namespace IFS.EFTP
_activeChannels.Remove(channel.ServerPort.Socket);
}
+ public static IEnumerable EnumerateActiveChannels()
+ {
+ return _activeChannels.Values;
+ }
+
///
/// Finds the appropriate channel for the given PUP.
///
@@ -124,40 +128,10 @@ namespace IFS.EFTP
}
}
- ///
- /// Generates a unique Socket ID. We count downward from the max value
- /// (whereas BSP counts upwards) so as to avoid any duplicates.
- /// TODO: this could be done much more sanely via a centralized ID factory...
- ///
- ///
- private static UInt32 GetNextSocketID()
- {
- UInt32 next = _nextSocketID;
-
- _nextSocketID--;
-
- //
- // Handle the wrap around case (which we're very unlikely to
- // ever hit, but why not do the right thing).
- // Start over at the initial ID. This is very unlikely to
- // collide with any pending channels.
- //
- if (_nextSocketID < 0x1000)
- {
- _nextSocketID = _startingSocketID;
- }
-
- return next;
- }
-
-
///
/// Map from socket address to BSP channel
///
private static Dictionary _activeChannels;
-
- private static UInt32 _nextSocketID;
- private static readonly UInt32 _startingSocketID = UInt32.MaxValue;
}
diff --git a/PUP/EFTP/EFTPSend.cs b/PUP/EFTP/EFTPSend.cs
index 59dd1a2..0d6645f 100644
--- a/PUP/EFTP/EFTPSend.cs
+++ b/PUP/EFTP/EFTPSend.cs
@@ -22,10 +22,11 @@ namespace IFS.EFTP
{
_data = data;
_channel = channel;
+ _sendDone = false;
_workerThread = new Thread(SendWorker);
_workerThread.Start();
-
+
channel.OnDestroy += OnChannelDestroyed;
}
@@ -49,17 +50,25 @@ namespace IFS.EFTP
}
}
_data.Close();
+ _sendDone = true;
+
+ EFTPManager.DestroyChannel(_channel);
}
private void OnChannelDestroyed()
{
- _workerThread.Abort();
- _data.Close();
+ if (!_sendDone)
+ {
+ _workerThread.Abort();
+ _data.Close();
+ }
}
private Thread _workerThread;
private EFTPChannel _channel;
private Stream _data;
+
+ private bool _sendDone;
}
}
diff --git a/PUP/EchoProtocol.cs b/PUP/EchoProtocol.cs
index c736f07..2ca5fd0 100644
--- a/PUP/EchoProtocol.cs
+++ b/PUP/EchoProtocol.cs
@@ -1,4 +1,5 @@
-using System;
+using IFS.Gateway;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -50,7 +51,7 @@ namespace IFS
}
PUP echoPup = new PUP(PupType.ImAnEcho, p.ID, p.SourcePort, localPort, contents, garbageByte);
- PUPProtocolDispatcher.Instance.SendPup(echoPup);
+ Router.Instance.SendPup(echoPup);
}
}
diff --git a/PUP/Entrypoint.cs b/PUP/Entrypoint.cs
index c00306c..95d78fb 100644
--- a/PUP/Entrypoint.cs
+++ b/PUP/Entrypoint.cs
@@ -1,6 +1,7 @@
using IFS.Boot;
using IFS.CopyDisk;
using IFS.FTP;
+using IFS.Gateway;
using IFS.IfsConsole;
using IFS.Transport;
using PcapDotNet.Core;
@@ -18,39 +19,24 @@ namespace IFS
{
static void Main(string[] args)
{
- PrintHerald();
-
- RegisterProtocols();
+ PrintHerald();
RegisterInterface();
// This runs forever, or until the user tells us to exit.
- RunCommandPrompt();
+ RunCommandPrompt();
+
+ // Shut things down
+ Console.WriteLine("Shutting down, please wait...");
+ Router.Instance.Shutdown();
+
}
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()));
-
- // RTP/BSP based:
- PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("CopyDisk", 0x15 /* 25B */, ConnectionType.BSP, typeof(CopyDiskWorker)));
- PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("FTP", 0x3, ConnectionType.BSP, typeof(FTPWorker)));
- PUPProtocolDispatcher.Instance.RegisterProtocol(new PUPProtocolEntry("Mail", 0x7, ConnectionType.BSP, typeof(FTPWorker)));
-
- // Breath Of Life
- _breathOfLifeServer = new BreathOfLife();
- }
+ }
private static void RegisterInterface()
{
@@ -66,7 +52,7 @@ namespace IFS
{
if (iface.Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
{
- PUPProtocolDispatcher.Instance.RegisterUDPInterface(iface);
+ Router.Instance.RegisterUDPInterface(iface);
bFound = true;
break;
}
@@ -81,7 +67,7 @@ namespace IFS
{
if (device.GetNetworkInterface().Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
{
- PUPProtocolDispatcher.Instance.RegisterRAWInterface(device);
+ Router.Instance.RegisterRAWInterface(device);
bFound = true;
break;
}
@@ -101,7 +87,5 @@ namespace IFS
{
ConsoleExecutor.Instance.Run();
}
-
- private static BreathOfLife _breathOfLifeServer;
}
}
diff --git a/PUP/FTP/FTPServer.cs b/PUP/FTP/FTPServer.cs
index 556e3f6..0e9a60b 100644
--- a/PUP/FTP/FTPServer.cs
+++ b/PUP/FTP/FTPServer.cs
@@ -131,7 +131,7 @@ namespace IFS.FTP
if (!(e is ThreadAbortException))
{
Log.Write(LogType.Error, LogComponent.FTP, "FTP worker thread terminated with exception '{0}'.", e.Message);
- _channel.SendAbort("Server encountered an error.");
+ Channel.SendAbort("Server encountered an error.");
OnExit(this);
}
@@ -300,7 +300,7 @@ namespace IFS.FTP
{
// Discard input until we get a Mark. We should (in general) get a
// command, followed by EndOfCommand.
- FTPCommand command = (FTPCommand)_channel.WaitForMark();
+ FTPCommand command = (FTPCommand)Channel.WaitForMark();
data = ReadNextCommandData();
@@ -344,7 +344,7 @@ namespace IFS.FTP
while(true)
{
- int length = _channel.Read(ref buffer, buffer.Length);
+ int length = Channel.Read(ref buffer, buffer.Length);
ms.Write(buffer, 0, length);
@@ -363,7 +363,7 @@ namespace IFS.FTP
}
data = ms.ToArray();
- return (FTPCommand)_channel.LastMark;
+ return (FTPCommand)Channel.LastMark;
}
///
@@ -397,8 +397,8 @@ namespace IFS.FTP
sb.Append(matchingFile.ToString());
}
- _channel.SendMark((byte)FTPCommand.HereIsPropertyList, false);
- _channel.Send(Helpers.StringToArray(sb.ToString()));
+ Channel.SendMark((byte)FTPCommand.HereIsPropertyList, false);
+ Channel.Send(Helpers.StringToArray(sb.ToString()));
}
else
{
@@ -406,13 +406,13 @@ namespace IFS.FTP
// command.
foreach (PropertyList matchingFile in files)
{
- _channel.SendMark((byte)FTPCommand.HereIsPropertyList, false);
- _channel.Send(Helpers.StringToArray(matchingFile.ToString()));
+ Channel.SendMark((byte)FTPCommand.HereIsPropertyList, false);
+ Channel.Send(Helpers.StringToArray(matchingFile.ToString()));
}
}
// End the enumeration.
- _channel.SendMark((byte)FTPCommand.EndOfCommand, true);
+ Channel.SendMark((byte)FTPCommand.EndOfCommand, true);
}
@@ -460,7 +460,7 @@ namespace IFS.FTP
Log.Write(LogType.Verbose, LogComponent.FTP, "Sending file...");
// Send the file data.
- _channel.SendMark((byte)FTPCommand.HereIsFile, true);
+ Channel.SendMark((byte)FTPCommand.HereIsFile, true);
data = new byte[512];
while (true)
@@ -474,7 +474,7 @@ namespace IFS.FTP
}
Log.Write(LogType.Verbose, LogComponent.FTP, "Sending data, current file position {0}.", outFile.Position);
- _channel.Send(data, read, true);
+ Channel.Send(data, read, true);
if (read < data.Length)
{
@@ -486,13 +486,13 @@ namespace IFS.FTP
// End the file successfully. Note that we do NOT send an EOC here.
Log.Write(LogType.Verbose, LogComponent.FTP, "Sent.");
- _channel.SendMark((byte)FTPCommand.Yes, false);
- _channel.Send(Serializer.Serialize(new FTPYesNoVersion(0, "File transferred successfully.")));
+ Channel.SendMark((byte)FTPCommand.Yes, false);
+ Channel.Send(Serializer.Serialize(new FTPYesNoVersion(0, "File transferred successfully.")));
}
// End the transfer.
Log.Write(LogType.Verbose, LogComponent.FTP, "All requested files sent.");
- _channel.SendMark((byte)FTPCommand.EndOfCommand, true);
+ Channel.SendMark((byte)FTPCommand.EndOfCommand, true);
}
@@ -544,7 +544,7 @@ namespace IFS.FTP
//
// We now expect a "Here-Is-File"...
//
- FTPCommand hereIsFile = (FTPCommand)_channel.WaitForMark();
+ FTPCommand hereIsFile = (FTPCommand)Channel.WaitForMark();
if (hereIsFile != FTPCommand.HereIsFile)
{
@@ -684,20 +684,20 @@ namespace IFS.FTP
// End the file successfully. Note that we do NOT send an EOC here, only after all files have been deleted.
Log.Write(LogType.Verbose, LogComponent.FTP, "Deleted.");
- _channel.SendMark((byte)FTPCommand.Yes, false);
- _channel.Send(Serializer.Serialize(new FTPYesNoVersion(0, "File deleted successfully.")));
+ Channel.SendMark((byte)FTPCommand.Yes, false);
+ Channel.Send(Serializer.Serialize(new FTPYesNoVersion(0, "File deleted successfully.")));
}
catch(Exception e)
{
// TODO: calculate real NO codes
- _channel.SendMark((byte)FTPCommand.No, false);
- _channel.Send(Serializer.Serialize(new FTPYesNoVersion((byte)NoCode.AccessDenied, e.Message)));
+ Channel.SendMark((byte)FTPCommand.No, false);
+ Channel.Send(Serializer.Serialize(new FTPYesNoVersion((byte)NoCode.AccessDenied, e.Message)));
}
}
// End the transfer.
Log.Write(LogType.Verbose, LogComponent.FTP, "All requested files deleted.");
- _channel.SendMark((byte)FTPCommand.EndOfCommand, true);
+ Channel.SendMark((byte)FTPCommand.EndOfCommand, true);
}
///
@@ -774,13 +774,13 @@ namespace IFS.FTP
//
// Send the property list (without EOC)
//
- _channel.SendMark((byte)FTPCommand.HereIsPropertyList, false);
- _channel.Send(Helpers.StringToArray(mailProps.ToString()));
+ Channel.SendMark((byte)FTPCommand.HereIsPropertyList, false);
+ Channel.Send(Helpers.StringToArray(mailProps.ToString()));
//
// Send the mail text.
//
- _channel.SendMark((byte)FTPCommand.HereIsFile, true);
+ Channel.SendMark((byte)FTPCommand.HereIsFile, true);
byte[] data = new byte[512];
while (true)
@@ -794,7 +794,7 @@ namespace IFS.FTP
}
Log.Write(LogType.Verbose, LogComponent.FTP, "Sending mail data, current file position {0}.", mailStream.Position);
- _channel.Send(data, read, true);
+ Channel.Send(data, read, true);
if (read < data.Length)
{
@@ -869,7 +869,7 @@ namespace IFS.FTP
//
// We now expect a "Here-Is-File"...
//
- FTPCommand hereIsFile = (FTPCommand)_channel.WaitForMark();
+ FTPCommand hereIsFile = (FTPCommand)Channel.WaitForMark();
if (hereIsFile != FTPCommand.HereIsFile)
{
@@ -951,6 +951,9 @@ namespace IFS.FTP
///
/// Enumerates all files in the IFS FTP directory matching the specified specification, and returns a full PropertyList for each.
+ ///
+ /// We also return all directories to make the directory structure discoverable. These are enumerated as "dirname "
+ /// This doesn't match the original IFS behavior but is a lot nicer to deal with as an end-user.
///
///
///
@@ -969,7 +972,31 @@ namespace IFS.FTP
// These will be absolute paths.
string[] matchingFiles = Directory.GetFiles(path, fileName, SearchOption.TopDirectoryOnly);
- // Build a property list containing the required properties.
+ // Find all directories that match the fileName, as above.
+ string[] matchingDirectories = Directory.GetDirectories(path, fileName, SearchOption.TopDirectoryOnly);
+
+ // Build a property list containing the required properties for the directories
+ // For now, we ignore any Desired-Property requests (this is legal) and return all properties we know about.
+ foreach (string matchingDirectory in matchingDirectories)
+ {
+ string nameOnly = String.Format("{0} ", Path.GetFileName(matchingDirectory));
+
+ PropertyList dirProps = new PropertyList();
+
+ dirProps.SetPropertyValue(KnownPropertyNames.ServerFilename, nameOnly);
+ dirProps.SetPropertyValue(KnownPropertyNames.Directory, path);
+ dirProps.SetPropertyValue(KnownPropertyNames.NameBody, nameOnly);
+ dirProps.SetPropertyValue(KnownPropertyNames.Type, "Binary"); // We treat all files as binary for now
+ dirProps.SetPropertyValue(KnownPropertyNames.ByteSize, "8"); // 8-bit bytes, please.
+ dirProps.SetPropertyValue(KnownPropertyNames.Version, "1"); // No real versioning support
+ dirProps.SetPropertyValue(KnownPropertyNames.CreationDate, File.GetCreationTime(matchingDirectory).ToString("dd-MMM-yy HH:mm:ss"));
+ dirProps.SetPropertyValue(KnownPropertyNames.WriteDate, File.GetLastWriteTime(matchingDirectory).ToString("dd-MMM-yy HH:mm:ss"));
+ dirProps.SetPropertyValue(KnownPropertyNames.ReadDate, File.GetLastAccessTime(matchingDirectory).ToString("dd-MMM-yy HH:mm:ss"));
+
+ properties.Add(dirProps);
+ }
+
+ // Build a property list containing the required properties for the files.
// For now, we ignore any Desired-Property requests (this is legal) and return all properties we know about.
foreach (string matchingFile in matchingFiles)
{
@@ -1120,6 +1147,14 @@ namespace IFS.FTP
string userName = fileSpec.GetPropertyValue(KnownPropertyNames.UserName);
string password = String.Empty;
+ //
+ // If the username is "guest" then we default to the guest account and ignore the password entirely.
+ //
+ if (userName == UserToken.Guest.UserName)
+ {
+ return UserToken.Guest;
+ }
+
if (fileSpec.ContainsPropertyValue(KnownPropertyNames.UserPassword))
{
password = fileSpec.GetPropertyValue(KnownPropertyNames.UserPassword);
@@ -1129,7 +1164,7 @@ namespace IFS.FTP
if (user == null)
{
- SendFTPNoResponse(NoCode.AccessDenied, "Invalid username or password.");
+ SendFTPNoResponse(NoCode.AccessDenied, "Invalid username or password.");
}
return user;
@@ -1157,30 +1192,30 @@ namespace IFS.FTP
private void SendFTPResponse(FTPCommand responseCommand, object data)
{
- _channel.SendMark((byte)responseCommand, false);
- _channel.Send(Serializer.Serialize(data));
- _channel.SendMark((byte)FTPCommand.EndOfCommand, true);
+ Channel.SendMark((byte)responseCommand, false);
+ Channel.Send(Serializer.Serialize(data));
+ Channel.SendMark((byte)FTPCommand.EndOfCommand, true);
}
private void SendFTPResponse(FTPCommand responseCommand, PropertyList data)
{
- _channel.SendMark((byte)responseCommand, false);
- _channel.Send(Helpers.StringToArray(data.ToString()));
- _channel.SendMark((byte)FTPCommand.EndOfCommand, true);
+ Channel.SendMark((byte)responseCommand, false);
+ Channel.Send(Helpers.StringToArray(data.ToString()));
+ Channel.SendMark((byte)FTPCommand.EndOfCommand, true);
}
private void SendFTPNoResponse(NoCode code, string message)
{
- _channel.SendMark((byte)FTPCommand.No, false);
- _channel.Send(Serializer.Serialize(new FTPYesNoVersion((byte)code, message)));
- _channel.SendMark((byte)FTPCommand.EndOfCommand, true);
+ Channel.SendMark((byte)FTPCommand.No, false);
+ Channel.Send(Serializer.Serialize(new FTPYesNoVersion((byte)code, message)));
+ Channel.SendMark((byte)FTPCommand.EndOfCommand, true);
}
private void SendFTPYesResponse(string message)
{
- _channel.SendMark((byte)FTPCommand.Yes, false);
- _channel.Send(Serializer.Serialize(new FTPYesNoVersion(1, message)));
- _channel.SendMark((byte)FTPCommand.EndOfCommand, true);
+ Channel.SendMark((byte)FTPCommand.Yes, false);
+ Channel.Send(Serializer.Serialize(new FTPYesNoVersion(1, message)));
+ Channel.SendMark((byte)FTPCommand.EndOfCommand, true);
}
private void ShutdownWorker()
diff --git a/PUP/FTP/PropertyList.cs b/PUP/FTP/PropertyList.cs
index 400e267..7491bff 100644
--- a/PUP/FTP/PropertyList.cs
+++ b/PUP/FTP/PropertyList.cs
@@ -80,7 +80,7 @@ namespace IFS.FTP
{
public PropertyList()
{
- _propertyList = new Dictionary();
+ _propertyList = new Dictionary>();
}
///
@@ -115,7 +115,7 @@ namespace IFS.FTP
}
///
- /// Returns the value for the specified property, if present. Otherwise returns null.
+ /// Returns the first value for the specified property, if present. Otherwise returns null.
///
///
///
@@ -123,6 +123,26 @@ namespace IFS.FTP
{
name = name.ToLowerInvariant();
+ if (_propertyList.ContainsKey(name))
+ {
+ return _propertyList[name][0];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Returns the list of property values associated with the given property name, if present.
+ /// Otherwise returns null.
+ ///
+ ///
+ ///
+ public List GetPropertyValues(string name)
+ {
+ name = name.ToLowerInvariant();
+
if (_propertyList.ContainsKey(name))
{
return _propertyList[name];
@@ -133,17 +153,44 @@ namespace IFS.FTP
}
}
+ ///
+ /// Sets a single value for the specified property, if present.
+ ///
+ ///
+ ///
public void SetPropertyValue(string name, string value)
{
- name = name.ToLowerInvariant();
+ name = name.ToLowerInvariant();
+
+ List newpList = new List();
+ newpList.Add(value);
if (_propertyList.ContainsKey(name))
- {
- _propertyList[name] = value;
+ {
+ _propertyList[name] = newpList;
}
else
{
- _propertyList.Add(name, value);
+ _propertyList.Add(name, newpList);
+ }
+ }
+
+ ///
+ /// Sets multiple values for the specified property, if present.
+ ///
+ ///
+ ///
+ public void SetPropertyValues(string name, List values)
+ {
+ name = name.ToLowerInvariant();
+
+ if (_propertyList.ContainsKey(name))
+ {
+ _propertyList[name] = values;
+ }
+ else
+ {
+ _propertyList.Add(name, values);
}
}
@@ -160,7 +207,10 @@ namespace IFS.FTP
foreach(string key in _propertyList.Keys)
{
- sb.AppendFormat("({0} {1})", key, EscapeString(_propertyList[key]));
+ foreach (string value in _propertyList[key])
+ {
+ sb.AppendFormat("({0} {1})", key, EscapeString(value));
+ }
}
// Closing paren
@@ -297,17 +347,21 @@ namespace IFS.FTP
//
if (!_propertyList.ContainsKey(propertyName))
{
- _propertyList.Add(propertyName, propertyValue.ToString());
+ // New property key
+ List newpList = new List();
+ newpList.Add(propertyValue.ToString());
+ _propertyList.Add(propertyName, newpList);
}
else
{
- throw new InvalidOperationException(String.Format("Duplicate property entry for '{0}", propertyName));
+ // Property key with multiple values
+ _propertyList[propertyName].Add(propertyValue.ToString());
}
}
return index + startOffset + 1;
}
- private Dictionary _propertyList;
+ private Dictionary> _propertyList;
}
}
diff --git a/PUP/GatewayInformationProtocol.cs b/PUP/Gateway/GatewayInformationProtocol.cs
similarity index 97%
rename from PUP/GatewayInformationProtocol.cs
rename to PUP/Gateway/GatewayInformationProtocol.cs
index ce528a1..a5836d3 100644
--- a/PUP/GatewayInformationProtocol.cs
+++ b/PUP/Gateway/GatewayInformationProtocol.cs
@@ -1,11 +1,12 @@
-using IFS.Logging;
+using IFS.Gateway;
+using IFS.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace IFS
+namespace IFS.Gateway
{
public struct GatewayInformation
{
@@ -82,7 +83,7 @@ namespace IFS
PUP response = new PUP(PupType.GatewayInformationResponse, p.ID, remotePort, localPort, Serializer.Serialize(info));
- PUPProtocolDispatcher.Instance.SendPup(response);
+ Router.Instance.SendPup(response);
}
}
diff --git a/PUP/Gateway/Router.cs b/PUP/Gateway/Router.cs
new file mode 100644
index 0000000..28b4d5b
--- /dev/null
+++ b/PUP/Gateway/Router.cs
@@ -0,0 +1,128 @@
+using IFS.Logging;
+using IFS.Transport;
+using PcapDotNet.Core;
+using System.Net.NetworkInformation;
+
+namespace IFS.Gateway
+{
+ public delegate void RoutePupCallback(PUP pup);
+
+ ///
+ /// Implements gateway services, routing PUPs intended for other networks to
+ /// their proper destination.
+ /// This is one layer above the physical transport layer (ethernet, udp) and
+ /// is below the protocol layer.
+ ///
+ /// The routing is currently a stub implmentation, and only handles PUPs destined for our own network.
+ ///
+ public class Router
+ {
+ private Router()
+ {
+ _localProtocolDispatcher = new PUPProtocolDispatcher();
+ }
+
+ public static Router Instance
+ {
+ get
+ {
+ return _router;
+ }
+ }
+
+ public void Shutdown()
+ {
+ _localProtocolDispatcher.Shutdown();
+ _pupPacketInterface.Shutdown();
+ }
+
+ public void RegisterRAWInterface(LivePacketDevice iface)
+ {
+ Ethernet enet = new Ethernet(iface);
+
+ _pupPacketInterface = enet;
+ _rawPacketInterface = enet;
+ _pupPacketInterface.RegisterRouterCallback(RouteIncomingPacket);
+ }
+
+ public void RegisterUDPInterface(NetworkInterface iface)
+ {
+ UDPEncapsulation udp = new UDPEncapsulation(iface);
+
+ _pupPacketInterface = udp;
+ _rawPacketInterface = udp;
+ _pupPacketInterface.RegisterRouterCallback(RouteIncomingPacket);
+ }
+
+ ///
+ /// Sends a PUP out to the world; this may be routed to a different network.
+ ///
+ ///
+ public void SendPup(PUP p)
+ {
+ RouteOutgoingPacket(p);
+ }
+
+ ///
+ /// Sends a raw packet out to the world. This packet will not be routed.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Send(byte[] data, byte source, byte destination, ushort frameType)
+ {
+ if (_rawPacketInterface != null)
+ {
+ _rawPacketInterface.Send(data, source, destination, frameType);
+ }
+ }
+
+ ///
+ /// Routes a PUP out to the world.
+ ///
+ ///
+ private void RouteOutgoingPacket(PUP p)
+ {
+ // For now, we send the packet out without performing any routing.
+ _pupPacketInterface.Send(p);
+ }
+
+ ///
+ /// Routes a newly received packet to the proper destination host.
+ ///
+ ///
+ public void RouteIncomingPacket(PUP pup)
+ {
+ //
+ // Check the network -- if this is network zero (coming from a host that doesn't yet know what
+ // network it's on, or specifying the current network) or our network, we will pass it on to the protocol suite.
+ //
+ if (pup.DestinationPort.Network == 0 || pup.DestinationPort.Network == DirectoryServices.Instance.LocalHostAddress.Network)
+ {
+ _localProtocolDispatcher.ReceivePUP(pup);
+ }
+ else
+ {
+ // Not for our network.
+ // For now, we will drop the packet. Once we implement
+ // Gateway services we will handle these appropriately.)
+ Log.Write(LogType.Verbose, LogComponent.Ethernet, "PUP is for network {0}, dropping.", pup.DestinationPort.Network);
+ }
+ }
+
+ ///
+ /// Our interface to a facility that can transmit/receive PUPs
+ ///
+ private IPupPacketInterface _pupPacketInterface;
+
+ ///
+ /// Our interface to a facility that can transmit raw Ethernet frames
+ ///
+ private IRawPacketInterface _rawPacketInterface;
+
+ private static Router _router = new Router();
+
+ private PUPProtocolDispatcher _localProtocolDispatcher;
+ }
+}
diff --git a/PUP/IFS.csproj b/PUP/IFS.csproj
index 8714937..ce7fc21 100644
--- a/PUP/IFS.csproj
+++ b/PUP/IFS.csproj
@@ -117,11 +117,13 @@
+
-
+
+
diff --git a/PUP/Mail/MailManager.cs b/PUP/Mail/MailManager.cs
index f5ca684..332b6e7 100644
--- a/PUP/Mail/MailManager.cs
+++ b/PUP/Mail/MailManager.cs
@@ -20,7 +20,7 @@ namespace IFS.Mail
/// name. Authentication is handled as one would expect -- each user has read/delete
/// access only to his/her own mailbox, all other mailboxes can only be sent to.
///
- /// Mailbox directories are lazy-init -- they're only created when a user receives an
+ /// Mailbox directories are lazy-init -- they're only created when a user first receives an
/// e-mail.
///
/// The guest account has its own mailbox, shared by all guest users.
diff --git a/PUP/MiscServicesProtocol.cs b/PUP/MiscServicesProtocol.cs
index 54537d7..37f814f 100644
--- a/PUP/MiscServicesProtocol.cs
+++ b/PUP/MiscServicesProtocol.cs
@@ -1,5 +1,6 @@
using IFS.Boot;
using IFS.EFTP;
+using IFS.Gateway;
using IFS.Logging;
using IFS.Mail;
using System;
@@ -53,8 +54,7 @@ namespace IFS
{
public MiscServicesProtocol()
{
- // TODO:
- // load host tables, etc.
+
}
///
@@ -123,7 +123,7 @@ namespace IFS
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
PUP response = new PUP(PupType.StringTimeReply, p.ID, p.SourcePort, localPort, timeString);
- PUPProtocolDispatcher.Instance.SendPup(response);
+ Router.Instance.SendPup(response);
}
private void SendAltoTimeReply(PUP p)
@@ -166,7 +166,7 @@ namespace IFS
PUP response = new PUP(PupType.AltoTimeResponse, p.ID, remotePort, localPort, Serializer.Serialize(time));
- PUPProtocolDispatcher.Instance.SendPup(response);
+ Router.Instance.SendPup(response);
}
private void SendAddressLookupReply(PUP p)
@@ -201,7 +201,7 @@ namespace IFS
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP lookupReply = new PUP(PupType.AddressLookupResponse, p.ID, p.SourcePort, localPort, interNetworkName);
- PUPProtocolDispatcher.Instance.SendPup(lookupReply);
+ Router.Instance.SendPup(lookupReply);
}
else
{
@@ -210,7 +210,7 @@ namespace IFS
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, Helpers.StringToArray(errorString));
- PUPProtocolDispatcher.Instance.SendPup(errorReply);
+ Router.Instance.SendPup(errorReply);
}
}
@@ -241,7 +241,7 @@ namespace IFS
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP lookupReply = new PUP(PupType.NameLookupResponse, p.ID, p.SourcePort, localPort, lookupPort.ToArray());
- PUPProtocolDispatcher.Instance.SendPup(lookupReply);
+ Router.Instance.SendPup(lookupReply);
}
else
{
@@ -250,7 +250,7 @@ namespace IFS
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP errorReply = new PUP(PupType.DirectoryLookupErrorReply, p.ID, p.SourcePort, localPort, Helpers.StringToArray(errorString));
- PUPProtocolDispatcher.Instance.SendPup(errorReply);
+ Router.Instance.SendPup(errorReply);
}
}
@@ -310,7 +310,7 @@ namespace IFS
{
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP bootDirReply = new PUP(PupType.BootDirectoryReply, p.ID, p.SourcePort, localPort, ms.ToArray());
- PUPProtocolDispatcher.Instance.SendPup(bootDirReply);
+ Router.Instance.SendPup(bootDirReply);
ms.Seek(0, SeekOrigin.Begin);
ms.SetLength(0);
@@ -322,7 +322,7 @@ namespace IFS
{
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP bootDirReply = new PUP(PupType.BootDirectoryReply, p.ID, p.SourcePort, localPort, ms.ToArray());
- PUPProtocolDispatcher.Instance.SendPup(bootDirReply);
+ Router.Instance.SendPup(bootDirReply);
}
}
@@ -358,7 +358,7 @@ namespace IFS
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP errorReply = new PUP(PupType.AuthenticateNegativeResponse, p.ID, p.SourcePort, localPort, Helpers.StringToArray(errorString));
- PUPProtocolDispatcher.Instance.SendPup(errorReply);
+ Router.Instance.SendPup(errorReply);
}
else
{
@@ -366,7 +366,7 @@ namespace IFS
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP okReply = new PUP(PupType.AuthenticatePositiveResponse, p.ID, p.SourcePort, localPort, new byte[] { });
- PUPProtocolDispatcher.Instance.SendPup(okReply);
+ Router.Instance.SendPup(okReply);
}
}
@@ -385,10 +385,7 @@ namespace IFS
// If mailbox name has a host/registry appended, we will strip it off.
// TODO: probably should validate host...
//
- if (mailboxName.Contains("."))
- {
- mailboxName = mailboxName.Substring(0, mailboxName.IndexOf("."));
- }
+ mailboxName = Authentication.GetUserNameFromFullName(mailboxName);
IEnumerable mailList = MailManager.EnumerateMail(mailboxName);
@@ -397,14 +394,14 @@ namespace IFS
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP noMailReply = new PUP(PupType.NoNewMailExistsReply, p.ID, p.SourcePort, localPort, new byte[] { });
- PUPProtocolDispatcher.Instance.SendPup(noMailReply);
+ Router.Instance.SendPup(noMailReply);
}
else
{
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP mailReply = new PUP(PupType.NewMailExistsReply, p.ID, p.SourcePort, localPort, Helpers.StringToArray("You've got mail!"));
- PUPProtocolDispatcher.Instance.SendPup(mailReply);
+ Router.Instance.SendPup(mailReply);
}
}
diff --git a/PUP/PUP.cs b/PUP/PUP.cs
index a15bab6..2247ed5 100644
--- a/PUP/PUP.cs
+++ b/PUP/PUP.cs
@@ -230,7 +230,7 @@ namespace IFS
}
///
- ///
+ /// Same as above, no garbage byte.
///
///
///
@@ -300,7 +300,7 @@ namespace IFS
if (Checksum != 0xffff && cChecksum != Checksum)
{
- // TODO: determine what to do with packets that are corrupted.
+ // TODO: determine what to do with packets that are corrupted -- drop, or continue anyway?
Log.Write(LogType.Warning, LogComponent.PUP, "PUP checksum is invalid. (got {0:x}, expected {1:x})", Checksum, cChecksum);
}
diff --git a/PUP/PUPProtocolDispatcher.cs b/PUP/PUPProtocolDispatcher.cs
index 1993e7e..32d1560 100644
--- a/PUP/PUPProtocolDispatcher.cs
+++ b/PUP/PUPProtocolDispatcher.cs
@@ -1,17 +1,12 @@
using IFS.BSP;
using IFS.EFTP;
using IFS.Logging;
-using IFS.Transport;
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-using PcapDotNet.Base;
-using System.Net.NetworkInformation;
-using PcapDotNet.Core;
+using IFS.CopyDisk;
+using IFS.FTP;
+using IFS.Gateway;
namespace IFS
{
@@ -23,74 +18,21 @@ namespace IFS
///
/// Private Constructor for this class, enforcing Singleton usage.
///
- private PUPProtocolDispatcher()
+ public PUPProtocolDispatcher()
{
- _dispatchMap = new Dictionary();
- }
+ _dispatchMap = new Dictionary();
- ///
- /// Accessor for singleton instance of this class.
- ///
- public static PUPProtocolDispatcher Instance
- {
- get { return _instance; }
- }
-
- public void RegisterRAWInterface(LivePacketDevice iface)
- {
- Ethernet enet = new Ethernet(iface);
-
- _pupPacketInterface = enet;
- _rawPacketInterface = enet;
- _pupPacketInterface.RegisterReceiveCallback(OnPupReceived);
- }
-
- public void RegisterUDPInterface(NetworkInterface iface)
- {
- UDPEncapsulation udp = new UDPEncapsulation(iface);
-
- _pupPacketInterface = udp;
- _rawPacketInterface = udp;
- _pupPacketInterface.RegisterReceiveCallback(OnPupReceived);
- }
-
- ///
- /// Registers a new protocol with the dispatcher.
- ///
- ///
- ///
- public void RegisterProtocol(PUPProtocolEntry entry)
- {
- if (_dispatchMap.ContainsKey(entry.Socket))
- {
- throw new InvalidOperationException(
- String.Format("Socket {0} has already been registered for protocol {1}", entry.Socket, _dispatchMap[entry.Socket].FriendlyName));
- }
-
- _dispatchMap[entry.Socket] = entry;
- }
-
- public void SendPup(PUP p)
- {
- // drop every 10th packet for testing
- _packet++;
-
- // if ((_packet % 10) != 5)
- {
- _pupPacketInterface.Send(p);
- }
+ RegisterProtocols();
+ }
- }
-
- public void Send(byte[] data, byte source, byte destination, ushort frameType)
+ public void Shutdown()
{
- if (_rawPacketInterface != null)
- {
- _rawPacketInterface.Send(data, source, destination, frameType);
- }
- }
+ _breathOfLifeServer.Shutdown();
+ BSPManager.Shutdown();
+ //EFTPManager.Shutdown();
+ }
- private void OnPupReceived(PUP pup)
+ public void ReceivePUP(PUP pup)
{
//
// Filter out packets not destined for us.
@@ -122,9 +64,7 @@ namespace IFS
else
{
// RTP / BSP protocol. Pass this to the BSP handler to set up a channel.
- 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);
-
+ Log.Write(LogType.Verbose, LogComponent.PUP, "Dispatching PUP (source {0}, dest {1}) to BSP protocol for {0}.", pup.SourcePort, pup.DestinationPort, entry.FriendlyName);
BSPManager.EstablishRendezvous(pup, entry.WorkerType);
}
}
@@ -143,24 +83,48 @@ namespace IFS
Log.Write(LogType.Normal, LogComponent.PUP, "Unhandled PUP protocol, source socket {0}, destination socket {1}, type {2}, dropped packet.", pup.SourcePort.Socket, pup.DestinationPort.Socket, pup.Type);
}
}
-
- ///
- /// Our interface to a facility that can transmit/receive PUPs
- ///
- private IPupPacketInterface _pupPacketInterface;
///
- /// Our interface to a facility that can transmit raw Ethernet frames
+ /// Registers a new protocol with the dispatcher.
///
- private IRawPacketInterface _rawPacketInterface;
+ ///
+ ///
+ private void RegisterProtocol(PUPProtocolEntry entry)
+ {
+ if (_dispatchMap.ContainsKey(entry.Socket))
+ {
+ throw new InvalidOperationException(
+ String.Format("Socket {0} has already been registered for protocol {1}", entry.Socket, _dispatchMap[entry.Socket].FriendlyName));
+ }
+
+ _dispatchMap[entry.Socket] = entry;
+ }
+
+ private void RegisterProtocols()
+ {
+ // Set up protocols:
+
+ // Connectionless
+ RegisterProtocol(new PUPProtocolEntry("Gateway Information", 2, ConnectionType.Connectionless, new GatewayInformationProtocol()));
+ RegisterProtocol(new PUPProtocolEntry("Misc Services", 0x4, ConnectionType.Connectionless, new MiscServicesProtocol()));
+ RegisterProtocol(new PUPProtocolEntry("Echo", 0x5, ConnectionType.Connectionless, new EchoProtocol()));
+
+ // RTP/BSP based:
+ RegisterProtocol(new PUPProtocolEntry("CopyDisk", 0x15 /* 25B */, ConnectionType.BSP, typeof(CopyDiskWorker)));
+ RegisterProtocol(new PUPProtocolEntry("FTP", 0x3, ConnectionType.BSP, typeof(FTPWorker)));
+ RegisterProtocol(new PUPProtocolEntry("Mail", 0x7, ConnectionType.BSP, typeof(FTPWorker)));
+
+ // Breath Of Life
+ _breathOfLifeServer = new BreathOfLife();
+ }
///
/// Map from socket to protocol implementation
///
private Dictionary _dispatchMap;
- private int _packet;
-
- private static PUPProtocolDispatcher _instance = new PUPProtocolDispatcher();
+ //
+ // Breath of Life server, which is its own thing.
+ private BreathOfLife _breathOfLifeServer;
}
}
diff --git a/PUP/Properties/AssemblyInfo.cs b/PUP/Properties/AssemblyInfo.cs
index df0ab97..46948ca 100644
--- a/PUP/Properties/AssemblyInfo.cs
+++ b/PUP/Properties/AssemblyInfo.cs
@@ -5,12 +5,12 @@ using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("PUP")]
+[assembly: AssemblyTitle("IFS")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Vulcan Inc.")]
-[assembly: AssemblyProduct("PUP")]
-[assembly: AssemblyCopyright("Copyright © Vulcan Inc. 2015")]
+[assembly: AssemblyCompany("Living Computers: Museum+Labs")]
+[assembly: AssemblyProduct("IFS")]
+[assembly: AssemblyCopyright("Copyright © LCM+L 2015-2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/PUP/Serializer.cs b/PUP/Serializer.cs
index 1c36611..bdbfa76 100644
--- a/PUP/Serializer.cs
+++ b/PUP/Serializer.cs
@@ -36,6 +36,11 @@ namespace IFS
public int Length;
}
+ ///
+ /// Provides a framework for serialization and deserialization of properly annotated
+ /// structs and classes; used to take raw data from the wire and reconstitute them into
+ /// IFS objects, and turn those objects back into a bag of bytes that meet IFS specs.
+ ///
public static class Serializer
{
diff --git a/PUP/SocketIDGenerator.cs b/PUP/SocketIDGenerator.cs
new file mode 100644
index 0000000..217067d
--- /dev/null
+++ b/PUP/SocketIDGenerator.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace IFS
+{
+ static class SocketIDGenerator
+ {
+ static SocketIDGenerator()
+ {
+ //
+ // Initialize the socket ID counter; we start with a
+ // number beyond the range of well-defined sockets.
+ // For each request for a new ID, we will
+ // increment this counter to ensure that each channel gets
+ // a unique ID. (Well, until we wrap around...)
+ //
+ _nextSocketID = _startingSocketID;
+
+ _idLock = new ReaderWriterLockSlim();
+ }
+
+
+ ///
+ /// Generates what should be a unique Socket ID.
+ /// Uniqueness is not guaranteed, but the probability of a collision
+ /// is extremely low given its intended use.
+ ///
+ /// ID Generation is sequential, but this behavior should not be
+ /// relied upon.
+ ///
+ public static UInt32 GetNextSocketID()
+ {
+ _idLock.EnterWriteLock();
+ UInt32 next = _nextSocketID;
+
+ _nextSocketID++;
+
+ //
+ // Handle the wrap around case (which we're very unlikely to
+ // ever hit, but why not do the right thing).
+ // Start over at the initial ID. This is very unlikely to
+ // collide with any pending channels.
+ //
+ if (_nextSocketID < _startingSocketID)
+ {
+ _nextSocketID = _startingSocketID;
+ }
+ _idLock.ExitWriteLock();
+
+ return next;
+ }
+
+ private static UInt32 _nextSocketID;
+
+ private static readonly UInt32 _startingSocketID = 0x1000;
+
+ private static ReaderWriterLockSlim _idLock;
+ }
+}
diff --git a/PUP/Transport/Ethernet.cs b/PUP/Transport/Ethernet.cs
index 3264f75..f122708 100644
--- a/PUP/Transport/Ethernet.cs
+++ b/PUP/Transport/Ethernet.cs
@@ -13,91 +13,88 @@ using IFS.Logging;
using System.IO;
using System.Net.NetworkInformation;
using System.Threading;
+using IFS.Gateway;
namespace IFS.Transport
{
///
- /// Defines interface "to the metal" (raw ethernet frames) which may wrap the underlying transport (for example, winpcap)
+ /// Defines interface "to the metal" (raw ethernet frames) using WinPCAP to send and receive Ethernet
+ /// frames.
+ ///
+ /// Ethernet packets are broadcast. See comments in UDP.cs for the reasoning behind this.
+ ///
///
public class Ethernet : IPupPacketInterface, IRawPacketInterface
{
public Ethernet(LivePacketDevice iface)
{
- _interface = iface;
-
- // Set up maps
- _pupToEthernetMap = new Dictionary(256);
- _ethernetToPupMap = new Dictionary(256);
+ _interface = iface;
}
- public void RegisterReceiveCallback(HandlePup callback)
+ public void RegisterRouterCallback(RoutePupCallback callback)
{
- _callback = callback;
+ _routerCallback = callback;
// Now that we have a callback we can start receiving stuff.
- Open(true /* promiscuous */, int.MaxValue);
+ Open(false /* not promiscuous */, int.MaxValue);
// Kick off the receiver thread, this will never return or exit.
Thread receiveThread = new Thread(new ThreadStart(BeginReceive));
receiveThread.Start();
}
+
+ public void Shutdown()
+ {
+ _routerCallback = null;
+ _communicator.Break();
+ }
public void Send(PUP p)
{
//
// Write PUP to ethernet:
- // Get destination network & host address from PUP and route to correct ethernet address.
- // For now, no actual routing (Gateway not implemented yet), everything is on the same 'net.
- // Just look up host address and find the MAC of the host to send it to.
- //
- if (_pupToEthernetMap.ContainsKey(p.DestinationPort.Host))
+ //
+
+ // Build the outgoing data; this is:
+ // 1st word: length of data following
+ // 2nd word: 3mbit destination / source bytes
+ // 3rd word: frame type (PUP)
+ byte[] encapsulatedFrame = new byte[6 + p.RawData.Length];
+
+ // 3mbit Packet length
+ encapsulatedFrame[0] = (byte)((p.RawData.Length / 2 + 2) >> 8);
+ encapsulatedFrame[1] = (byte)(p.RawData.Length / 2 + 2);
+
+ // addressing
+ encapsulatedFrame[2] = p.DestinationPort.Host;
+ encapsulatedFrame[3] = p.SourcePort.Host;
+
+ // frame type
+ encapsulatedFrame[4] = (byte)(_pupFrameType >> 8);
+ encapsulatedFrame[5] = (byte)_pupFrameType;
+
+ // Actual data
+ p.RawData.CopyTo(encapsulatedFrame, 6);
+
+ MacAddress destinationMac = new MacAddress(_10mbitBroadcast);
+
+ // Build the outgoing packet; place the source/dest addresses, type field and the PUP data.
+ EthernetLayer ethernetLayer = new EthernetLayer
{
- // Build the outgoing data; this is:
- // 1st word: length of data following
- // 2nd word: 3mbit destination / source bytes
- // 3rd word: frame type (PUP)
- byte[] encapsulatedFrame = new byte[6 + p.RawData.Length];
+ Source = _interface.GetMacAddress(),
+ Destination = destinationMac,
+ EtherType = (EthernetType)_3mbitFrameType,
+ };
- // 3mbit Packet length
- encapsulatedFrame[0] = (byte)((p.RawData.Length / 2 + 2) >> 8);
- encapsulatedFrame[1] = (byte)(p.RawData.Length / 2 + 2);
-
- // addressing
- encapsulatedFrame[2] = p.DestinationPort.Host;
- encapsulatedFrame[3] = p.SourcePort.Host;
-
- // frame type
- encapsulatedFrame[4] = (byte)(_pupFrameType >> 8);
- encapsulatedFrame[5] = (byte)_pupFrameType;
-
- // Actual data
- p.RawData.CopyTo(encapsulatedFrame, 6);
-
- MacAddress destinationMac = _pupToEthernetMap[p.DestinationPort.Host];
-
- // Build the outgoing packet; place the source/dest addresses, type field and the PUP data.
- EthernetLayer ethernetLayer = new EthernetLayer
- {
- Source = _interface.GetMacAddress(),
- Destination = destinationMac,
- EtherType = (EthernetType)_3mbitFrameType,
- };
-
- PayloadLayer payloadLayer = new PayloadLayer
- {
- Data = new Datagram(encapsulatedFrame),
- };
-
- PacketBuilder builder = new PacketBuilder(ethernetLayer, payloadLayer);
-
- // Send it over the 'net!
- _communicator.SendPacket(builder.Build(DateTime.Now));
- }
- else
+ PayloadLayer payloadLayer = new PayloadLayer
{
- // Log error, this should not happen.
- Log.Write(LogType.Error, LogComponent.Ethernet, String.Format("PUP destination address {0} is unknown.", p.DestinationPort.Host));
- }
+ Data = new Datagram(encapsulatedFrame),
+ };
+
+ PacketBuilder builder = new PacketBuilder(ethernetLayer, payloadLayer);
+
+ // Send it over the 'net!
+ _communicator.SendPacket(builder.Build(DateTime.Now));
}
public void Send(byte[] data, byte source, byte destination, ushort frameType)
@@ -122,33 +119,8 @@ namespace IFS.Transport
// Actual data
data.CopyTo(encapsulatedFrame, 6);
-
- // Byte swap
- // encapsulatedFrame = ByteSwap(encapsulatedFrame);
-
- MacAddress destinationMac;
- if (destination != 0xff)
- {
- if (_pupToEthernetMap.ContainsKey(destination))
- {
- //
- // Use the existing map.
- //
- destinationMac = _pupToEthernetMap[destination];
- }
- else
- {
- //
- // Nothing mapped for this PUP, do our best with it.
- //
- destinationMac = new MacAddress((UInt48)(_10mbitMACPrefix | destination));
- }
- }
- else
- {
- // 3mbit broadcast becomes 10mbit broadcast
- destinationMac = new MacAddress(_10mbitBroadcast);
- }
+
+ MacAddress destinationMac = new MacAddress(_10mbitBroadcast);
// Build the outgoing packet; place the source/dest addresses, type field and the PUP data.
EthernetLayer ethernetLayer = new EthernetLayer
@@ -193,24 +165,17 @@ namespace IFS.Transport
if (etherType3mbit == _pupFrameType)
{
- PUP pup = new PUP(packetStream, length);
+ try
+ {
+ PUP pup = new PUP(packetStream, length);
+ _routerCallback(pup);
+ }
+ catch(Exception e)
+ {
+ // An error occurred, log it.
+ Log.Write(LogType.Error, LogComponent.PUP, "Error handling PUP: {0}", e.Message);
+ }
- //
- // Check the network -- if this is not network zero (coming from a host that doesn't yet know what
- // network it's on, or specifying the current network) or the network we're on, we will ignore it (for now). Once we implement
- // Gateway services we will handle these appropriately (at a higher, as-yet-unimplemented layer between this
- // and the Dispatcher).
- //
- if (pup.DestinationPort.Network == 0 || pup.DestinationPort.Network == DirectoryServices.Instance.LocalHostAddress.Network)
- {
- UpdateMACTable(pup, p);
- _callback(pup);
- }
- else
- {
- // Not for our network.
- Log.Write(LogType.Verbose, LogComponent.Ethernet, "PUP is for network {0}, dropping.", pup.DestinationPort.Network);
- }
}
else
{
@@ -240,45 +205,11 @@ namespace IFS.Transport
private void BeginReceive()
{
_communicator.ReceivePackets(-1, ReceiveCallback);
- }
-
- private void UpdateMACTable(PUP p, Packet e)
- {
- //
- // See if we already have this entry.
- //
- if (_pupToEthernetMap.ContainsKey(p.SourcePort.Host))
- {
- //
- // We do; ensure that the mac addresses match -- if not we have a duplicate
- // PUP host id on the network.
- //
- if (_pupToEthernetMap[p.SourcePort.Host] != e.Ethernet.Source)
- {
- Log.Write(LogType.Error, LogComponent.Ethernet,
- "Duplicate host ID {0} for MAC {1} (currently mapped to MAC {2})",
- p.SourcePort.Host,
- e.Ethernet.Source,
- _pupToEthernetMap[p.SourcePort.Host]);
- }
- }
- else
- {
- // Add a mapping in both directions
- _pupToEthernetMap.Add(p.SourcePort.Host, e.Ethernet.Source);
- _ethernetToPupMap.Add(e.Ethernet.Source, p.SourcePort.Host);
- }
}
- ///
- /// PUP<->Ethernet address map
- ///
- private Dictionary _pupToEthernetMap;
- private Dictionary _ethernetToPupMap;
-
private LivePacketDevice _interface;
private PacketCommunicator _communicator;
- private HandlePup _callback;
+ private RoutePupCallback _routerCallback;
// Constants
@@ -286,11 +217,7 @@ namespace IFS.Transport
private readonly ushort _pupFrameType = 512;
// The type used for 3mbit frames encapsulated in 10mb frames
- private readonly int _3mbitFrameType = 0xbeef; // easy to identify, ostensibly unused by anything of any import
-
- // 5 byte prefix for 3mbit->10mbit addresses when sending raw frames; this is the convention ContrAlto uses.
- // TODO: this should be configurable.
- private UInt48 _10mbitMACPrefix = 0x0000aa010200; // 00-00-AA is the Xerox vendor code, used just to be cute.
+ private readonly int _3mbitFrameType = 0xbeef; // easy to identify, ostensibly unused by anything of any import
// 10mbit broadcast address
private UInt48 _10mbitBroadcast = (UInt48)0xffffffffffff;
diff --git a/PUP/Transport/PacketInterface.cs b/PUP/Transport/PacketInterface.cs
index 6476745..6dea17a 100644
--- a/PUP/Transport/PacketInterface.cs
+++ b/PUP/Transport/PacketInterface.cs
@@ -1,4 +1,5 @@
-using PcapDotNet.Packets;
+using IFS.Gateway;
+using PcapDotNet.Packets;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -7,30 +8,49 @@ using System.Threading.Tasks;
namespace IFS.Transport
{
-
- public delegate void HandlePup(PUP pup);
-
///
/// IPupPacketInterface provides an abstraction over a transport (Ethernet, IP, Carrier Pigeon)
/// which can provide encapsulation for PUPs.
///
public interface IPupPacketInterface
{
+ ///
+ /// Sends the given PUP over the transport.
+ ///
+ ///
void Send(PUP p);
- void RegisterReceiveCallback(HandlePup callback);
+ ///
+ /// Registers a callback (into the router) to be invoked on receipt of a PUP.
+ ///
+ ///
+ void RegisterRouterCallback(RoutePupCallback callback);
+
+ ///
+ /// Shuts down the interface.
+ ///
+ void Shutdown();
}
///
- /// IPupPacketInterface provides an abstraction over a transport (Ethernet, IP, Carrier Pigeon)
+ /// IRawPacketInterface provides an abstraction over a transport (Ethernet, IP, Carrier Pigeon)
/// which can provide encapsulation for raw Ethernet frames.
///
/// For the time being, this exists only to provide support for BreathOfLife packets (the only non-PUP
/// Ethernet Packet the IFS suite deals with). This only requires being able to send packets, so no
/// receive is implemented.
+ ///
+ /// Note also that no routing will be provided for raw packets; they are sent to the local 'net only.
///
public interface IRawPacketInterface
{
+ ///
+ /// Sends the specified data over the transport.
+ ///
+ ///
+ ///
+ ///
+ ///
void Send(byte[] data, byte source, byte destination, ushort frameType);
}
}
diff --git a/PUP/Transport/UDP.cs b/PUP/Transport/UDP.cs
index b2b838a..cfe0f12 100644
--- a/PUP/Transport/UDP.cs
+++ b/PUP/Transport/UDP.cs
@@ -6,12 +6,43 @@ using System.Threading;
using System.Net.NetworkInformation;
using IFS.Logging;
using System.IO;
+using IFS.Gateway;
namespace IFS.Transport
{
///
/// Implements the logic for encapsulating a 3mbit ethernet packet into/out of UDP datagrams.
- /// Sent packets are broadcast to the subnet.
+ /// Sent packets are broadcast to the subnet.
+ ///
+ /// A brief diversion into the subject of broadcasts and the reason for using them. (This applies
+ /// to the Ethernet transport as well.)
+ ///
+ /// Effectively, the IFS suite is implemented on top of a virtual 3 Megabit Ethernet network
+ /// encapsulated over a modern network (UDP over IP, raw Ethernet frames, etc.). Participants
+ /// on this virtual network are virtual Altos (ContrAlto or others) and real Altos bridged via
+ /// a 3M<->100M device.
+ ///
+ /// Any of these virtual or real Altos can, at any time, be running in Promiscuous mode, can send
+ /// arbitrary packets with any source or destination address in the header, or send broadcasts.
+ /// This makes address translation from the virtual (3M) side to the physical (UDP, 100M) side and
+ /// back again tricky.
+ ///
+ /// If each participant on the virtual network were to have a table mapping physical (UDP IP, 100M MAC) to
+ /// virtual (3M MAC) addresses then broadcasts could be avoided, but it complicates the logic in all
+ /// parties and requires each user to maintain this mapping table manually.
+ ///
+ /// Resorting to using broadcasts at all times on the physical network removes these complications and
+ /// makes it easy for end-users to deal with.
+ /// The drawback is that broadcasts can reduce the efficiency of the network segment they're broadcast to.
+ /// However, most Alto networks are extremely quiet (by today's standards) -- the maximum throughput
+ /// of one Alto continuously transferring data to another is on the order of 20-30 kilobytes/sec.
+ /// (Most of the time, a given Alto will be completely silent.)
+ /// On a modern 100M or 1G network, this is background noise and modern computers receiving these broadcasts
+ /// will hardly notice.
+ ///
+ /// Based on the above, and after a lot of experimentation, it was decided to err on the side of simplicity
+ /// and go with the broadcast implementation.
+ ///
///
public class UDPEncapsulation : IPupPacketInterface, IRawPacketInterface
{
@@ -64,14 +95,24 @@ namespace IFS.Transport
}
}
- public void RegisterReceiveCallback(HandlePup callback)
+ ///
+ /// Registers a gateway to handle incoming PUPs.
+ ///
+ ///
+ public void RegisterRouterCallback(RoutePupCallback callback)
{
- _callback = callback;
+ _routerCallback = callback;
// Now that we have a callback we can start receiving stuff.
BeginReceive();
}
+ public void Shutdown()
+ {
+ _receiveThread.Abort();
+ _routerCallback = null;
+ }
+
public void Send(PUP p)
{
//
@@ -159,22 +200,15 @@ namespace IFS.Transport
if (etherType3mbit == _pupFrameType)
{
- PUP pup = new PUP(packetStream, length);
-
- //
- // Check the network -- if this is not network zero (coming from a host that doesn't yet know what
- // network it's on, or specifying the current network) or the network we're on, we will ignore it (for now). Once we implement
- // Gateway services we will handle these appropriately (at a higher, as-yet-unimplemented layer between this
- // and the Dispatcher).
- //
- if (pup.DestinationPort.Network == 0 || pup.DestinationPort.Network == DirectoryServices.Instance.LocalHostAddress.Network)
- {
- _callback(pup);
- }
- else
+ try
{
- // Not for our network.
- Log.Write(LogType.Verbose, LogComponent.Ethernet, "PUP is for network {0}, dropping.", pup.DestinationPort.Network);
+ PUP pup = new PUP(packetStream, length);
+ _routerCallback(pup);
+ }
+ catch(Exception e)
+ {
+ // An error occurred, log it.
+ Log.Write(LogType.Error, LogComponent.PUP, "Error handling PUP: {0}", e.Message);
}
}
else
@@ -193,6 +227,9 @@ namespace IFS.Transport
_receiveThread.Start();
}
+ ///
+ /// Worker thread for UDP packet receipt.
+ ///
private void ReceiveThread()
{
// Just call ReceivePackets, that's it. This will never return.
@@ -232,7 +269,7 @@ namespace IFS.Transport
// The ethertype used in the encapsulated 3mbit frame
private readonly ushort _pupFrameType = 512;
- private HandlePup _callback;
+ private RoutePupCallback _routerCallback;
// Thread used for receive
private Thread _receiveThread;