mirror of
https://github.com/livingcomputermuseum/ContrAlto.git
synced 2026-01-13 07:10:06 +00:00
244 lines
8.7 KiB
C#
244 lines
8.7 KiB
C#
/*
|
|
This file is part of ContrAlto.
|
|
|
|
ContrAlto 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.
|
|
|
|
ContrAlto 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 ContrAlto. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
using System;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
|
|
using Contralto.Logging;
|
|
using System.Threading;
|
|
using System.Net.NetworkInformation;
|
|
|
|
namespace Contralto.IO
|
|
{
|
|
/// <summary>
|
|
/// Implements the logic for encapsulating a 3mbit ethernet packet into/out of UDP datagrams.
|
|
/// Sent packets are broadcast to the subnet.
|
|
/// </summary>
|
|
public class UDPEncapsulation : IPacketEncapsulation
|
|
{
|
|
public UDPEncapsulation(string interfaceName)
|
|
{
|
|
// Try to set up UDP client.
|
|
try
|
|
{
|
|
_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
|
|
// for our UDP datagrams.
|
|
//
|
|
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
|
|
|
|
IPInterfaceProperties props = null;
|
|
foreach(NetworkInterface nic in nics)
|
|
{
|
|
if (nic.Name == interfaceName)
|
|
{
|
|
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.EthernetPacket,
|
|
"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.EthernetPacket,
|
|
"Error was '{0}'.",
|
|
e.Message);
|
|
|
|
_udpClient = null;
|
|
}
|
|
}
|
|
|
|
public void RegisterReceiveCallback(ReceivePacketDelegate callback)
|
|
{
|
|
// UDP connection could not be configured, can't receive anything.
|
|
if (_udpClient == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Set up input
|
|
_callback = callback;
|
|
BeginReceive();
|
|
}
|
|
|
|
public void Shutdown()
|
|
{
|
|
// Shut down the reciever thread.
|
|
|
|
if (_receiveThread != null)
|
|
{
|
|
_receiveThread.Abort();
|
|
}
|
|
|
|
if (_udpClient != null)
|
|
{
|
|
_udpClient.Close();
|
|
}
|
|
}
|
|
|
|
|
|
/// <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)
|
|
{
|
|
// UDP could not be configured, drop the packet.
|
|
if (_udpClient == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 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) >> 8);
|
|
packetBytes[1] = (byte)(length);
|
|
|
|
//
|
|
// 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] >> 8);
|
|
packetBytes[i * 2 + 3] = (byte)(packet[i]);
|
|
}
|
|
|
|
Log.Write(LogType.Verbose, LogComponent.HostNetworkInterface, "Sending packet via UDP; source {0} destination {1}, length {2} words.",
|
|
Conversion.ToOctal(packetBytes[3]),
|
|
Conversion.ToOctal(packetBytes[2]),
|
|
length);
|
|
|
|
_udpClient.Send(packetBytes, packetBytes.Length, _broadcastEndpoint);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begin receiving packets, forever.
|
|
/// </summary>
|
|
private void BeginReceive()
|
|
{
|
|
// Kick off receive thread.
|
|
_receiveThread = new Thread(ReceiveThread);
|
|
_receiveThread.Start();
|
|
}
|
|
|
|
private void ReceiveThread()
|
|
{
|
|
Log.Write(LogComponent.HostNetworkInterface, "UDP Receiver thread started.");
|
|
|
|
IPEndPoint groupEndPoint = new IPEndPoint(IPAddress.Any, _udpPort);
|
|
|
|
while (true)
|
|
{
|
|
byte[] data = _udpClient.Receive(ref groupEndPoint);
|
|
|
|
//
|
|
// Sanitize the data (at least make sure the length is valid):
|
|
//
|
|
if (data.Length < 4)
|
|
{
|
|
Log.Write(LogType.Verbose, LogComponent.HostNetworkInterface, "Invalid packet: Packet is fewer than 2 words long, dropping.");
|
|
continue;
|
|
}
|
|
|
|
// 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));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
// Callback delegate for received data.
|
|
private ReceivePacketDelegate _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;
|
|
}
|
|
}
|