mirror of
https://github.com/livingcomputermuseum/IFS.git
synced 2026-02-28 01:25:31 +00:00
Adding revised version of MicrocodeBootRequest, to support booting Dolphin and Dorado hardware.
242 lines
9.6 KiB
C#
242 lines
9.6 KiB
C#
/*
|
|
This file is part of IFS.
|
|
|
|
IFS is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
IFS is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with IFS. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
using System;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
|
|
using System.Threading;
|
|
using System.Net.NetworkInformation;
|
|
using IFS.Logging;
|
|
using System.IO;
|
|
using IFS.Gateway;
|
|
|
|
namespace IFS.Transport
|
|
{
|
|
/// <summary>
|
|
/// Implements the logic for encapsulating a 3mbit ethernet packet into/out of UDP datagrams.
|
|
/// Sent packets are broadcast to the subnet.
|
|
///
|
|
/// 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. It also makes it tricky to ensure an outgoing packet makes it to any and
|
|
/// all parties that may be interested (consider the Promiscuous Alto case.)
|
|
///
|
|
/// If each participant on the virtual network were to have a table mapping physical (UDP IP, 100M MAC) to
|
|
/// virtual (3M MAC) addresses then broadcasts could be avoided, but it complicates the logic in all
|
|
/// parties and requires each user to maintain this mapping table manually.
|
|
///
|
|
/// Resorting to using broadcasts at all times on the physical network removes these complications and
|
|
/// makes it easy for end-users to deal with.
|
|
/// The drawback is that broadcasts can reduce the efficiency of the network segment they're broadcast to.
|
|
/// However, most Alto networks are extremely quiet (by today's standards) -- the maximum throughput
|
|
/// of one Alto continuously transferring data to another is on the order of 20-30 kilobytes/sec.
|
|
/// (Most of the time, a given Alto will be completely silent.)
|
|
/// On a modern 100M or 1G network, this is background noise and modern computers receiving these broadcasts
|
|
/// will hardly notice.
|
|
///
|
|
/// Based on the above, and after a lot of experimentation, it was decided to err on the side of simplicity
|
|
/// and go with the broadcast implementation.
|
|
///
|
|
/// </summary>
|
|
public class UDPEncapsulation : IPacketInterface
|
|
{
|
|
public UDPEncapsulation(NetworkInterface iface)
|
|
{
|
|
// Try to set up UDP client.
|
|
try
|
|
{
|
|
_udpClient = new UdpClient(Configuration.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.
|
|
//
|
|
IPInterfaceProperties props = iface.GetIPProperties();
|
|
|
|
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), Configuration.UDPPort);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_broadcastEndpoint == null)
|
|
{
|
|
throw new InvalidOperationException(String.Format("No IPV4 network information was found for interface '{0}'.", iface.Name));
|
|
}
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Write(LogType.Error, LogComponent.UDP,
|
|
"Error configuring UDP socket {0} for use with IFS on interface {1}. Ensure that the selected network interface is valid, configured properly, and that nothing else is using this port.",
|
|
Configuration.UDPPort,
|
|
iface.Name);
|
|
|
|
Log.Write(LogType.Error, LogComponent.UDP,
|
|
"Error was '{0}'.",
|
|
e.Message);
|
|
|
|
_udpClient = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a gateway to handle incoming PUPs.
|
|
/// </summary>
|
|
/// <param name="callback"></param>
|
|
public void RegisterRouterCallback(ReceivedPacketCallback 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)
|
|
{
|
|
//
|
|
// Write PUP to UDP:
|
|
//
|
|
// Just send a broadcast UDP with the encapsulated frame inside of it.
|
|
//
|
|
byte[] encapsulatedFrame = PupPacketBuilder.BuildEncapsulatedEthernetFrameFromPup(p);
|
|
|
|
// Send as UDP broadcast.
|
|
_udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends an array of bytes over the network as a 3mbit packet encapsulated in a UDP datagram.
|
|
/// </summary>
|
|
/// <param name="packet"></param>
|
|
/// <param name="hostId"></param>
|
|
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 = PupPacketBuilder.BuildEncapsulatedEthernetFrameFromRawData(data, source, destination, frameType);
|
|
|
|
// Send as UDP broadcast.
|
|
_udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a stream of bytes over the network as a 3mbit packet encapsulated in a UDP datagram.
|
|
/// </summary>
|
|
/// <param name="encapsulatedPacketStream"></param>
|
|
public void Send(MemoryStream encapsulatedPacketStream)
|
|
{
|
|
// Send as UDP broadcast.
|
|
byte[] buf = encapsulatedPacketStream.ToArray();
|
|
_udpClient.Send(buf, buf.Length, _broadcastEndpoint);
|
|
}
|
|
|
|
private void Receive(MemoryStream packetStream)
|
|
{
|
|
_routerCallback(packetStream, this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begin receiving packets, forever.
|
|
/// </summary>
|
|
private void BeginReceive()
|
|
{
|
|
// Kick off receive thread.
|
|
_receiveThread = new Thread(ReceiveThread);
|
|
_receiveThread.Start();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Worker thread for UDP packet receipt.
|
|
/// </summary>
|
|
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, Configuration.UDPPort);
|
|
|
|
while (true)
|
|
{
|
|
byte[] data = _udpClient.Receive(ref groupEndPoint);
|
|
|
|
// Drop our own UDP packets.
|
|
if (!groupEndPoint.Address.Equals(_thisIPAddress) && groupEndPoint.Port == Configuration.UDPPort)
|
|
{
|
|
Receive(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 ReceivedPacketCallback _routerCallback;
|
|
|
|
// Thread used for receive
|
|
private Thread _receiveThread;
|
|
|
|
private UdpClient _udpClient;
|
|
private IPEndPoint _broadcastEndpoint;
|
|
|
|
// The IP address (unicast address) of the interface we're using to send UDP datagrams.
|
|
private IPAddress _thisIPAddress;
|
|
|
|
}
|
|
}
|