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:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 =
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
128
PUP/Gateway/Router.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("")]
|
||||
|
||||
|
||||
@@ -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
63
PUP/SocketIDGenerator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user