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