1
0
mirror of https://github.com/livingcomputermuseum/IFS.git synced 2026-02-19 22:15:20 +00:00

Cleanup, cleanup, cleanup. Fixed guest account a bit.

This commit is contained in:
Josh Dersch
2016-12-12 12:42:00 -08:00
parent c03d930bec
commit ef6bd4cc97
30 changed files with 782 additions and 531 deletions

View File

@@ -9,8 +9,6 @@ using System.Threading.Tasks;
namespace IFS
{
/// <summary>
/// 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.
/// </summary>
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
/// <summary>
/// Given a full user name (i.e. username.HOST), returns only the username portion.
/// (Given just a username, returns it unchanged.)
/// </summary>
/// <param name="fullUserName"></param>
/// <returns></returns>

View File

@@ -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
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
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);
}
/// <summary>
@@ -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);
}
/// <summary>
@@ -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);

View File

@@ -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;
}
/// <summary>
/// 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.
/// </summary>
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<uint, BSPChannel>();
_workers = new List<BSPWorkerBase>(Configuration.MaxWorkers);
}
public static void Shutdown()
{
foreach(BSPWorkerBase worker in _workers)
{
worker.Terminate();
}
}
/// <summary>
/// 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);
}
/// <summary>
@@ -203,6 +204,20 @@ namespace IFS.BSP
_activeChannels.Remove(channel.ServerPort.Socket);
}
public static List<BSPWorkerBase> EnumerateActiveWorkers()
{
return _workers;
}
public static int WorkerCount
{
get
{
return _workers.Count();
}
}
/// <summary>
/// Finds the appropriate channel for the given PUP.
/// </summary>
@@ -218,31 +233,7 @@ namespace IFS.BSP
{
return null;
}
}
/// <summary>
/// Generates a unique Socket ID.
/// </summary>
/// <returns></returns>
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
/// </summary>
private static Dictionary<UInt32, BSPChannel> _activeChannels;
private static UInt32 _nextSocketID;
private static readonly UInt32 _startingSocketID = 0x1000;
}
}

View File

@@ -29,6 +29,7 @@ namespace IFS.Boot
{
_numberToNameTable = new Dictionary<ushort, string>();
// 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;
}

View File

@@ -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
/// <summary>
/// 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.
/// </summary>
private byte[] _bolPacket =
{

View File

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

View File

@@ -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<MethodInfo> methods, string[] args)
private bool InvokeConsoleMethod(List<MethodInfo> 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)

View File

@@ -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", "<username> <password> [User|Admin] <full name> <home directory>")]
private void AddUser(string username, string newPassword, string privileges, string fullName, string homeDir)
[ConsoleFunction("add user", "Adds a new user account", "<username> <password> [User|Admin] <full name> <home directory>")]
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", "<username>")]
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<BSP.BSPWorkerBase> 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<EFTP.EFTPChannel> 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;

View File

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

View File

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

View File

@@ -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);
*/
}

View File

@@ -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<uint, EFTPChannel>();
}
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<EFTPChannel> EnumerateActiveChannels()
{
return _activeChannels.Values;
}
/// <summary>
/// Finds the appropriate channel for the given PUP.
/// </summary>
@@ -124,40 +128,10 @@ namespace IFS.EFTP
}
}
/// <summary>
/// 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...
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Map from socket address to BSP channel
/// </summary>
private static Dictionary<UInt32, EFTPChannel> _activeChannels;
private static UInt32 _nextSocketID;
private static readonly UInt32 _startingSocketID = UInt32.MaxValue;
}

View File

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

View File

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

View File

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

View File

@@ -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;
}
/// <summary>
@@ -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);
}
/// <summary>
@@ -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
/// <summary>
/// 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 <directory>"
/// This doesn't match the original IFS behavior but is a lot nicer to deal with as an end-user.
/// </summary>
/// <param name="fileSpec"></param>
/// <returns></returns>
@@ -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} <directory>", 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()

View File

@@ -80,7 +80,7 @@ namespace IFS.FTP
{
public PropertyList()
{
_propertyList = new Dictionary<string, string>();
_propertyList = new Dictionary<string, List<string>>();
}
/// <summary>
@@ -115,7 +115,7 @@ namespace IFS.FTP
}
/// <summary>
/// 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.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
@@ -123,6 +123,26 @@ namespace IFS.FTP
{
name = name.ToLowerInvariant();
if (_propertyList.ContainsKey(name))
{
return _propertyList[name][0];
}
else
{
return null;
}
}
/// <summary>
/// Returns the list of property values associated with the given property name, if present.
/// Otherwise returns null.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public List<string> GetPropertyValues(string name)
{
name = name.ToLowerInvariant();
if (_propertyList.ContainsKey(name))
{
return _propertyList[name];
@@ -133,17 +153,44 @@ namespace IFS.FTP
}
}
/// <summary>
/// Sets a single value for the specified property, if present.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void SetPropertyValue(string name, string value)
{
name = name.ToLowerInvariant();
name = name.ToLowerInvariant();
List<string> newpList = new List<string>();
newpList.Add(value);
if (_propertyList.ContainsKey(name))
{
_propertyList[name] = value;
{
_propertyList[name] = newpList;
}
else
{
_propertyList.Add(name, value);
_propertyList.Add(name, newpList);
}
}
/// <summary>
/// Sets multiple values for the specified property, if present.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void SetPropertyValues(string name, List<string> 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<string> newpList = new List<string>();
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<string, string> _propertyList;
private Dictionary<string, List<string>> _propertyList;
}
}

View File

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

128
PUP/Gateway/Router.cs Normal file
View File

@@ -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);
/// <summary>
/// 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.
/// </summary>
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);
}
/// <summary>
/// Sends a PUP out to the world; this may be routed to a different network.
/// </summary>
/// <param name="p"></param>
public void SendPup(PUP p)
{
RouteOutgoingPacket(p);
}
/// <summary>
/// Sends a raw packet out to the world. This packet will not be routed.
/// </summary>
/// <param name="data"></param>
/// <param name="source"></param>
/// <param name="destination"></param>
/// <param name="frameType"></param>
public void Send(byte[] data, byte source, byte destination, ushort frameType)
{
if (_rawPacketInterface != null)
{
_rawPacketInterface.Send(data, source, destination, frameType);
}
}
/// <summary>
/// Routes a PUP out to the world.
/// </summary>
/// <param name="p"></param>
private void RouteOutgoingPacket(PUP p)
{
// For now, we send the packet out without performing any routing.
_pupPacketInterface.Send(p);
}
/// <summary>
/// Routes a newly received packet to the proper destination host.
/// </summary>
/// <param name="pup"></param>
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);
}
}
/// <summary>
/// Our interface to a facility that can transmit/receive PUPs
/// </summary>
private IPupPacketInterface _pupPacketInterface;
/// <summary>
/// Our interface to a facility that can transmit raw Ethernet frames
/// </summary>
private IRawPacketInterface _rawPacketInterface;
private static Router _router = new Router();
private PUPProtocolDispatcher _localProtocolDispatcher;
}
}

View File

@@ -117,11 +117,13 @@
<Compile Include="Entrypoint.cs" />
<Compile Include="FTP\FTPServer.cs" />
<Compile Include="FTP\PropertyList.cs" />
<Compile Include="Gateway\Router.cs" />
<Compile Include="Logging\Log.cs" />
<Compile Include="GatewayInformationProtocol.cs" />
<Compile Include="Gateway\GatewayInformationProtocol.cs" />
<Compile Include="Mail\MailManager.cs" />
<Compile Include="MiscServicesProtocol.cs" />
<Compile Include="Serializer.cs" />
<Compile Include="SocketIDGenerator.cs" />
<Compile Include="Transport\Ethernet.cs" />
<Compile Include="PUPProtocolBase.cs" />
<Compile Include="PUP.cs" />

View File

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

View File

@@ -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.
}
/// <summary>
@@ -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<string> 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);
}
}

View File

@@ -230,7 +230,7 @@ namespace IFS
}
/// <summary>
///
/// Same as above, no garbage byte.
/// </summary>
/// <param name="type"></param>
/// <param name="id"></param>
@@ -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);
}

View File

@@ -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
/// <summary>
/// Private Constructor for this class, enforcing Singleton usage.
/// </summary>
private PUPProtocolDispatcher()
public PUPProtocolDispatcher()
{
_dispatchMap = new Dictionary<uint, PUPProtocolEntry>();
}
_dispatchMap = new Dictionary<uint, PUPProtocolEntry>();
/// <summary>
/// Accessor for singleton instance of this class.
/// </summary>
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);
}
/// <summary>
/// Registers a new protocol with the dispatcher.
/// </summary>
/// <param name="reg"></param>
/// <param name="impl"></param>
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);
}
}
/// <summary>
/// Our interface to a facility that can transmit/receive PUPs
/// </summary>
private IPupPacketInterface _pupPacketInterface;
/// <summary>
/// Our interface to a facility that can transmit raw Ethernet frames
/// Registers a new protocol with the dispatcher.
/// </summary>
private IRawPacketInterface _rawPacketInterface;
/// <param name="reg"></param>
/// <param name="impl"></param>
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();
}
/// <summary>
/// Map from socket to protocol implementation
/// </summary>
private Dictionary<UInt32, PUPProtocolEntry> _dispatchMap;
private int _packet;
private static PUPProtocolDispatcher _instance = new PUPProtocolDispatcher();
//
// Breath of Life server, which is its own thing.
private BreathOfLife _breathOfLifeServer;
}
}

View File

@@ -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("")]

View File

@@ -36,6 +36,11 @@ namespace IFS
public int Length;
}
/// <summary>
/// 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.
/// </summary>
public static class Serializer
{

63
PUP/SocketIDGenerator.cs Normal file
View File

@@ -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();
}
/// <summary>
/// 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.
/// <returns></returns>
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;
}
}

View File

@@ -13,91 +13,88 @@ using IFS.Logging;
using System.IO;
using System.Net.NetworkInformation;
using System.Threading;
using IFS.Gateway;
namespace IFS.Transport
{
/// <summary>
/// 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.
///
/// </summary>
public class Ethernet : IPupPacketInterface, IRawPacketInterface
{
public Ethernet(LivePacketDevice iface)
{
_interface = iface;
// Set up maps
_pupToEthernetMap = new Dictionary<byte, MacAddress>(256);
_ethernetToPupMap = new Dictionary<MacAddress, byte>(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);
}
}
/// <summary>
/// PUP<->Ethernet address map
/// </summary>
private Dictionary<byte, MacAddress> _pupToEthernetMap;
private Dictionary<MacAddress, byte> _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;

View File

@@ -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);
/// <summary>
/// IPupPacketInterface provides an abstraction over a transport (Ethernet, IP, Carrier Pigeon)
/// which can provide encapsulation for PUPs.
/// </summary>
public interface IPupPacketInterface
{
/// <summary>
/// Sends the given PUP over the transport.
/// </summary>
/// <param name="p"></param>
void Send(PUP p);
void RegisterReceiveCallback(HandlePup callback);
/// <summary>
/// Registers a callback (into the router) to be invoked on receipt of a PUP.
/// </summary>
/// <param name="callback"></param>
void RegisterRouterCallback(RoutePupCallback callback);
/// <summary>
/// Shuts down the interface.
/// </summary>
void Shutdown();
}
/// <summary>
/// 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.
/// </summary>
public interface IRawPacketInterface
{
/// <summary>
/// Sends the specified data over the transport.
/// </summary>
/// <param name="data"></param>
/// <param name="source"></param>
/// <param name="destination"></param>
/// <param name="frameType"></param>
void Send(byte[] data, byte source, byte destination, ushort frameType);
}
}

View File

@@ -6,12 +6,43 @@ using System.Threading;
using System.Net.NetworkInformation;
using IFS.Logging;
using System.IO;
using IFS.Gateway;
namespace IFS.Transport
{
/// <summary>
/// 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.
///
/// </summary>
public class UDPEncapsulation : IPupPacketInterface, IRawPacketInterface
{
@@ -64,14 +95,24 @@ namespace IFS.Transport
}
}
public void RegisterReceiveCallback(HandlePup callback)
/// <summary>
/// Registers a gateway to handle incoming PUPs.
/// </summary>
/// <param name="callback"></param>
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();
}
/// <summary>
/// Worker thread for UDP packet receipt.
/// </summary>
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;