mirror of
https://github.com/livingcomputermuseum/ContrAlto.git
synced 2026-04-28 12:48:34 +00:00
Finished up configuration UI. Config is now persisted to a configuration file. Small fixes to UDP encapsulation to ignore packets coming from the same host. Small fix for PInvoke for high res timer.
This commit is contained in:
@@ -21,25 +21,32 @@ namespace Contralto.IO
|
||||
Reset();
|
||||
|
||||
_fifoTransmitWakeupEvent = new Event(_fifoTransmitDuration, null, OutputFifoCallback);
|
||||
|
||||
|
||||
|
||||
// Attach real Ethernet device if user has specified one, otherwise leave unattached; output data
|
||||
// will go into a bit-bucket.
|
||||
switch(Configuration.HostPacketInterfaceType)
|
||||
try
|
||||
{
|
||||
case PacketInterfaceType.UDPEncapsulation:
|
||||
_hostInterface = new UDPEncapsulation(Configuration.HostPacketInterfaceName);
|
||||
_hostInterface.RegisterReceiveCallback(OnHostPacketReceived);
|
||||
break;
|
||||
switch (Configuration.HostPacketInterfaceType)
|
||||
{
|
||||
case PacketInterfaceType.UDPEncapsulation:
|
||||
_hostInterface = new UDPEncapsulation(Configuration.HostPacketInterfaceName);
|
||||
_hostInterface.RegisterReceiveCallback(OnHostPacketReceived);
|
||||
break;
|
||||
|
||||
case PacketInterfaceType.EthernetEncapsulation:
|
||||
_hostInterface = new HostEthernetEncapsulation(Configuration.HostPacketInterfaceName);
|
||||
_hostInterface.RegisterReceiveCallback(OnHostPacketReceived);
|
||||
break;
|
||||
case PacketInterfaceType.EthernetEncapsulation:
|
||||
_hostInterface = new HostEthernetEncapsulation(Configuration.HostPacketInterfaceName);
|
||||
_hostInterface.RegisterReceiveCallback(OnHostPacketReceived);
|
||||
break;
|
||||
|
||||
default:
|
||||
_hostInterface = null;
|
||||
break;
|
||||
default:
|
||||
_hostInterface = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_hostInterface = null;
|
||||
}
|
||||
|
||||
// More words than the Alto will ever send.
|
||||
@@ -359,7 +366,9 @@ namespace Contralto.IO
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Write(LogType.Error, LogComponent.EthernetPacket, "Packet queue has reached its limit of {0} packets, dropping incoming packet.", _maxQueuedPackets);
|
||||
Log.Write(LogType.Error, LogComponent.EthernetPacket, "Input packet queue has reached its limit of {0} packets, dropping oldest packet.", _maxQueuedPackets);
|
||||
_nextPackets.Dequeue();
|
||||
_nextPackets.Enqueue(data);
|
||||
}
|
||||
|
||||
_receiverLock.ExitWriteLock();
|
||||
@@ -367,7 +376,7 @@ namespace Contralto.IO
|
||||
|
||||
/// <summary>
|
||||
/// Runs the input state machine. This runs periodically (as scheduled by the Scheduler) and:
|
||||
/// 1) Drops incoming packets if the interface is off
|
||||
/// 1) Ignores incoming packets if the receiver is off.
|
||||
/// 2) Pulls incoming packets from the queue if the interface is active
|
||||
/// 3) Reads words from incoming packets into the controller's FIFO
|
||||
/// </summary>
|
||||
@@ -379,13 +388,16 @@ namespace Contralto.IO
|
||||
switch(_inputState)
|
||||
{
|
||||
case InputState.ReceiverOff:
|
||||
// Receiver is off, if we have any incoming packets, drop the first.
|
||||
// Receiver is off, if we have any incoming packets, they are ignored.
|
||||
// TODO: would it make sense to expire really old packets (say more than a couple of seconds old)
|
||||
// so that the receiver doesn't pick up ancient history the next time it runs?
|
||||
// We already cycle out packets as new ones come in, so this would only be an issue on very quiet networks.
|
||||
// (And even then I don't know if it's really an issue.)
|
||||
_receiverLock.EnterReadLock();
|
||||
|
||||
if (_nextPackets.Count > 0)
|
||||
{
|
||||
_nextPackets.Dequeue();
|
||||
Log.Write(LogComponent.EthernetPacket, "Receiver is off, dropped incoming packet from packet queue.");
|
||||
Log.Write(LogComponent.EthernetPacket, "Receiver is off, ignoring incoming packet from packet queue.");
|
||||
}
|
||||
_receiverLock.ExitReadLock();
|
||||
break;
|
||||
|
||||
281
Contralto/IO/HostEthernet.cs
Normal file
281
Contralto/IO/HostEthernet.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using PcapDotNet.Base;
|
||||
using PcapDotNet.Core;
|
||||
using PcapDotNet.Core.Extensions;
|
||||
using PcapDotNet.Packets;
|
||||
using PcapDotNet.Packets.Ethernet;
|
||||
using System.IO;
|
||||
using Contralto.Logging;
|
||||
using System.Threading;
|
||||
|
||||
namespace Contralto.IO
|
||||
{
|
||||
|
||||
public struct EthernetInterface
|
||||
{
|
||||
public EthernetInterface(string name, string description, MacAddress macAddress)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
MacAddress = macAddress;
|
||||
}
|
||||
|
||||
public static List<EthernetInterface> EnumerateDevices()
|
||||
{
|
||||
List<EthernetInterface> interfaces = new List<EthernetInterface>();
|
||||
|
||||
foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
|
||||
{
|
||||
interfaces.Add(new EthernetInterface(device.Name, device.Description, device.GetMacAddress()));
|
||||
}
|
||||
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
public string Name;
|
||||
public string Description;
|
||||
public MacAddress MacAddress;
|
||||
}
|
||||
|
||||
public delegate void ReceivePacketDelegate(MemoryStream data);
|
||||
|
||||
/// <summary>
|
||||
/// Implements the logic for encapsulating a 3mbit ethernet packet into a 10mb packet and sending it over an actual
|
||||
/// interface controlled by the host operating system.
|
||||
///
|
||||
/// This uses PCap.NET to do the dirty work.
|
||||
/// </summary>
|
||||
public class HostEthernet
|
||||
{
|
||||
public HostEthernet(EthernetInterface iface)
|
||||
{
|
||||
AttachInterface(iface);
|
||||
}
|
||||
|
||||
public HostEthernet(string name)
|
||||
{
|
||||
// Find the specified device by name
|
||||
List<EthernetInterface> interfaces = EthernetInterface.EnumerateDevices();
|
||||
|
||||
foreach (EthernetInterface i in interfaces)
|
||||
{
|
||||
if (name == i.Name)
|
||||
{
|
||||
AttachInterface(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Specified ethernet interface does not exist.");
|
||||
}
|
||||
|
||||
public void RegisterReceiveCallback(ReceivePacketDelegate callback)
|
||||
{
|
||||
_callback = callback;
|
||||
|
||||
// Now that we have a callback we can start receiving stuff.
|
||||
Open(true /* promiscuous */, int.MaxValue);
|
||||
BeginReceive();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends an array of bytes over the ethernet as a 3mbit packet encapsulated in a 10mbit packet.
|
||||
/// </summary>
|
||||
/// <param name="packet"></param>
|
||||
/// <param name="hostId"></param>
|
||||
public void Send(ushort[] packet, int length)
|
||||
{
|
||||
// Sanity check.
|
||||
if (length < 1)
|
||||
{
|
||||
throw new InvalidOperationException("Raw packet data must contain at least two bytes for addressing.");
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Outgoing packet contains 1 extra word (2 bytes) containing
|
||||
// the prepended packet length (one word)
|
||||
byte[] packetBytes = new byte[length * 2 + 2];
|
||||
|
||||
//
|
||||
// First two bytes include the length of the 3mbit packet; since 10mbit packets have a minimum length of 46
|
||||
// bytes, and 3mbit packets have no minimum length this is necessary so the receiver can pull out the
|
||||
// correct amount of data.
|
||||
//
|
||||
packetBytes[0] = (byte)(length);
|
||||
packetBytes[1] = (byte)((length) >> 8);
|
||||
|
||||
//
|
||||
// Do this annoying dance to stuff the ushorts into bytes because this is C#.
|
||||
//
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
packetBytes[i * 2 + 2] = (byte)(packet[i]);
|
||||
packetBytes[i * 2 + 3] = (byte)(packet[i] >> 8);
|
||||
}
|
||||
|
||||
//
|
||||
// Grab the source and destination host addresses from the packet we're sending
|
||||
// and build 10mbit versions.
|
||||
//
|
||||
byte destinationHost = packetBytes[3];
|
||||
byte sourceHost = packetBytes[2];
|
||||
|
||||
Log.Write(LogComponent.HostEthernet, "Sending packet; source {0} destination {1}, length {2} words.",
|
||||
Conversion.ToOctal(sourceHost),
|
||||
Conversion.ToOctal(destinationHost),
|
||||
length);
|
||||
|
||||
MacAddress destinationMac = Get10mbitDestinationMacFrom3mbit(destinationHost);
|
||||
MacAddress sourceMac = new MacAddress((UInt48)(_10mbitMACPrefix | Configuration.HostAddress));
|
||||
|
||||
// Build the outgoing packet; place the source/dest addresses, type field and the raw data.
|
||||
EthernetLayer ethernetLayer = new EthernetLayer
|
||||
{
|
||||
Source = sourceMac,
|
||||
Destination = destinationMac,
|
||||
EtherType = (EthernetType)_3mbitFrameType,
|
||||
};
|
||||
|
||||
PayloadLayer payloadLayer = new PayloadLayer
|
||||
{
|
||||
Data = new Datagram(packetBytes),
|
||||
};
|
||||
|
||||
PacketBuilder builder = new PacketBuilder(ethernetLayer, payloadLayer);
|
||||
|
||||
// Send it over the 'net!
|
||||
_communicator.SendPacket(builder.Build(DateTime.Now));
|
||||
|
||||
Log.Write(LogComponent.HostEthernet, "Encapsulated 3mbit packet sent.");
|
||||
}
|
||||
|
||||
private void ReceiveCallback(Packet p)
|
||||
{
|
||||
//
|
||||
// Filter out packets intended for the emulator, forward them on, drop everything else.
|
||||
//
|
||||
if ((int)p.Ethernet.EtherType == _3mbitFrameType && // encapsulated 3mbit frames
|
||||
((p.Ethernet.Destination.ToValue() & 0xffffffffff00) == _10mbitMACPrefix || // addressed to any emulator OR
|
||||
p.Ethernet.Destination.ToValue() == _10mbitBroadcast) && // broadcast
|
||||
(p.Ethernet.Source.ToValue() != (UInt48)(_10mbitMACPrefix | Configuration.HostAddress))) // and not sent by this emulator
|
||||
{
|
||||
Log.Write(LogComponent.HostEthernet, "Received encapsulated 3mbit packet.");
|
||||
_callback(p.Ethernet.Payload.ToMemoryStream());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not for us, discard the packet.
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachInterface(EthernetInterface iface)
|
||||
{
|
||||
_interface = null;
|
||||
|
||||
// Find the specified device by name
|
||||
foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
|
||||
{
|
||||
if (device.Name == iface.Name && device.GetMacAddress() == iface.MacAddress)
|
||||
{
|
||||
_interface = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_interface == null)
|
||||
{
|
||||
throw new InvalidOperationException("Requested interface not found.");
|
||||
}
|
||||
|
||||
Log.Write(LogComponent.HostEthernet, "Attached to host interface {0}", iface.Name);
|
||||
}
|
||||
|
||||
private void Open(bool promiscuous, int timeout)
|
||||
{
|
||||
_communicator = _interface.Open(65536, promiscuous ? PacketDeviceOpenAttributes.MaximumResponsiveness | PacketDeviceOpenAttributes.Promiscuous : PacketDeviceOpenAttributes.MaximumResponsiveness, timeout);
|
||||
|
||||
// Set this to 1 so we'll get packets as soon as they arrive, no buffering.
|
||||
_communicator.SetKernelMinimumBytesToCopy(1);
|
||||
|
||||
Log.Write(LogComponent.HostEthernet, "Host interface opened and receiving packets.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin receiving packets, forever.
|
||||
/// </summary>
|
||||
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.HostEthernet, "Receiver thread started.");
|
||||
_communicator.ReceivePackets(-1, ReceiveCallback);
|
||||
}
|
||||
|
||||
private MacAddress Get10mbitDestinationMacFrom3mbit(byte destinationHost)
|
||||
{
|
||||
MacAddress destinationMac;
|
||||
|
||||
if (destinationHost == _3mbitBroadcast)
|
||||
{
|
||||
// 3mbit broadcast gets translated to 10mbit broadcast
|
||||
destinationMac = new MacAddress(_10mbitBroadcast);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check addressing table for external (non emulator) addresses;
|
||||
// otherwise just address other emulators
|
||||
// TODO: implement table. Currently hardcoded address 1 to test IFS on dev machine
|
||||
//
|
||||
if (destinationHost == 1)
|
||||
{
|
||||
destinationMac = new MacAddress((UInt48)(_ifsTestMAC));
|
||||
}
|
||||
else
|
||||
{
|
||||
destinationMac = new MacAddress((UInt48)(_10mbitMACPrefix | destinationHost)); // emulator destination address
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return destinationMac;
|
||||
}
|
||||
|
||||
private LivePacketDevice _interface;
|
||||
private PacketCommunicator _communicator;
|
||||
private ReceivePacketDelegate _callback;
|
||||
|
||||
|
||||
// Thread used for receive
|
||||
private Thread _receiveThread;
|
||||
|
||||
private const int _3mbitFrameType = 0xbeef; // easy to identify, ostensibly unused by anything of any import
|
||||
|
||||
/// <summary>
|
||||
/// On output, these bytes are prepended to the Alto's 3mbit (1 byte) address to form a full
|
||||
/// 6 byte Ethernet MAC.
|
||||
/// On input, ethernet frames are checked for this prefix
|
||||
/// </summary>
|
||||
private UInt48 _10mbitMACPrefix = 0x0000aa010200; // 00-00-AA is the Xerox vendor code, used just to be cute.
|
||||
|
||||
private UInt48 _10mbitBroadcast = (UInt48)0xffffffffffff;
|
||||
private const int _3mbitBroadcast = 0;
|
||||
|
||||
// Temporary; to be replaced with an external address mapping table
|
||||
private UInt48 _ifsTestMAC = (UInt48)0x001060b88e3e;
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ namespace Contralto.IO
|
||||
|
||||
foreach (EthernetInterface i in interfaces)
|
||||
{
|
||||
if (name == i.Name)
|
||||
if (name == i.Description)
|
||||
{
|
||||
AttachInterface(i);
|
||||
return;
|
||||
@@ -183,7 +183,7 @@ namespace Contralto.IO
|
||||
// Find the specified device by name
|
||||
foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
|
||||
{
|
||||
if (device.Description == iface.Name)
|
||||
if (device.Description == iface.Description)
|
||||
{
|
||||
_interface = device;
|
||||
break;
|
||||
|
||||
@@ -119,8 +119,7 @@ namespace Contralto.IO
|
||||
// If we had been holding boot keys, release them now that a real user is pressing a key.
|
||||
if (_bootKeysPressed)
|
||||
{
|
||||
Reset();
|
||||
_bootKeysPressed = false;
|
||||
Reset();
|
||||
}
|
||||
|
||||
AltoKeyBit bits = _keyMap[key];
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace Contralto.IO
|
||||
{
|
||||
_udpClient = new UdpClient(_udpPort, AddressFamily.InterNetwork);
|
||||
_udpClient.EnableBroadcast = true;
|
||||
_udpClient.Client.Blocking = true;
|
||||
_udpClient.Client.MulticastLoopback = false;
|
||||
|
||||
//
|
||||
// Grab the broadcast address for the interface so that we know what broadcast address to use
|
||||
@@ -31,7 +33,7 @@ namespace Contralto.IO
|
||||
IPInterfaceProperties props = null;
|
||||
foreach(NetworkInterface nic in nics)
|
||||
{
|
||||
if (nic.Description.ToLowerInvariant() == interfaceName.ToLowerInvariant())
|
||||
if (nic.Description == interfaceName)
|
||||
{
|
||||
props = nic.GetIPProperties();
|
||||
break;
|
||||
@@ -49,7 +51,8 @@ namespace Contralto.IO
|
||||
// go with it.
|
||||
if (unicast.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
_broadcastEndpoint = new IPEndPoint(GetBroadcastAddress(unicast.Address, unicast.IPv4Mask), _udpPort);
|
||||
_thisIPAddress = unicast.Address;
|
||||
_broadcastEndpoint = new IPEndPoint(GetBroadcastAddress(_thisIPAddress, unicast.IPv4Mask), _udpPort);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -173,8 +176,12 @@ namespace Contralto.IO
|
||||
|
||||
// TODO: sanitize data before handing it off.
|
||||
|
||||
Log.Write(LogComponent.HostNetworkInterface, "Received UDP-encapsulated 3mbit packet.");
|
||||
_callback(new System.IO.MemoryStream(data));
|
||||
// Drop our own UDP packets.
|
||||
if (!groupEndPoint.Address.Equals(_thisIPAddress))
|
||||
{
|
||||
Log.Write(LogComponent.HostNetworkInterface, "Received UDP-encapsulated 3mbit packet.");
|
||||
_callback(new System.IO.MemoryStream(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,5 +211,7 @@ namespace Contralto.IO
|
||||
private UdpClient _udpClient;
|
||||
private IPEndPoint _broadcastEndpoint;
|
||||
|
||||
// The IP address (unicast address) of the interface we're using to send UDP datagrams.
|
||||
private IPAddress _thisIPAddress;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user