1
0
mirror of https://github.com/livingcomputermuseum/IFS.git synced 2026-04-09 22:28:00 +00:00

Added gateway routing services. Minor cleanup.

This commit is contained in:
Josh Dersch
2017-06-06 13:22:39 -07:00
parent 8975478a1c
commit c165ba40a7
14 changed files with 862 additions and 130 deletions

View File

@@ -18,10 +18,6 @@
using IFS.Gateway;
using IFS.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace IFS
@@ -35,7 +31,11 @@ namespace IFS
{
public BreathOfLife()
{
Log.Write(LogType.Verbose, LogComponent.BreathOfLife, "Breath Of Life service starting. Broadcast interval is {0} milliseconds.", _bolPacketDelay);
Log.Write(LogType.Verbose,
LogComponent.BreathOfLife,
"Breath Of Life service starting. Broadcast interval is {0} milliseconds.",
Configuration.BOLDelay);
_bolThread = new Thread(BreathOfLifeThread);
_bolThread.Start();
}
@@ -51,7 +51,8 @@ namespace IFS
{
//
// Send BOL
//
//
Router.Instance.Send(_bolPacket, DirectoryServices.Instance.LocalHost, _bolAddress, _bolPacketType);
Log.Write(LogType.Verbose, LogComponent.BreathOfLife, "Breath Of Life packet sent.");
@@ -59,7 +60,7 @@ namespace IFS
//
// Go to sleep.
//
Thread.Sleep(_bolPacketDelay);
Thread.Sleep(Configuration.BOLDelay);
//
// That's it. Go home, do it again.
@@ -73,8 +74,6 @@ namespace IFS
private const ushort _bolPacketType = 0x182; // 602B
private const byte _bolAddress = 0xff; // 377B (boot address)
private const int _bolPacketDelay = 5000; // 5 seconds
/// <summary>
/// The gold-standard BOL packet, containing the Alto ethernet bootstrap code.
/// Note that this does not contain padding for the ethernet header, the router adds those two words.

View File

@@ -11,8 +11,11 @@
# all numbers are in octal.
#
1# ifs
42# Muffin
43# Pumpkin
44# Frunobulax
45# Phydeaux
# Local Network
1#1# lcm
1#100# Thacker
1#42# Kay
1#43# Boggs
1#44# Kaehler
1#45# Lampson

View File

@@ -4,17 +4,36 @@
# All numbers are in decimal.
#
# Normal configuration
# Directory configuration
FTPRoot = c:\ifs\ftp
CopyDiskRoot = c:\ifs\copydisk
BootRoot = c:\ifs\boot
MailRoot = c:\ifs\mail
# InterfaceType defines the type of interface for local networking,
# (either RAW or UDP)
# and must be the same as other devices or software on the network that
# wishes to talk to this server.
InterfaceType = raw
# The name of the network interface to use for local networking.
# This is the name reported by "ipconfig"
InterfaceName = Ethernet
# Note that UDPPort is for the local network UDP transport,
# not for gateway ports!
# (gateway ports are specified in networks.txt)
UDPPort = 42424
# Defines the address for this IFS server.
# An entry for this IFS server's network must be
# present in networks.txt
ServerNetwork = 1
ServerHost = 1
# Delay between BreathOfLife packet broadcasts, in milliseconds.
BOLDelay = 5000
# Debug settings
LogTypes = None
LogComponents = None
LogTypes = All
LogComponents = Routing

20
PUP/Conf/networks.txt Normal file
View File

@@ -0,0 +1,20 @@
# networks.txt:
#
# This identifies known networks and provides the IPs for their IFS gateway servers.
#
# Each line in this file is of the format
# <inter-network number> <IFS host IP or hostname[:port]>
#
# for example:
# 5# 132.22.110.96
# would define network 5's gateway as 132.22.110.96 with the default port of 42425.
#
# 12# myhostname.net:6666
# defines network 12's gateway at myhostname.net, port 6666.
#
#
# There must be an entry present for our local network, or else routing will be disabled.
#
# 1# <your IP here>

View File

@@ -79,6 +79,7 @@ namespace IFS
// Set to default.
UDPPort = 42424;
}
}
/// <summary>
@@ -141,6 +142,11 @@ namespace IFS
/// </summary>
public static readonly LogType LogTypes;
/// <summary>
/// The delay (in msec) between Breath Of Life packets
/// </summary>
public static readonly int BOLDelay;
private static void ReadConfiguration()
{

View File

@@ -1178,7 +1178,9 @@ namespace IFS.FTP
if (user == null)
{
SendFTPNoResponse(NoCode.AccessDenied, "Invalid username or password.");
// Default to guest user.
user = UserToken.Guest;
//SendFTPNoResponse(NoCode.AccessDenied, "Invalid username or password.");
}
return user;

View File

@@ -15,13 +15,9 @@
along with IFS. If not, see <http://www.gnu.org/licenses/>.
*/
using IFS.Gateway;
using IFS.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace IFS.Gateway
{
@@ -40,11 +36,8 @@ namespace IFS.Gateway
{
public GatewayInformationProtocol()
{
//
// TODO (once routing is implemented):
// load host tables, etc.
// spin up thread that spits out a GatewayInformation PUP periodically.
//
_gatewayInfoThread = new Thread(GatewayInformationWorker);
_gatewayInfoThread.Start();
}
/// <summary>
@@ -59,8 +52,13 @@ namespace IFS.Gateway
SendGatewayInformationResponse(p);
break;
case PupType.GatewayInformationResponse:
// Currently a no-op.
Log.Write(LogComponent.MiscServices, String.Format("Gateway Information handler unimplemented."));
break;
default:
Log.Write(LogComponent.MiscServices, String.Format("Unhandled Gateway protocol {0}", p.Type));
Log.Write(LogComponent.MiscServices, String.Format("Unhandled Gateway protocol {0} ({1})", p.Type, (int)p.Type));
break;
}
}
@@ -87,23 +85,93 @@ namespace IFS.Gateway
// inaccessible.
//
// Right now, we know of only one network (our own) and we are directly connected to it.
//
GatewayInformation info = new GatewayInformation();
info.TargetNet = DirectoryServices.Instance.LocalNetwork;
info.GatewayNet = DirectoryServices.Instance.LocalNetwork;
info.GatewayHost = DirectoryServices.Instance.LocalNetwork;
info.HopCount = 0;
byte[] infoArray = GetGatewayInformationArray();
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
// Response must contain our network number; this is used to tell clients what network they're on if they don't already know.
PUPPort remotePort = new PUPPort(DirectoryServices.Instance.LocalNetwork, p.SourcePort.Host, p.SourcePort.Socket);
PUP response = new PUP(PupType.GatewayInformationResponse, p.ID, remotePort, localPort, Serializer.Serialize(info));
PUP response = new PUP(PupType.GatewayInformationResponse, p.ID, remotePort, localPort, infoArray);
Router.Instance.SendPup(response);
}
private static byte[] GetGatewayInformationArray()
{
//
// We build the gateway information response from the RoutingTable that the Router maintains.
// Since we do not at this time implement multi-hop routing, all networks known by the Router
// are assumed to be directly connected and to have a hop-count of 0.
//
byte[] knownNetworks = Router.Instance.RoutingTable.GetKnownNetworks();
byte[] infoArray = new byte[knownNetworks.Length * 4];
for (int i = 0; i < knownNetworks.Length; i++)
{
GatewayInformation info = new GatewayInformation();
info.TargetNet = knownNetworks[i];
info.GatewayNet = DirectoryServices.Instance.LocalNetwork;
info.GatewayHost = DirectoryServices.Instance.LocalHost;
info.HopCount = 0; // all networks are directly connected
byte[] entry = Serializer.Serialize(info);
entry.CopyTo(infoArray, i * 4);
}
return infoArray;
}
private void GatewayInformationWorker()
{
uint infoPupID = (uint)(new Random().Next());
while (true)
{
//
// From gatewayinformation.press:
// "Each gateway host must also periodically broadcast Gateway Information Pups, as described above,
// on all directly-connected networks. The frequency of this broadcast should be approximately one
// every 30 seconds, and immediately whenever the gateways own routing table changes (see below).
// These Pups should be sent from socket 2 to socket 2."
//
// At this time, we don't do anything with gateway information PUPs that we receive -- they could
// at some point be used as originally intended, to dynamically update routing tables, but it would
// require some serious security investments to make sure that the tables don't get poisoned.
// However, even though we don't use them, some Alto software does. For example, the PUP libraries
// used by Mazewar expect to get periodic updates or eventually it will assume the route is no longer
// viable and drop connections.
//
// Delay 30 seconds
Thread.Sleep(30000);
byte[] infoArray = GetGatewayInformationArray();
// From us, on socket 2
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, 2);
//
// The set of known networks is by default the set of directly-connected networks.
//
byte[] knownNetworks = Router.Instance.RoutingTable.GetKnownNetworks();
foreach (byte network in knownNetworks)
{
// Send a broadcast to the specified network
PUPPort remotePort = new PUPPort(network, 0, 2);
PUP infoPup = new PUP(PupType.GatewayInformationResponse, infoPupID++, remotePort, localPort, infoArray);
Router.Instance.SendPup(infoPup);
Log.Write(LogComponent.MiscServices, "Gateway Information packet sent to network {0}", network);
}
}
}
private Thread _gatewayInfoThread;
}
}

View File

@@ -18,11 +18,17 @@
using IFS.Logging;
using IFS.Transport;
using PcapDotNet.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
namespace IFS.Gateway
{
public delegate void RoutePupCallback(PUP pup);
public delegate void RoutePupCallback(PUP pup, bool route);
/// <summary>
/// Implements gateway services, routing PUPs intended for other networks to
@@ -30,13 +36,45 @@ namespace IFS.Gateway
/// 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.
/// Routed PUPs are transferred over UDP, with the intent to connect other IFS
/// gateway servers (and thus the networks they serve) to each other either
/// over the Internet or over a local network.
/// Since we're sending these routed PUPs via TCP/IP, it makes little sense
/// to support multi-hop PUP routing and since I tend to err on the side of simplicity
/// in this implementation that's what's been implemented here. Each IFS network
/// known to the Router is assumed to be directly connected.
/// </summary>
public class Router
{
private Router()
{
_localProtocolDispatcher = new PUPProtocolDispatcher();
_routingTable = new RoutingTable();
//
// Look up our own network in the table and get our port.
// If we don't have an entry in the table, disable routing.
//
RoutingTableEntry ourNetwork = _routingTable.GetAddressForNetworkNumber(DirectoryServices.Instance.LocalNetwork);
_gatewayUdpClientLock = new ReaderWriterLockSlim();
if (ourNetwork == null)
{
Log.Write(LogType.Warning,
LogComponent.Routing,
"networks.txt does not contain a definition for our network ({0}). Gateway routing disabled.",
DirectoryServices.Instance.LocalNetwork);
_gatewayUdpClient = null;
}
else
{
_gatewayUdpPort = ourNetwork.Port;
// Start the external network receiver.
BeginExternalReceive();
}
}
public static Router Instance
@@ -47,10 +85,25 @@ namespace IFS.Gateway
}
}
public RoutingTable RoutingTable
{
get
{
return _routingTable;
}
}
public void Shutdown()
{
_localProtocolDispatcher.Shutdown();
_pupPacketInterface.Shutdown();
if (_gatewayUdpClient != null)
{
_gatewayUdpClientLock.EnterWriteLock();
_gatewayUdpClient.Close();
_gatewayUdpClientLock.ExitWriteLock();
}
}
public void RegisterRAWInterface(LivePacketDevice iface)
@@ -59,7 +112,7 @@ namespace IFS.Gateway
_pupPacketInterface = enet;
_rawPacketInterface = enet;
_pupPacketInterface.RegisterRouterCallback(RouteIncomingPacket);
_pupPacketInterface.RegisterRouterCallback(RouteIncomingLocalPacket);
}
public void RegisterUDPInterface(NetworkInterface iface)
@@ -68,7 +121,7 @@ namespace IFS.Gateway
_pupPacketInterface = udp;
_rawPacketInterface = udp;
_pupPacketInterface.RegisterRouterCallback(RouteIncomingPacket);
_pupPacketInterface.RegisterRouterCallback(RouteIncomingLocalPacket);
}
/// <summary>
@@ -77,7 +130,7 @@ namespace IFS.Gateway
/// <param name="p"></param>
public void SendPup(PUP p)
{
RouteOutgoingPacket(p);
RouteOutgoingPacket(p);
}
/// <summary>
@@ -101,30 +154,251 @@ namespace IFS.Gateway
/// <param name="p"></param>
private void RouteOutgoingPacket(PUP p)
{
// For now, we send the packet out without performing any routing.
_pupPacketInterface.Send(p);
//
// Check the destination network. If it's 0 (meaning the sender doesn't know
// what network it's on, or wants it to go out to whatever network it's currently
// connected to) or it's destined for our network, we send it out directly through
// the local network interface.
//
if (p.DestinationPort.Network == 0 ||
p.DestinationPort.Network == DirectoryServices.Instance.LocalNetwork)
{
_pupPacketInterface.Send(p);
}
else
{
//
// Not for our network -- see if we know what network this is going to.
//
RoutePacketExternally(p);
}
}
/// <summary>
/// Routes a newly received packet to the proper destination host.
/// Routes a locally received PUP to the proper destination host.
/// </summary>
/// <param name="pup"></param>
public void RouteIncomingPacket(PUP pup)
public void RouteIncomingLocalPacket(PUP pup, bool route)
{
//
// 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.
// Check the network -- if it specifies 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 if (route)
{
//
// Not for our network -- see if we know where to route it.
//
RoutePacketExternally(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);
//
// Not local, and we were asked not to route this PUP, so drop it on the floor.
//
}
}
private void RoutePacketExternally(PUP p)
{
RoutingTableEntry destinationNetworkEntry = _routingTable.GetAddressForNetworkNumber(p.DestinationPort.Network);
if (destinationNetworkEntry != null)
{
//
// Send this out through the external network interface.
//
if (_gatewayUdpClient != null)
{
Log.Write(LogType.Verbose,
LogComponent.Routing,
"-> PUP routed to {0}:{1}, type {2} source {3} destination {4}.",
destinationNetworkEntry.HostAddress,
destinationNetworkEntry.Port,
p.Type,
p.SourcePort,
p.DestinationPort);
try
{
_gatewayUdpClientLock.EnterWriteLock();
_gatewayUdpClient.Send(p.RawData, p.RawData.Length, destinationNetworkEntry.HostAddress, destinationNetworkEntry.Port);
}
catch (Exception e)
{
Log.Write(LogType.Error,
LogComponent.Routing,
"Gateway UDP client send failed, error {0}. Continuing.",
e.Message);
}
finally
{
_gatewayUdpClientLock.ExitWriteLock();
}
}
}
else
{
//
// We don't know where to send this, drop it instead.
//
Log.Write(
LogType.Verbose,
LogComponent.Routing,
"Outgoing PUP is for unknown network {0}, dropping.", p.DestinationPort.Network);
}
}
private void RouteIncomingExternalPacket(PUP p)
{
//
// Ensure that this is for our network; otherwise this has been misrouted.
// (Since we don't do multi-hop routing, any packet coming in through a gateway
// interface must be destined for us.)
//
if (p.DestinationPort.Network == DirectoryServices.Instance.LocalNetwork)
{
//
// Send it out on the local network for anyone to see.
//
_pupPacketInterface.Send(p);
//
// And if it's intended for us (the IFS server) let our services have a crack at it, too.
//
if (p.DestinationPort.Host == DirectoryServices.Instance.LocalHostAddress.Host || // us specifically
p.DestinationPort.Host == 0) // broadcast
{
_localProtocolDispatcher.ReceivePUP(p);
}
}
else
{
//
// This was misrouted. Log it and drop.
//
Log.Write(LogType.Error,
LogComponent.Routing,
"PUP was misrouted; intended for network {0}, host {1}",
p.DestinationPort.Network,
p.DestinationPort.Host);
}
}
private void BeginExternalReceive()
{
CreateGatewayReceiver();
// Kick off receive thread.
_gatewayReceiveThread = new Thread(GatewayReceiveThread);
_gatewayReceiveThread.Start();
}
private void CreateGatewayReceiver()
{
_gatewayUdpClientLock.EnterWriteLock();
if (_gatewayUdpClient != null)
{
_gatewayUdpClient.Close();
}
_gatewayUdpClient = new UdpClient(_gatewayUdpPort, AddressFamily.InterNetwork);
_gatewayUdpClient.Client.Blocking = true;
_gatewayUdpClientLock.ExitWriteLock();
}
/// <summary>
/// Worker thread for UDP packet receipt.
/// </summary>
private void GatewayReceiveThread()
{
// Just call Receive forever, 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.Routing, "Gateway UDP Receiver thread started for port {0}.", _gatewayUdpPort);
IPEndPoint groupEndPoint = new IPEndPoint(IPAddress.Any, Configuration.UDPPort);
while (true)
{
byte[] data = null;
try
{
data = _gatewayUdpClient.Receive(ref groupEndPoint);
}
catch(Exception e)
{
//
// This can happen on occasion for reasons I don't quite understand.
// We will log the failure and attempt to continue.
//
Log.Write(LogType.Error,
LogComponent.Routing,
"Gateway UDP client receive failed, error {0}. Continuing.",
e.Message);
continue;
}
// 1) validate that the packet came in on the right port
// 2) validate packet
// 3) get a PUP out of it
// 4) send to RouteIncomingPacket.
// 5) do it again.
if (groupEndPoint.Port == _gatewayUdpPort)
{
if (data.Length < PUP.PUP_HEADER_SIZE + PUP.PUP_CHECKSUM_SIZE ||
data.Length > PUP.MAX_PUP_SIZE + PUP.PUP_HEADER_SIZE + PUP.PUP_CHECKSUM_SIZE)
{
Log.Write(LogType.Error, LogComponent.Routing, "External PUP has an invalid size ({0}). Dropping.", data.Length);
continue;
}
try
{
//
// See if we can get a PUP out of this.
//
PUP externalPUP = new PUP(new MemoryStream(data), data.Length);
//
// TODO: should technically bump the PUP's TransportControl field up;
// really need to rewrite the PUP class to make this possible without
// building up an entirely new PUP.
//
RouteIncomingExternalPacket(externalPUP);
Log.Write(LogType.Verbose,
LogComponent.Routing,
"<- External PUP received from {0}:{1}, type {2} source {3} destination {4}. Routing to local network.",
groupEndPoint.Address,
groupEndPoint.Port,
externalPUP.Type,
externalPUP.SourcePort,
externalPUP.DestinationPort);
}
catch (Exception e)
{
Log.Write(LogType.Error, LogComponent.Routing, "Error handling external PUP: {0}", e.Message);
}
}
else
{
Log.Write(LogType.Verbose,
LogComponent.Routing,
"Packet from {0} received on wrong port ({1}), expected {2}.",
groupEndPoint.Address,
groupEndPoint.Port,
_gatewayUdpPort);
}
}
}
@@ -138,8 +412,241 @@ namespace IFS.Gateway
/// </summary>
private IRawPacketInterface _rawPacketInterface;
/// <summary>
/// Our UdpClient for sending PUPs to external networks.
/// </summary>
private UdpClient _gatewayUdpClient;
/// <summary>
/// Used to ensure thread-safety for the UDP client (which is not thread-safe)
/// </summary>
private ReaderWriterLockSlim _gatewayUdpClientLock;
/// <summary>
/// Thread to watch for incoming external PUPs
/// </summary>
private Thread _gatewayReceiveThread;
/// <summary>
/// The UDP port we use for our gateway, as specified in networks.txt
/// </summary>
private int _gatewayUdpPort;
private RoutingTable _routingTable;
private static Router _router = new Router();
private PUPProtocolDispatcher _localProtocolDispatcher;
}
public class RoutingTableEntry
{
public RoutingTableEntry(string hostAddress, int port)
{
HostAddress = hostAddress;
Port = port;
}
public string HostAddress;
public int Port;
}
public class RoutingTable
{
public RoutingTable()
{
LoadRoutingTables();
}
/// <summary>
/// Returns the host address of the gateway server for the specified inter-
/// network number. Returns null if no address is defined.
/// </summary>
/// <param name="networkNumber"></param>
/// <returns></returns>
public RoutingTableEntry GetAddressForNetworkNumber(byte networkNumber)
{
if (_addressTable.ContainsKey(networkNumber))
{
return _addressTable[networkNumber];
}
else
{
return null;
}
}
/// <summary>
/// Returns the inter-network number for the network served by the given
/// host address. Returns 0 (undefined network) if no number is defined
/// for the given address.
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
public byte GetNetworkNumberForAddress(RoutingTableEntry address)
{
throw new NotImplementedException();
}
/// <summary>
/// Returns an array containing the numbers of networks that have been defined.
/// This is used by Gateway Services to return routing information.
/// </summary>
/// <returns></returns>
public byte[] GetKnownNetworks()
{
byte[] networks = new byte[_addressTable.Keys.Count];
_addressTable.Keys.CopyTo(networks, 0);
return networks;
}
private void LoadRoutingTables()
{
_addressTable = new Dictionary<byte, RoutingTableEntry>();
//
// Read in the routing tables from Conf\networks.txt.
//
using (StreamReader sr = new StreamReader(Path.Combine("Conf", "networks.txt")))
{
int lineNumber = 0;
while (!sr.EndOfStream)
{
lineNumber++;
//
// A line is either:
// '#' followed by comment to EOL
// <inter-network name> <hostname>
// Any whitespace is ignored
//
// Format for Inter-Network name expressions for a network is:
// network# (to specify hosts on another network)
//
string line = sr.ReadLine().Trim().ToLowerInvariant();
if (line.StartsWith("#") || String.IsNullOrWhiteSpace(line))
{
// Comment or empty, just ignore
continue;
}
// Tokenize on whitespace
string[] tokens = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
// We need exactly two tokens (inter-network name and one hostname)
if (tokens.Length != 2)
{
// Log warning and continue.
Log.Write(LogType.Warning, LogComponent.Routing,
"networks.txt line {0}: Invalid syntax.", lineNumber);
continue;
}
// First token should be an inter-network name, which should end with '#'.
if (!tokens[0].EndsWith("#"))
{
// Log warning and continue.
Log.Write(LogType.Warning, LogComponent.Routing,
"networks.txt line {0}: Improperly formed inter-network name '{1}'.", lineNumber, tokens[0]);
continue;
}
// tokenize on '#'
string[] networkTokens = tokens[0].Split(new char[] { '#' }, StringSplitOptions.RemoveEmptyEntries);
byte networkNumber = 0;
// 1 token means a network name, anything else is invalid here
if (networkTokens.Length == 1)
{
try
{
networkNumber = Convert.ToByte(networkTokens[0], 8);
}
catch
{
// Log warning and continue.
Log.Write(LogType.Warning, LogComponent.Routing,
"hosts.txt line {0}: Invalid network number in inter-network address '{1}'.", lineNumber, tokens[0]);
continue;
}
}
else
{
// Log warning and continue.
Log.Write(LogType.Warning, LogComponent.Routing,
"networks.txt line {0}: Invalid network number in inter-network address '{1}'.", lineNumber, tokens[0]);
continue;
}
if (_addressTable.ContainsKey(networkNumber))
{
// Log warning and continue.
Log.Write(LogType.Warning, LogComponent.Routing,
"networks.txt line {0}: Duplicate network entry '{1}'.", lineNumber, networkNumber);
continue;
}
//
// The 2nd token contains the hostname for the network gateway.
// This could be a domain name or an IP address (V4 only for the moment)
// with or without port.
//
string hostName = tokens[1];
string[] hostNameParts = hostName.Split(':');
string address = String.Empty;
int port = 0;
if (hostNameParts.Length == 2)
{
try
{
// Hostname + port
address = hostNameParts[0];
port = int.Parse(hostNameParts[1]);
if (port <= 0 || port > 65535)
{
throw new InvalidOperationException("Port number is out of range.");
}
}
catch (Exception)
{
Log.Write(LogType.Warning, LogComponent.Routing,
"networks.txt line {0}: Invalid hostname specification '{1}'.", lineNumber, hostName);
continue;
}
}
else if (hostNameParts.Length == 1)
{
address = hostNameParts[0];
port = DefaultUDPPort;
}
else
{
Log.Write(LogType.Warning, LogComponent.Routing,
"networks.txt line {0}: Invalid hostname specification '{1}'.", lineNumber, hostName);
continue;
}
// Add entry to the table.
_addressTable.Add(networkNumber, new RoutingTableEntry(address, port));
}
}
}
/// <summary>
/// The default UDP port for external networks.
/// </summary>
private static readonly int DefaultUDPPort = 42425;
private Dictionary<byte, RoutingTableEntry> _addressTable;
}
}

View File

@@ -136,6 +136,9 @@
<Content Include="Conf\bootdirectory.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Conf\networks.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="readme.txt" />
<None Include="Conf\ifs.cfg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@@ -42,8 +42,9 @@ namespace IFS.Logging
BootServer = 0x400,
UDP = 0x800,
Mail = 0x1000,
Routing = 0x2000,
Configuration = 0x1000,
Configuration = 0x4000,
All = 0x7fffffff
}
@@ -99,7 +100,7 @@ namespace IFS.Logging
{
//
// My log has something to tell you...
Console.WriteLine(component.ToString() + ": " + message, args);
Console.WriteLine("[" + DateTime.Now + "] " + component.ToString() + ": " + message, args);
if (_logStream != null)
{

View File

@@ -164,7 +164,10 @@ namespace IFS
public override string ToString()
{
return String.Format("Net {0} Host {1} Socket {2}", Network, Host, Socket);
return String.Format("Net {0} Host {1} Socket {2}",
Helpers.ToOctal(Network),
Helpers.ToOctal(Host),
Helpers.ToOctal((int)Socket));
}
public byte Network;
@@ -188,7 +191,7 @@ namespace IFS
/// TODO: Update to use Serialization code rather than packing bytes by hand.
/// </param>
///
public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source, byte[] contents, bool contentsContainsGarbageByte)
public PUP(PupType type, byte transportControl, UInt32 id, PUPPort destination, PUPPort source, byte[] contents, bool contentsContainsGarbageByte)
{
_rawData = null;
@@ -208,7 +211,7 @@ namespace IFS
throw new InvalidOperationException("Odd content length with garbage byte specified.");
}
TransportControl = 0;
TransportControl = transportControl;
Type = type;
ID = id;
DestinationPort = destination;
@@ -246,6 +249,21 @@ namespace IFS
Helpers.WriteUShort(ref _rawData, Checksum, _rawData.Length - 2);
}
/// <summary>
/// Constructor that assumes a transport control of 0
/// </summary>
/// <param name="type"></param>
/// <param name="id"></param>
/// <param name="destination"></param>
/// <param name="source"></param>
/// <param name="contents"></param>
/// <param name="contentsContainsGarbageByte"></param>
public PUP(PupType type, UInt32 id, PUPPort destination, PUPPort source, byte[] contents, bool contentsContainsGarbageByte) :
this(type, 0, id, destination, source, contents, contentsContainsGarbageByte)
{
}
/// <summary>
/// Same as above, no garbage byte.
/// </summary>
@@ -359,16 +377,16 @@ namespace IFS
return (ushort)sum;
}
}
public readonly ushort Length;
public readonly byte TransportControl;
public readonly PupType Type;
public readonly UInt32 ID;
public readonly PUPPort DestinationPort;
public readonly PUPPort SourcePort;
public readonly PUPPort SourcePort;
public readonly byte[] Contents;
public readonly ushort Checksum;
public readonly ushort Checksum;
private byte[] _rawData;
@@ -475,5 +493,10 @@ namespace IFS
return sb.ToString();
}
public static string ToOctal(int i)
{
return Convert.ToString(i, 8);
}
}
}

View File

@@ -84,7 +84,7 @@ namespace IFS.Transport
// addressing
encapsulatedFrame[2] = p.DestinationPort.Host;
encapsulatedFrame[3] = p.SourcePort.Host;
encapsulatedFrame[3] = p.SourcePort.Host;
// frame type
encapsulatedFrame[4] = (byte)(_pupFrameType >> 8);
@@ -180,12 +180,17 @@ namespace IFS.Transport
// Read the type and switch on it
int etherType3mbit = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte()));
if (etherType3mbit == _pupFrameType)
//
// Ensure this is a packet we're interested in.
//
if (etherType3mbit == _pupFrameType && // it's a PUP
(destination == DirectoryServices.Instance.LocalHost || // for us, or...
destination == 0)) // broadcast
{
try
{
PUP pup = new PUP(packetStream, length);
_routerCallback(pup);
PUP pup = new PUP(packetStream, length);
_routerCallback(pup, destination != 0);
}
catch(Exception e)
{

View File

@@ -69,15 +69,15 @@ namespace IFS.Transport
// Try to set up UDP client.
try
{
_udpClient = new UdpClient(Configuration.UDPPort, AddressFamily.InterNetwork);
_udpClient.Client.Blocking = true;
_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)
@@ -214,12 +214,17 @@ namespace IFS.Transport
// Read the type and switch on it
int etherType3mbit = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte()));
if (etherType3mbit == _pupFrameType)
//
// Ensure this is a packet we're interested in.
//
if (etherType3mbit == _pupFrameType && // it's a PUP
(destination == DirectoryServices.Instance.LocalHost || // for us, or...
destination == 0)) // broadcast
{
try
{
PUP pup = new PUP(packetStream, length);
_routerCallback(pup);
_routerCallback(pup, destination != 0);
}
catch(Exception e)
{

View File

@@ -1,4 +1,4 @@
Readme.txt for IFS v1.1:
Readme.txt for IFS v1.2:
1. Introduction and Overview
============================
@@ -16,21 +16,23 @@ emulator over either Raw Ethernet packets or UDP broadcasts.
It provides the following IFS services:
- BreathOfLife: Provides the "Breath Of Life" packet needed to bootstrap
an Alto over the network.
- EFTP/Boot: Provides boot files over the network.
- FTP: File Transfer Protocol.
- CopyDisk: Allows imaging and restoring of Alto disk packs over the
network.
- BreathOfLife: Provides the "Breath Of Life" packet needed to bootstrap
an Alto over the network.
- EFTP/Boot: Provides boot files over the network.
- FTP: File Transfer Protocol.
- CopyDisk: Allows imaging and restoring of Alto disk packs over the
network.
- Misc. Services: Provides network name lookup, time and other miscellaneous
services.
- Mail: Delivers mail to other users.
- Gateway: Routes PUPs to other sites on the Internet
- Mail: Delivers mail to other users. (Currently only on the same
network, mail is not routed.)
The following services are not yet provided, but are planned:
- EFTP/Printing: Provides print services to networked Altos
- Gateway: Routes PUPs to other Alto networks across the Internet
- EFTP/Printing: Provides print services to networked Altos
- Mail routing: Sending mail to other sites over the Internet.
If you have questions, or run into issues or have feature requests, please
feel free to e-mail me at joshd@livingcomputers.org.
@@ -62,10 +64,10 @@ in mind. All files (even those in user directories) are globally readable.
IFS uses a set of files in the "Conf" subdirectory to configure the server.
These include:
- accounts.txt: Defines the set of user accounts
- bootdirectory.txt: Maps boot numbers to boot files for network boot
- hosts.txt: Maps Inter-network numbers to names
- ifs.cfg: General configuration for the IFS server
- accounts.txt: Defines the set of user accounts
- bootdirectory.txt: Maps boot numbers to boot files for network boot
- hosts.txt: Maps Inter-network numbers to names
- ifs.cfg: General configuration for the IFS server
2.1 ifs.cfg:
------------
@@ -74,33 +76,33 @@ ifs.cfg contains general configuration details for the server. It specifies
configuration for the network transport, directory paths and debugging options.
Directory configuration:
- FTPRoot: Specifies the path for the root of the FTP directory tree.
- CopyDiskRoot: Specifies the path for the directory to store CopyDisk
images.
- BootRoot: Specifies the path for boot images.
- MailRoot: Specifies the path for the root of the Mail directory tree.
(User mail folders are placed in this directory.)
- FTPRoot: Specifies the path for the root of the FTP directory tree.
- CopyDiskRoot: Specifies the path for the directory to store CopyDisk
images.
- BootRoot: Specifies the path for boot images.
- MailRoot: Specifies the path for the root of the Mail directory tree.
(User mail folders are placed in this directory.)
Interface configuration:
- InterfaceType: "RAW" or "UDP". Specifies the transport to use for
communication.
- InterfaceName: The name of the host network adapter to use for
communication.
- InterfaceType: "RAW" or "UDP". Specifies the transport to use for
communication.
- InterfaceName: The name of the host network adapter to use for
communication.
- UDPPort: The port number (decimal) to use for the UDP transport.
- UDPPort: The port number (decimal) to use for the UDP transport.
Network configuration:
- ServerNetwork: The IFS server's network number.
- ServerHost: The IFS server's host number.
- ServerNetwork: The IFS server's network number.
- ServerHost: The IFS server's host number.
Debugging configuration:
- LogTypes: The level of verbosity for logging. One of:
None, Normal, Warning, Error, Verbose, or All
- LogTypes: The level of verbosity for logging. One of:
None, Normal, Warning, Error, Verbose, or All
- LogComponent: The components to log details about. One of:
None, Ethernet, RTP, BSP, MiscServices, CopyDisk,
DirectoryServices, PUP, FTP, BreathOfLife, EFTP,
BootServer, UDP, Mail, Configuration, or All
- LogComponent: The components to log details about. One of:
None, Ethernet, RTP, BSP, MiscServices, CopyDisk,
DirectoryServices, PUP, FTP, BreathOfLife, EFTP,
BootServer, UDP, Mail, Configuration, or All
2.2 hosts.txt:
--------------
@@ -124,18 +126,44 @@ A Hostname is an alphanumeric sequence that must begin with a letter.
A hosts.txt entry for our Alto on network 5 with host number 72 providing said
system with name "alan" would thus look like:
5#72# alan
5#72# alan
Or optionally, if our IFS server is on network 5:
72# alan
72# alan
It is a good idea to provide an entry for the IFS server itself so that the
server can easily be reached by name. By default (unless ifs.cfg has been
changed), the IFS server's inter-network name is 1#1# (network 1, host 1).
2.3 bootdirectory.txt
2.3 networks.txt
----------------
networks.txt identifies known networks and provides the address and port for
their IFS gateway servers. See Section 5 for more details on gateways.
This file is processed when IFS starts.
Each line in this file is of the format
<inter-network number> <IFS host IP or hostname>[:port]
For example:
5# 192.168.1.137
would define network 5's gateway as 192.168.1.137 with the default port.
12# myhostname.net:6666
defines network 12's gateway at myhostname.net, port 6666.
If no port number is specified for a given network entry, the entry will
default to 42425.
networks.txt must contain an entry for the local IFS server itself if you
want to enable routing through the gateway. In order for the IFS gateway
to be able to talk to the outside world, the port specified for the local
IFS server must be opened. (You may need to enable port forwarding if you
are going to be routing PUPs over the Internet, for example.)
2.4 bootdirectory.txt
---------------------
bootdirectory.txt maps boot numbers to the bootfile they correspond to. The
@@ -156,7 +184,7 @@ Note that the IFS server does not include the actual boot files -- see Section
6.0 for details on where to find these files to populate your BootRoot
directory.
2.4 accounts.txt
2.5 accounts.txt
----------------
accounts.txt defines user accounts for the IFS system.
@@ -178,18 +206,18 @@ console to add, remove, or change user accounts.
Each user definition is a line in the format:
<username>:<password hash>:<privileges>:<full user name>:<home directory>
- username: an alphanumeric sequence starting with a letter. This
define's the user's login name.
- password hash: an encoded version of the user's password. This can be
edited, but is generally not advisable. See Section
3.0 for details on setting and changing user passwords.
- privileges: Either Admin (administrative privileges) or User (normal
user privileges). See section 4.0 for details.
- full user name: Self explanatory; the full name (i.e. Alan Kay) of the
user.
- home directory: The user's directory (which is placed under the FTPRoot
directory). See Section 4.0 for details on user
directories.
- username: an alphanumeric sequence starting with a letter. This
define's the user's login name.
- password hash: an encoded version of the user's password. This can be
edited, but is generally not advisable. See Section
3.0 for details on setting and changing user passwords.
- privileges: Either Admin (administrative privileges) or User (normal
user privileges). See section 4.0 for details.
- full user name: Self explanatory; the full name (i.e. Alan Kay) of the
user.
- home directory: The user's directory (which is placed under the FTPRoot
directory). See Section 4.0 for details on user
directories.
Changes made to this file while IFS is running will not take effect until IFS
is restarted. (This is another reason to use the Console to make changes --
@@ -210,24 +238,23 @@ synopses and descriptions.
Here is a rundown of the basic command set:
show users - Displays the current user database (See Section 4.0)
show users - Displays the current user database (See Section 4.0)
show user <username> - Displays information for the specified user
(See Section 4.0)
show user <username> - Displays information for the specified user
(See Section 4.0)
set password <username> - Sets the password for the specified user
(See Section 4.0)
set password <username> - Sets the password for the specified user
(See Section 4.0)
add user <username> <password> [User|Admin] <full name> <home directory>
- Adds a new user account (See section 4.0 for details)
add user <username> <password> [User|Admin] <full name> <home directory>
- Adds a new user account (See section 4.0 for details)
remove user <username> - Removes an existing user account (See Section 4.0)
remove user <username> - Removes an existing user account (See Section 4.0)
show active servers - Displays active server statistics.
show active servers - Displays active server statistics.
quit - Terminates the IFS process
quit - Terminates the IFS process
show commands - Shows console commands and their descriptions.
show commands - Shows console commands and their descriptions.
4.0 User Accounts, Authentication, and Security
@@ -305,6 +332,50 @@ usage.
You cannot run both the IFS server and a ContrAlto emulator on the same machine
if they are configured to use UDP as the transport.
5.1 Gateways and Routing
------------------------
The original IFS at PARC provided Gateway services for routing PUPs across
multiple networks via various transports (ethernet, serial, modems and even
experimental wireless networks). It supported multi-hop routing that was
in many ways similar to the modern Internet.
The LCM+L IFS server provides single-hop routing via UDP. This allows
the connection of multiple Alto networks together, either on a local network
or over the global Internet.
The networks involved are defined in the networks.txt configuration file
(see Section 2.3) and specify what IP address corresponds to the network in
question. When the local IFS server receives a packet destined for another
network, it uses networks.txt to figure out what IP to send it to. Similarly,
the local IFS server listens for incoming packets from other IFS servers and
routes them onto the local network.
Unlike the original IFS, routing is statically defined by networks.txt at
startup and cannot be changed at runtime. If in the future the advantages
of supporting a dynamic routing scheme outweigh the disadvantages (complication,
security, etc.) this may be added.
Additionally, routing is single-hop only. The assumption is made that any
site on a TCP/IP network can reach any other via the Internet or local
networking. In effect, the real routing is done by TCP/IP, not IFS. Multi-
hop routing would be an interesting exercise but seems superfluous and as usual
the decision was to err on the side of simplicity.
Important things to keep in mind when configuring routing:
- Ensure all sites have unique network numbers: make sure each IFS
server has a unique network number in ifs.cfg
- Ensure all IFS servers have entries in networks.txt (including the
local IFS server!)
- Ensure the port you have specified for the local network in
networks.txt is open, and is accessible by other IFS servers.
- It is useful (but not necessary) to have entries for IFS servers
and Alto hosts in hosts.txt
If you need to debug routing, you can set "LogComponents" to "Routing" and
LogTypes to "All" in ifs.cfg. This will cause incoming and outgoing PUPs to
be logged to the console as they are processed.
6.0 Where to Find Alto Files
============================