From 22b9676b7a2e63fcb528502109c76069888dc04e Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Wed, 2 Mar 2016 17:22:51 -0800 Subject: [PATCH] Implemented UDP transport, fixed a couple of small BSP issues uncovered by it. --- PUP/BSP/BSPChannel.cs | 24 ++- PUP/BSP/BSPManager.cs | 2 + PUP/CopyDisk/CopyDiskServer.cs | 19 ++- PUP/Entrypoint.cs | 10 +- PUP/FTP/FTPServer.cs | 2 + PUP/IFS.csproj | 1 + PUP/Logging/Log.cs | 1 + PUP/PUPProtocolDispatcher.cs | 13 +- PUP/Transport/Ethernet.cs | 8 +- PUP/Transport/UDP.cs | 301 +++++++++++++++++++++++++++++++++ 10 files changed, 360 insertions(+), 21 deletions(-) create mode 100644 PUP/Transport/UDP.cs diff --git a/PUP/BSP/BSPChannel.cs b/PUP/BSP/BSPChannel.cs index 4e2a88f..a817a78 100644 --- a/PUP/BSP/BSPChannel.cs +++ b/PUP/BSP/BSPChannel.cs @@ -146,6 +146,13 @@ namespace IFS.BSP throw new InvalidOperationException("count + offset must be less than or equal to the length of the buffer being read into."); } + if (count == 0) + { + // Honor requests to read 0 bytes always, since technically 0 bytes are always available. + data = new byte[0]; + return 0; + } + int read = 0; // @@ -410,7 +417,7 @@ namespace IFS.BSP } // Send the data. - PUP dataPup = new PUP(PupType.Data, _sendPos, _clientConnectionPort, _serverConnectionPort, chunk); + PUP dataPup = new PUP(flush? PupType.AData : PupType.Data, _sendPos, _clientConnectionPort, _serverConnectionPort, chunk); SendDataPup(dataPup); } } @@ -480,9 +487,11 @@ namespace IFS.BSP ack.BytesSent = MaxBytes; _inputLock.ExitReadLock(); - PUP ackPup = new PUP(PupType.Ack, _recvPos, _clientConnectionPort, _serverConnectionPort, Serializer.Serialize(ack)); + PUP ackPup = new PUP(PupType.Ack, _recvPos, _clientConnectionPort, _serverConnectionPort, Serializer.Serialize(ack)); PUPProtocolDispatcher.Instance.SendPup(ackPup); + + Log.Write(LogType.Verbose, LogComponent.BSP, "ACK sent."); } /// @@ -591,8 +600,9 @@ namespace IFS.BSP // // If we've sent as many PUPs to the client as it says it can take, + // or we've sent all pups currently in the output window, // we need to change the PUP to an AData PUP so we can acknowledge - // acceptance of the entire window we've sent. + // acceptance of the window we've sent. // bool bAck = false; if (_outputWindowIndex >= _clientLimits.MaxPups) @@ -607,18 +617,20 @@ namespace IFS.BSP // if (nextPup.Type == PupType.Data || nextPup.Type == PupType.AData) { - _outputWindow[_outputWindowIndex - 1] = nextPup = new PUP(bAck ? PupType.AData : PupType.Data, _sendPos, nextPup.DestinationPort, nextPup.SourcePort, nextPup.Contents); + _outputWindow[_outputWindowIndex - 1] = nextPup = new PUP(bAck ? PupType.AData : nextPup.Type, _sendPos, nextPup.DestinationPort, nextPup.SourcePort, nextPup.Contents); } else if (nextPup.Type == PupType.Mark || nextPup.Type == PupType.AMark) { - _outputWindow[_outputWindowIndex - 1] = nextPup = new PUP(bAck ? PupType.AMark : PupType.Mark, _sendPos, nextPup.DestinationPort, nextPup.SourcePort, nextPup.Contents); + _outputWindow[_outputWindowIndex - 1] = nextPup = new PUP(bAck ? PupType.AMark : nextPup.Type, _sendPos, nextPup.DestinationPort, nextPup.SourcePort, nextPup.Contents); } // // Send it! // _sendPos += (uint)nextPup.Contents.Length; - PUPProtocolDispatcher.Instance.SendPup(nextPup); + PUPProtocolDispatcher.Instance.SendPup(nextPup); + + Log.Write(LogType.Verbose, LogComponent.BSP, "Sent data PUP. Current position is {0}, output window count is {1}", _sendPos, _outputWindow.Count); // // If we required an ACK, wait for it to arrive so we can confirm client reception of data. diff --git a/PUP/BSP/BSPManager.cs b/PUP/BSP/BSPManager.cs index 091193c..21d7de4 100644 --- a/PUP/BSP/BSPManager.cs +++ b/PUP/BSP/BSPManager.cs @@ -98,6 +98,8 @@ namespace IFS.BSP return; } + Log.Write(LogType.Verbose, LogComponent.BSP, "BSP pup is {0}", p.Type); + switch (p.Type) { case PupType.RFC: diff --git a/PUP/CopyDisk/CopyDiskServer.cs b/PUP/CopyDisk/CopyDiskServer.cs index de68049..e68db15 100644 --- a/PUP/CopyDisk/CopyDiskServer.cs +++ b/PUP/CopyDisk/CopyDiskServer.cs @@ -411,6 +411,18 @@ namespace IFS.CopyDisk } break; + case CopyDiskBlock.HereAreDiskParams: + { + HereAreDiskParamsBFSBlock diskParams = (HereAreDiskParamsBFSBlock)Serializer.Deserialize(data, typeof(HereAreDiskParamsBFSBlock)); + + Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Disk params are: Type {0}, C/H/S {1}/{2}/{3}", + diskParams.DiskType, + diskParams.Cylinders, + diskParams.Heads, + diskParams.Sectors); + + } + break; case CopyDiskBlock.RetrieveDisk: case CopyDiskBlock.StoreDisk: @@ -524,19 +536,20 @@ namespace IFS.CopyDisk Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Saving {0}...", _pack.PackName); _pack.Save(packStream, true /* reverse byte order */); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Saved."); - } + } } catch(Exception e) { // Log error, reset state. - Log.Write(LogType.Error, LogComponent.CopyDisk, "Failed to save pack {0} - {1}", _pack.PackName, e.Message); - } + Log.Write(LogType.Error, LogComponent.CopyDisk, "Failed to save pack {0} - {1}", _pack.PackName, e.Message); + } } } break; case CopyDiskBlock.SendErrors: { + 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)); diff --git a/PUP/Entrypoint.cs b/PUP/Entrypoint.cs index e48ec3c..0185912 100644 --- a/PUP/Entrypoint.cs +++ b/PUP/Entrypoint.cs @@ -5,6 +5,7 @@ using IFS.Transport; using System; using System.Collections.Generic; using System.Linq; +using System.Net.NetworkInformation; using System.Text; using System.Threading.Tasks; @@ -13,14 +14,17 @@ namespace IFS public class Entrypoint { static void Main(string[] args) - { + { + List ifaces = EthernetInterface.EnumerateDevices(); Console.WriteLine("available interfaces are:"); foreach(EthernetInterface i in ifaces) { Console.WriteLine(String.Format("{0} - address {1}, desc {2} ", i.Name, i.MacAddress, i.Description)); - } + } + + NetworkInterface[] netfaces = NetworkInterface.GetAllNetworkInterfaces(); // Set up protocols: @@ -38,7 +42,7 @@ namespace IFS // TODO: MAKE THIS CONFIGURABLE. - PUPProtocolDispatcher.Instance.RegisterInterface(ifaces[2]); + PUPProtocolDispatcher.Instance.RegisterInterface(netfaces[0].Description); while (true) { diff --git a/PUP/FTP/FTPServer.cs b/PUP/FTP/FTPServer.cs index 046c077..2815f2d 100644 --- a/PUP/FTP/FTPServer.cs +++ b/PUP/FTP/FTPServer.cs @@ -153,6 +153,8 @@ namespace IFS.FTP byte[] data = null; FTPCommand command = ReadNextCommandWithData(out data); + + Log.Write(LogType.Verbose, LogComponent.FTP, "FTP command is {0}", command); // // At this point we should have the entire command, execute it. diff --git a/PUP/IFS.csproj b/PUP/IFS.csproj index b969f0d..dd73410 100644 --- a/PUP/IFS.csproj +++ b/PUP/IFS.csproj @@ -98,6 +98,7 @@ + diff --git a/PUP/Logging/Log.cs b/PUP/Logging/Log.cs index d01eebc..dae89a5 100644 --- a/PUP/Logging/Log.cs +++ b/PUP/Logging/Log.cs @@ -23,6 +23,7 @@ namespace IFS.Logging BreathOfLife = 0x100, EFTP = 0x200, BootServer = 0x400, + UDP = 0x800, All = 0x7fffffff } diff --git a/PUP/PUPProtocolDispatcher.cs b/PUP/PUPProtocolDispatcher.cs index 98b866e..bbebb64 100644 --- a/PUP/PUPProtocolDispatcher.cs +++ b/PUP/PUPProtocolDispatcher.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading.Tasks; using PcapDotNet.Base; +using System.Net.NetworkInformation; namespace IFS { @@ -34,13 +35,15 @@ namespace IFS get { return _instance; } } - public void RegisterInterface(EthernetInterface i) + public void RegisterInterface(string description) { // TODO: support multiple interfaces (for gateway routing, for example.) - // Also, this should not be ethernet-specific. - Ethernet enet = new Ethernet(i); - _pupPacketInterface = enet as IPupPacketInterface; - _rawPacketInterface = enet as IRawPacketInterface; + // TODO: support configuration options for backend. + //Ethernet enet = new Ethernet(i.Description); + + UDPEncapsulation udp = new UDPEncapsulation(description); + _pupPacketInterface = udp as IPupPacketInterface; + _rawPacketInterface = udp as IRawPacketInterface; _pupPacketInterface.RegisterReceiveCallback(OnPupReceived); } diff --git a/PUP/Transport/Ethernet.cs b/PUP/Transport/Ethernet.cs index 87d48c8..41b8924 100644 --- a/PUP/Transport/Ethernet.cs +++ b/PUP/Transport/Ethernet.cs @@ -45,9 +45,9 @@ namespace IFS.Transport /// public class Ethernet : IPupPacketInterface, IRawPacketInterface { - public Ethernet(EthernetInterface iface) + public Ethernet(string ifaceName) { - AttachInterface(iface); + AttachInterface(ifaceName); // Set up maps _pupToEthernetMap = new Dictionary(256); @@ -246,14 +246,14 @@ namespace IFS.Transport } } - private void AttachInterface(EthernetInterface iface) + private void AttachInterface(string ifaceName) { _interface = null; // Find the specified device by name foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine) { - if (device.Name == iface.Name && device.GetMacAddress() == iface.MacAddress) + if (device.Description == ifaceName) { _interface = device; break; diff --git a/PUP/Transport/UDP.cs b/PUP/Transport/UDP.cs new file mode 100644 index 0000000..f1e161e --- /dev/null +++ b/PUP/Transport/UDP.cs @@ -0,0 +1,301 @@ +using System; +using System.Net; +using System.Net.Sockets; + +using System.Threading; +using System.Net.NetworkInformation; +using IFS.Logging; +using System.IO; + +namespace IFS.Transport +{ + /// + /// Implements the logic for encapsulating a 3mbit ethernet packet into/out of UDP datagrams. + /// Sent packets are broadcast to the subnet. + /// + public class UDPEncapsulation : IPupPacketInterface, IRawPacketInterface + { + public UDPEncapsulation(string interfaceName) + { + // Try to set up UDP client. + try + { + _udpClient = new UdpClient(_udpPort, AddressFamily.InterNetwork); + _udpClient.Client.Blocking = true; + _udpClient.EnableBroadcast = true; + _udpClient.MulticastLoopback = false; + + + // + // Grab the broadcast address for the interface so that we know what broadcast address to use + // for our UDP datagrams. + // + NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); + + IPInterfaceProperties props = null; + foreach (NetworkInterface nic in nics) + { + if (nic.Description.ToLowerInvariant() == interfaceName.ToLowerInvariant()) + { + props = nic.GetIPProperties(); + break; + } + } + + if (props == null) + { + throw new InvalidOperationException(String.Format("No interface matching description '{0}' was found.", interfaceName)); + } + + foreach (UnicastIPAddressInformation unicast in props.UnicastAddresses) + { + // Find the first InterNetwork address for this interface and + // go with it. + if (unicast.Address.AddressFamily == AddressFamily.InterNetwork) + { + _thisIPAddress = unicast.Address; + _broadcastEndpoint = new IPEndPoint(GetBroadcastAddress(_thisIPAddress, unicast.IPv4Mask), _udpPort); + break; + } + } + + if (_broadcastEndpoint == null) + { + throw new InvalidOperationException(String.Format("No IPV4 network information was found for interface '{0}'.", interfaceName)); + } + + } + catch (Exception e) + { + Log.Write(LogType.Error, LogComponent.UDP, + "Error configuring UDP socket {0} for use with ContrAlto on interface {1}. Ensure that the selected network interface is valid, configured properly, and that nothing else is using this port.", + _udpPort, + interfaceName); + + Log.Write(LogType.Error, LogComponent.UDP, + "Error was '{0}'.", + e.Message); + + _udpClient = null; + } + } + + public void RegisterReceiveCallback(HandlePup callback) + { + _callback = callback; + + // Now that we have a callback we can start receiving stuff. + BeginReceive(); + } + + public void Send(PUP p) + { + // + // Write PUP to UDP: + // + // For now, no actual routing (Gateway not implemented yet), everything is on the same 'net. + // Just send a broadcast UDP with the encapsulated frame inside of it. + // + + // 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); + + // Byte swap + encapsulatedFrame = ByteSwap(encapsulatedFrame); + + // Send as UDP broadcast. + // TODO: this could be done without broadcasts if we kept a table mapping IPs to 3mbit MACs. + _udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint); + } + + /// + /// Sends an array of bytes over the ethernet as a 3mbit packet encapsulated in a 10mbit packet. + /// + /// + /// + public void Send(byte[] data, byte source, byte destination, ushort frameType) + { + // 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 + data.Length]; + + // 3mbit Packet length + encapsulatedFrame[0] = (byte)((data.Length / 2 + 2) >> 8); + encapsulatedFrame[1] = (byte)(data.Length / 2 + 2); + + // addressing + encapsulatedFrame[2] = destination; + encapsulatedFrame[3] = source; + + // frame type + encapsulatedFrame[4] = (byte)(frameType >> 8); + encapsulatedFrame[5] = (byte)frameType; + + // Actual data + data.CopyTo(encapsulatedFrame, 6); + + // Byte swap + encapsulatedFrame = ByteSwap(encapsulatedFrame); + + // Send as UDP broadcast. + // TODO: this could be done without broadcasts if we kept a table mapping IPs to 3mbit MACs. + _udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint); + } + + private void Receive(MemoryStream packetStream) + { + // + // Look for PUPs, forward them on. + // + + // Read the length prefix (in words), convert to bytes. + // Subtract off 2 words for the ethernet header + int length = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte())) * 2 - 4; + + // Read the address (1st word of 3mbit packet) + byte destination = (byte)packetStream.ReadByte(); + byte source = (byte)packetStream.ReadByte(); + + // Read the type and switch on it + int etherType3mbit = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte())); + + 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 + { + // Not for our network. + Log.Write(LogType.Verbose, LogComponent.Ethernet, "PUP is for network {0}, dropping.", pup.DestinationPort.Network); + } + } + else + { + Log.Write(LogType.Warning, LogComponent.Ethernet, "UDP packet is not a PUP, dropping"); + } + } + + /// + /// Begin receiving packets, forever. + /// + private void BeginReceive() + { + // Kick off receive thread. + _receiveThread = new Thread(ReceiveThread); + _receiveThread.Start(); + } + + private void ReceiveThread() + { + // Just call ReceivePackets, that's it. This will never return. + // (probably need to make this more elegant so we can tear down the thread + // properly.) + Log.Write(LogComponent.UDP, "UDP Receiver thread started."); + + IPEndPoint groupEndPoint = new IPEndPoint(IPAddress.Any, _udpPort); + + while (true) + { + byte[] data = _udpClient.Receive(ref groupEndPoint); + + // Drop our own UDP packets. + if (!groupEndPoint.Address.Equals(_thisIPAddress)) + { + Receive(ByteSwap(new System.IO.MemoryStream(data))); + } + } + } + + + private IPAddress GetBroadcastAddress(IPAddress address, IPAddress subnetMask) + { + byte[] ipAdressBytes = address.GetAddressBytes(); + byte[] subnetMaskBytes = subnetMask.GetAddressBytes(); + + byte[] broadcastAddress = new byte[ipAdressBytes.Length]; + for (int i = 0; i < broadcastAddress.Length; i++) + { + broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255)); + } + + return new IPAddress(broadcastAddress); + } + + private MemoryStream ByteSwap(MemoryStream input) + { + byte[] buffer = new byte[input.Length]; + + input.Read(buffer, 0, buffer.Length); + + for (int i = 0; i < buffer.Length; i += 2) + { + byte temp = buffer[i]; + buffer[i] = buffer[i + 1]; + buffer[i + 1] = temp; + } + + input.Position = 0; + + return new MemoryStream(buffer); + } + + private byte[] ByteSwap(byte[] input) + { + for (int i = 0; i < input.Length; i += 2) + { + byte temp = input[i]; + input[i] = input[i + 1]; + input[i + 1] = temp; + } + + return input; + } + + // The ethertype used in the encapsulated 3mbit frame + private readonly ushort _pupFrameType = 512; + + private HandlePup _callback; + + // Thread used for receive + private Thread _receiveThread; + + // UDP port (TODO: make configurable?) + private const int _udpPort = 42424; + private UdpClient _udpClient; + private IPEndPoint _broadcastEndpoint; + + // The IP address (unicast address) of the interface we're using to send UDP datagrams. + private IPAddress _thisIPAddress; + + } +}