623 lines
20 KiB
C#
623 lines
20 KiB
C#
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Microsoft Research Singularity
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
/**
|
||
|
* Microsoft Research, Cambridge
|
||
|
* author: Yaron Weinsberg, Richard Black
|
||
|
*/
|
||
|
|
||
|
// #define DEBUG_ARP
|
||
|
|
||
|
using NetStack.Common;
|
||
|
using System;
|
||
|
using System.Diagnostics;
|
||
|
using System.Collections;
|
||
|
using System.Text;
|
||
|
|
||
|
#if !SINGULARITY
|
||
|
using System.Net;
|
||
|
#endif
|
||
|
|
||
|
using System.Net.IP;
|
||
|
using Drivers.Net;
|
||
|
using NetStack.Protocols;
|
||
|
|
||
|
using Microsoft.Singularity;
|
||
|
|
||
|
namespace NetStack.Runtime
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Callback for Arp Requests.
|
||
|
///
|
||
|
/// This delegate is invoked when the ArpModule gets a
|
||
|
/// response to a request.
|
||
|
///
|
||
|
/// <param name="answer">Ethernet address corresponding to
|
||
|
/// requested IP address if a response is received,
|
||
|
/// EthernetAddress.Zero if request timed out.</param>
|
||
|
/// </summary>
|
||
|
public delegate void ArpRequestCallback(IPv4 ipAddress,
|
||
|
EthernetAddress macAddress,
|
||
|
object cookie);
|
||
|
|
||
|
///<summary>
|
||
|
///This module implements the ARP protocol.
|
||
|
///</summary>
|
||
|
public class ArpModule : IProtocol
|
||
|
{
|
||
|
private class PendingRequest
|
||
|
{
|
||
|
public IPv4 address;
|
||
|
public ArpRequestCallback callback;
|
||
|
public object cookie;
|
||
|
public DateTime expiry;
|
||
|
|
||
|
public PendingRequest(IPv4 address,
|
||
|
ArpRequestCallback callback,
|
||
|
object cookie,
|
||
|
DateTime expiry)
|
||
|
{
|
||
|
this.address = address;
|
||
|
this.callback = callback;
|
||
|
this.cookie = cookie;
|
||
|
this.expiry = expiry;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Class used to hold expiration time of Request timeouts.
|
||
|
/// </summary>
|
||
|
private class TimeoutArgument : Dispatcher.CallbackArgs
|
||
|
{
|
||
|
public DateTime now;
|
||
|
|
||
|
public TimeoutArgument(DateTime when)
|
||
|
{
|
||
|
now = when;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ARP variables
|
||
|
private ArpTable arpTable = null;
|
||
|
private IPModule ipModule = null;
|
||
|
private ArrayList pendingRequests; // <expiry, PendingRequest>
|
||
|
|
||
|
// Pending request polling period
|
||
|
private static readonly TimeSpan PollPeriod = TimeSpan.FromSeconds(1);
|
||
|
|
||
|
// INetModule interfaces
|
||
|
// ------------------------
|
||
|
string INetModule.ModuleName { get { return "ARP"; } }
|
||
|
ushort INetModule.ModuleVersion { get { return 0x01; } } // 0.1
|
||
|
|
||
|
[Conditional("DEBUG_ARP")]
|
||
|
internal static void DebugPrint(string format,
|
||
|
params object [] arguments)
|
||
|
{
|
||
|
DebugStub.Print("ARPModule: {0}",
|
||
|
__arglist(
|
||
|
string.Format(format, arguments))
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// called by the runtime when
|
||
|
// the protocol should be started
|
||
|
public bool StartModule()
|
||
|
{
|
||
|
DebugPrint("Starting ARP.\n");
|
||
|
|
||
|
// we have the request...
|
||
|
// Notice: can be ARP_REQUEST or ARP_RESPONSE
|
||
|
ipModule = Core.Instance().GetProtocolByName("IP") as IPModule;
|
||
|
if (ipModule == null)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// ask for an aging[msec] timer
|
||
|
DateTime then = DateTime.UtcNow + ArpTable.AgePeriod;
|
||
|
Core.Instance().TheDispatcher.AddCallback(
|
||
|
new Dispatcher.Callback(OnAgingTimer), null, (ulong)then.Ticks
|
||
|
);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// make the arp table entries older...
|
||
|
// currently arg is null
|
||
|
public NetStatus OnAgingTimer(Dispatcher.CallbackArgs arg)
|
||
|
{
|
||
|
arpTable.AgeTable();
|
||
|
|
||
|
DateTime then = DateTime.UtcNow + ArpTable.AgePeriod;
|
||
|
Core.Instance().TheDispatcher.AddCallback(
|
||
|
new Dispatcher.Callback(OnAgingTimer), null, (ulong)then.Ticks
|
||
|
);
|
||
|
|
||
|
return NetStatus.Code.RT_OK;
|
||
|
}
|
||
|
|
||
|
public bool StopModule()
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public bool DestroyModule()
|
||
|
{
|
||
|
arpTable = null;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// IProtocol interfaces
|
||
|
// ------------------------
|
||
|
public bool Initialize(ProtocolParams parameters)
|
||
|
{
|
||
|
Debug.Assert(parameters == null || parameters["name"]=="ARP");
|
||
|
|
||
|
int size = ProtocolParams.LookupInt32(parameters, "cacheSize",
|
||
|
128);
|
||
|
int age = ProtocolParams.LookupInt32(parameters, "max-age",
|
||
|
ArpTable.MaxAge);
|
||
|
arpTable = new ArpTable(size, age, this);
|
||
|
pendingRequests = new ArrayList();
|
||
|
|
||
|
Core core = Core.Instance();
|
||
|
core.RegisterProtocol(this);
|
||
|
if (! core.packetTypes.RegisterTypeHandler(PacketTypes.ARP, this))
|
||
|
{
|
||
|
core.DeregisterProtocol(this);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public ushort GetProtocolID()
|
||
|
{
|
||
|
return (EthernetFormat.PROTOCOL_ARP);
|
||
|
}
|
||
|
|
||
|
public Session CreateSession()
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ARP logic: see RFC 826 http://www.faqs.org/rfcs/rfc826.html
|
||
|
*/
|
||
|
public NetStatus OnProtocolReceive(NetPacket! pkt)
|
||
|
{
|
||
|
NetStatus res = NetStatus.Code.PROTOCOL_OK;
|
||
|
Debug.Assert(pkt!=null);
|
||
|
|
||
|
ArpFormat.Type opcode;
|
||
|
EthernetAddress senderMAC, targetMAC;
|
||
|
IPv4 senderIP, targetIP;
|
||
|
|
||
|
bool ok = ArpFormat.Read(pkt, out opcode,
|
||
|
out senderMAC, out senderIP,
|
||
|
out targetMAC, out targetIP);
|
||
|
DebugPrint("ARP RESPONSE\n");
|
||
|
|
||
|
// check for problems
|
||
|
if (ok == false)
|
||
|
{
|
||
|
DebugPrint("ARP READ ERROR\n");
|
||
|
return NetStatus.Code.PROTOCOL_DROP_ERROR;
|
||
|
}
|
||
|
|
||
|
bool merged = false;
|
||
|
bool updated = false;
|
||
|
ArpEntry target = arpTable.Lookup(senderIP);
|
||
|
if (target != null && target.Dynamic == true)
|
||
|
{
|
||
|
DebugPrint("ARP UPDATE\n");
|
||
|
// we have it already - just update the details...
|
||
|
target.MacAddress = senderMAC;
|
||
|
target.EntryAge = arpTable.Age;
|
||
|
merged = true;
|
||
|
updated = true;
|
||
|
}
|
||
|
|
||
|
bool forSelf = ipModule.HostConfiguration.IsLocalAddress(targetIP);
|
||
|
if (forSelf == false)
|
||
|
{
|
||
|
return NetStatus.Code.PROTOCOL_OK;
|
||
|
}
|
||
|
|
||
|
if (merged == false)
|
||
|
{
|
||
|
DebugPrint("ARP ADDITION\n");
|
||
|
arpTable.AddEntry(new ArpEntry(senderIP, senderMAC, true));
|
||
|
merged = true;
|
||
|
UpdatePendingRequests(senderIP, senderMAC);
|
||
|
}
|
||
|
|
||
|
// now figure out the opcode
|
||
|
if (opcode == ArpFormat.Type.ARP_REQUEST)
|
||
|
{
|
||
|
DebugPrint("Handling request ({0},{1}) ---> ({2},{3})\n",
|
||
|
senderIP, senderMAC, targetIP, targetMAC
|
||
|
);
|
||
|
|
||
|
// send reply...
|
||
|
// we reuse the received packet for sending a fast response
|
||
|
// so we should NOT return it to the Demux's free list!!!
|
||
|
// (we replace it with a new one instead)
|
||
|
// we use the adapter from which the request arrived
|
||
|
// (available in the context)
|
||
|
Multiplexer mux = Core.Instance().GetMuxForAdapter((IAdapter!)pkt.AdapterContext);
|
||
|
if (mux == null) {
|
||
|
// some internal bug, for now just panic
|
||
|
Core.Panic("At ArpModule.PacketReceive: " +
|
||
|
"context isn't Multiplexer!\n");
|
||
|
assert(false);
|
||
|
}
|
||
|
|
||
|
int dataLength = EthernetFormat.Size + ArpFormat.Size;
|
||
|
if (dataLength < pkt.Length)
|
||
|
dataLength = pkt.Length;
|
||
|
|
||
|
byte[]! data = new byte[dataLength];
|
||
|
Array.Copy(pkt.GetRawData(), 0, data, 0, pkt.Length);
|
||
|
|
||
|
ArpFormat.CreateArpReply(ref data, targetIP,
|
||
|
mux.Adapter.HardwareAddress);
|
||
|
mux.SendDirect(new NetPacket(data));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// otherwise we are done
|
||
|
DebugPrint(
|
||
|
"Handling reply ({2},{3}) <--- ({0},{1})\n",
|
||
|
senderIP, senderMAC, targetIP, targetMAC
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (merged && !updated)
|
||
|
{
|
||
|
DebugPrint(arpTable.ToString());
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
// sends an arp request only.
|
||
|
// the given packet is already prepared.
|
||
|
public NetStatus OnProtocolSend(NetPacket! pkt)
|
||
|
{
|
||
|
DebugPrint("ARPModule.OnProtocolSend\n");
|
||
|
((Multiplexer!)pkt.Mux).SendDirect(pkt);
|
||
|
return NetStatus.Code.PROTOCOL_OK;
|
||
|
}
|
||
|
|
||
|
public void ArpRequest(IPv4 sourceIP,
|
||
|
IPv4 targetIP,
|
||
|
Multiplexer! targetMux,
|
||
|
ArpRequestCallback callback,
|
||
|
object cookie,
|
||
|
TimeSpan timeout)
|
||
|
{
|
||
|
/*
|
||
|
XXXX Check pending request does not already exist!
|
||
|
*/
|
||
|
EthernetAddress localMac = targetMux.Adapter.HardwareAddress;
|
||
|
|
||
|
AddPendingRequest(targetIP, callback, cookie, timeout);
|
||
|
|
||
|
// initiate an arp request...
|
||
|
DebugPrint("Initiating request " +
|
||
|
"({0},{1}) --> ({2},{3})\n",
|
||
|
sourceIP, localMac, targetIP, EthernetAddress.Zero);
|
||
|
|
||
|
byte[] data = new byte[ArpFormat.Size + EthernetFormat.Size];
|
||
|
int pos = EthernetFormat.Write(data, 0, localMac,
|
||
|
EthernetAddress.Broadcast,
|
||
|
EthernetFormat.PROTOCOL_ARP);
|
||
|
|
||
|
ArpFormat.Write(data, pos, localMac, sourceIP,
|
||
|
ArpFormat.Type.ARP_REQUEST, EthernetAddress.Zero,
|
||
|
targetIP);
|
||
|
|
||
|
NetPacket pkt = new NetPacket(data);
|
||
|
pkt.Mux = targetMux;
|
||
|
OnProtocolSend(pkt);
|
||
|
}
|
||
|
|
||
|
public bool Lookup(IPv4 targetIP, out EthernetAddress macAddress)
|
||
|
{
|
||
|
return arpTable.Lookup(targetIP, out macAddress);
|
||
|
}
|
||
|
|
||
|
public NetStatus SetProtocolSpecific(ushort opcode,byte[]! data)
|
||
|
{
|
||
|
return NetStatus.Code.PROTOCOL_OK;
|
||
|
}
|
||
|
|
||
|
public NetStatus GetProtocolSpecific(ushort opcode,out byte[] data)
|
||
|
{
|
||
|
data = null;
|
||
|
return NetStatus.Code.PROTOCOL_OK;
|
||
|
}
|
||
|
|
||
|
private void AddPendingRequest(IPv4 address,
|
||
|
ArpRequestCallback callback,
|
||
|
object cookie,
|
||
|
TimeSpan timeout)
|
||
|
{
|
||
|
DateTime expiry = DateTime.UtcNow + timeout;
|
||
|
pendingRequests.Add(
|
||
|
new PendingRequest(address, callback, cookie, expiry)
|
||
|
);
|
||
|
|
||
|
if (pendingRequests.Count == 1)
|
||
|
{
|
||
|
DateTime nextPoll = DateTime.UtcNow + PollPeriod;
|
||
|
Core.Instance().TheDispatcher.AddCallback(
|
||
|
new Dispatcher.Callback(OnRequestTimeout),
|
||
|
new TimeoutArgument(nextPoll),
|
||
|
(ulong)nextPoll.Ticks
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void UpdatePendingRequests(IPv4 ipAddress,
|
||
|
EthernetAddress macAddress)
|
||
|
{
|
||
|
int i = 0;
|
||
|
while (i != pendingRequests.Count)
|
||
|
{
|
||
|
PendingRequest request = (PendingRequest!) pendingRequests[i];
|
||
|
if (request.address == ipAddress)
|
||
|
{
|
||
|
request.callback(ipAddress, macAddress, request.cookie);
|
||
|
pendingRequests.RemoveAt(i);
|
||
|
continue;
|
||
|
}
|
||
|
i++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private NetStatus OnRequestTimeout(Dispatcher.CallbackArgs args)
|
||
|
{
|
||
|
DateTime now = ((TimeoutArgument!) args).now;
|
||
|
|
||
|
int i = 0;
|
||
|
while (i != pendingRequests.Count)
|
||
|
{
|
||
|
PendingRequest request = (PendingRequest!) pendingRequests[i];
|
||
|
if (request.expiry < now)
|
||
|
{
|
||
|
DebugPrint("Expiring request");
|
||
|
|
||
|
request.callback(request.address,
|
||
|
EthernetAddress.Zero,
|
||
|
request.cookie);
|
||
|
pendingRequests.RemoveAt(i);
|
||
|
continue;
|
||
|
}
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
if (pendingRequests.Count > 0)
|
||
|
{
|
||
|
DateTime nextPoll = now + PollPeriod;
|
||
|
Core.Instance().TheDispatcher.AddCallback(
|
||
|
new Dispatcher.Callback(OnRequestTimeout),
|
||
|
new TimeoutArgument(nextPoll),
|
||
|
(ulong)nextPoll.Ticks
|
||
|
);
|
||
|
}
|
||
|
return NetStatus.Code.RT_OK;
|
||
|
}
|
||
|
|
||
|
// ctor
|
||
|
public ArpModule()
|
||
|
{
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// an arp entry (we only deal with IPv4)
|
||
|
public class ArpEntry
|
||
|
{
|
||
|
private IPv4 ipAddress;
|
||
|
private EthernetAddress mac;
|
||
|
private int entryAge;
|
||
|
private bool dynamic;
|
||
|
|
||
|
public int EntryAge
|
||
|
{
|
||
|
get { return entryAge; }
|
||
|
set { entryAge = value; }
|
||
|
}
|
||
|
|
||
|
public EthernetAddress MacAddress
|
||
|
{
|
||
|
get { return mac; }
|
||
|
set { mac = value; }
|
||
|
}
|
||
|
|
||
|
public IPv4 IPAddress
|
||
|
{
|
||
|
get { return ipAddress; }
|
||
|
}
|
||
|
|
||
|
public bool Dynamic
|
||
|
{
|
||
|
get { return dynamic; }
|
||
|
}
|
||
|
|
||
|
// create a new entry
|
||
|
public ArpEntry(IPv4 ipAddress, EthernetAddress mac, bool dynamic)
|
||
|
{
|
||
|
this.ipAddress = ipAddress;
|
||
|
this.mac = mac;
|
||
|
this.dynamic = dynamic;
|
||
|
this.entryAge = ArpTable.MaxAge;
|
||
|
}
|
||
|
|
||
|
public override string! ToString()
|
||
|
{
|
||
|
return String.Format("{0} {1} {2} {3}",
|
||
|
ipAddress, mac, entryAge,
|
||
|
dynamic ? "dynamic" : "static");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// define the arp table
|
||
|
internal class ArpTable
|
||
|
{
|
||
|
Hashtable! arpEntries; // <Key = IPv4 address, Value = ArpEntry>
|
||
|
|
||
|
// max table size
|
||
|
protected readonly int maxEntries;
|
||
|
|
||
|
// default entry age
|
||
|
protected int defaultAge;
|
||
|
|
||
|
// get the default age
|
||
|
public int Age { get { return defaultAge; } }
|
||
|
|
||
|
// the default age
|
||
|
public const int MaxAge = 50;
|
||
|
|
||
|
// aging timeout [msec]
|
||
|
public static readonly TimeSpan AgePeriod = TimeSpan.FromMinutes(5);
|
||
|
|
||
|
// our parent
|
||
|
protected ArpModule arp;
|
||
|
|
||
|
// ctor
|
||
|
public ArpTable(int size, int age, ArpModule arp)
|
||
|
{
|
||
|
ArpModule.DebugPrint("creating ArpTable size={0}, age={1}\n",
|
||
|
size, age);
|
||
|
|
||
|
arpEntries = new Hashtable(size);
|
||
|
maxEntries = size;
|
||
|
defaultAge = age;
|
||
|
this.arp = arp;
|
||
|
base();
|
||
|
}
|
||
|
|
||
|
// add a new entry
|
||
|
// return false if there is no more room
|
||
|
public bool AddEntry(ArpEntry! e)
|
||
|
{
|
||
|
// if no more room, make one
|
||
|
if (arpEntries.Count >= maxEntries)
|
||
|
{
|
||
|
PurgeLRUEntry();
|
||
|
}
|
||
|
e.EntryAge = this.defaultAge;
|
||
|
arpEntries.Add(e.IPAddress, e);
|
||
|
ArpModule.DebugPrint("Added entry {0}\n", e);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private void RemoveEntry(ArpEntry! e)
|
||
|
{
|
||
|
arpEntries.Remove(e.IPAddress);
|
||
|
ArpModule.DebugPrint("Removed entry for {0}\n", e);
|
||
|
}
|
||
|
|
||
|
public void RemoveEntry(IPv4 targetIP)
|
||
|
{
|
||
|
arpEntries.Remove(targetIP);
|
||
|
ArpModule.DebugPrint("Removed entry for {0}\n", targetIP);
|
||
|
}
|
||
|
|
||
|
// makes a room for a new entry, get rid of
|
||
|
// the least recently used entry (LRU)
|
||
|
public void PurgeLRUEntry()
|
||
|
{
|
||
|
if (arpEntries.Count == 0)
|
||
|
return;
|
||
|
|
||
|
// can use a LRU list to avoid O(n)
|
||
|
// but this is a kind of a small table...
|
||
|
IDictionaryEnumerator dicEnum = arpEntries.GetEnumerator();
|
||
|
dicEnum.MoveNext(); // get the first entry
|
||
|
|
||
|
ArpEntry lruElement = (ArpEntry!)dicEnum.Value;
|
||
|
|
||
|
while (dicEnum.MoveNext())
|
||
|
{
|
||
|
ArpEntry current = (ArpEntry)dicEnum.Value;
|
||
|
if (current.EntryAge < lruElement.EntryAge)
|
||
|
{
|
||
|
lruElement = current;
|
||
|
}
|
||
|
}
|
||
|
RemoveEntry(lruElement);
|
||
|
}
|
||
|
|
||
|
// age the dynamic table entries, if age drops to 0 purge entry
|
||
|
internal bool AgeTable()
|
||
|
{
|
||
|
int startCount = arpEntries.Count;
|
||
|
|
||
|
// Can't hold iterator and add or remove items so use
|
||
|
// list to hold items to be deleted.
|
||
|
ArrayList purgeItems = new ArrayList();
|
||
|
|
||
|
foreach (ArpEntry! e in arpEntries.Values)
|
||
|
{
|
||
|
if (e.Dynamic == true && --e.EntryAge == 0)
|
||
|
{
|
||
|
purgeItems.Add(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach (ArpEntry! e in purgeItems)
|
||
|
{
|
||
|
RemoveEntry(e);
|
||
|
}
|
||
|
|
||
|
return startCount < arpEntries.Count;
|
||
|
}
|
||
|
|
||
|
public ArpEntry Lookup(IPv4 destination)
|
||
|
{
|
||
|
return arpEntries[destination] as ArpEntry;
|
||
|
}
|
||
|
|
||
|
// an upper layer interface to get the mac
|
||
|
// to a target IP. The upper protocol must
|
||
|
// provide the Mux for the target IP.
|
||
|
// if we have it then we return true + macAddress
|
||
|
// and refresh the age to create a LRU list
|
||
|
public bool Lookup(IPv4 targetIP, out EthernetAddress macAddress)
|
||
|
{
|
||
|
ArpEntry e = arpEntries[targetIP] as ArpEntry;
|
||
|
if (e != null)
|
||
|
{
|
||
|
e.EntryAge = Age;
|
||
|
macAddress = e.MacAddress;
|
||
|
return true;
|
||
|
}
|
||
|
macAddress = EthernetAddress.Zero;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public override string! ToString()
|
||
|
{
|
||
|
StringBuilder stringBuilder = new StringBuilder();
|
||
|
stringBuilder.Append("Internet Address Physical Address Type \n");
|
||
|
stringBuilder.Append("-----------------------------------------------------------\n");
|
||
|
|
||
|
string [] types = { "static", "dynamic" };
|
||
|
foreach (ArpEntry! e in arpEntries.Values)
|
||
|
{
|
||
|
stringBuilder.Append(e.IPAddress + " " + e.MacAddress +
|
||
|
" " + types[ e.Dynamic ? 1 : 0] +
|
||
|
" [Age=" + e.EntryAge + "]\n");
|
||
|
}
|
||
|
return stringBuilder.ToString();
|
||
|
}
|
||
|
}
|
||
|
}
|