/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Netstack / Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: DhcpClientSession.cs // // Note: DHCP Client Session. // //#define DEBUG_DHCP_CLIENT using NetStack.Common; using System; using System.Collections; using System.Diagnostics; using System.Threading; using System.Net.IP; using Drivers.Net; using NetStack.NetDrivers; #if SINGULARITY using Microsoft.Singularity; #else using System.Net; #endif namespace NetStack.Runtime { using Protocols; /// /// A DhcpClient acts as a DHCP Client on a particular interface. /// /// The implementation is based on RFC2131. /// public class DhcpClient { private IAdapter adapter; private UdpSession udpSession; private Thread workerThread; private bool workerDone; private DhcpClientState @state; private SortedList activeDhcpOptions; private DateTime renewalTimeout; private DateTime rebindTimeout; private DateTime stateTimeout; private DateTime transactionStart; private uint transactionID; public static byte [] StandardRequestParameters = new byte [] { DhcpSubnetMask.OptionCode, DhcpRouter.OptionCode, DhcpHostName.OptionCode, DhcpTimeOffset.OptionCode, DhcpDomainNameServer.OptionCode, DhcpDomainName.OptionCode, DhcpInterfaceMtu.OptionCode, DhcpRouterDiscovery.OptionCode, DhcpStaticRoutes.OptionCode, DhcpRenewalTime.OptionCode, DhcpRebindingTime.OptionCode, DhcpIPAddressLeaseTime.OptionCode, DhcpVendorSpecific.OptionCode }; private static readonly TimeSpan PollInterval = TimeSpan.FromMilliseconds(1000); [ Conditional("DEBUG_DHCP_CLIENT") ] internal static void DebugPrint(string format, params object [] args) { Core.Log("DhcpClient: "); Core.Log(format, args); } public DhcpClient(IAdapter adapter) { this.adapter = adapter; transactionID = (uint)(new Random()).Next(); } ~DhcpClient() { if (workerThread != null) { Stop(); } } public IAdapter Adapter { get { return adapter; } } public EthernetAddress MacAddress { get { return adapter.HardwareAddress; } } public DateTime TransactionStart { get { return transactionStart; } } public ushort TransactionSeconds { get { TimeSpan delta = DateTime.Now - transactionStart; return (ushort)delta.Seconds; } } public uint TransactionID { get { return transactionID; } } public bool Start() { lock (this) { if (udpSession != null) { return false; } Core core = Core.Instance(); UdpModule udpModule = core.GetProtocolByName("UDP") as UdpModule; if (udpModule == null) { DebugPrint("DhcpClient.Start() failed -- " + "UdpModule not found.\n"); return false; } udpSession = udpModule.CreateBoundSession( IPv4.Any, DhcpFormat.ClientPort, IPv4.Any, DhcpFormat.ServerPort ); udpSession.BoundMux = core.GetMuxForAdapter(adapter); ResetAdapterIPInfo(); workerDone = false; workerThread = new Thread(new ThreadStart(WorkerMain)); #if !SINGULARITY workerThread.Name = String.Format("DhcpClient/{0}", adapter.HardwareAddress); #endif workerThread.Start(); return true; } } public void Stop() { lock (this) { workerDone = true; workerThread.Join(); udpSession.Close(); udpSession = null; workerThread = null; CancelTimeouts(); @state = null; } DebugPrint("DhcpClient.Stop()\n"); } private bool SetTimeout(ref DateTime timeout, DateTime when) { timeout = when; return timeout >= DateTime.Now; } internal void SetRenewalTimeout(DateTime when) { if (SetTimeout(ref renewalTimeout, when) == false) DebugPrint("Set renewal timeout in the past!\n"); } internal void CancelRenewalTimeout() { renewalTimeout = DateTime.MaxValue; } internal void SetRebindTimeout(DateTime when) { if (SetTimeout(ref rebindTimeout, when) == false) DebugPrint("Set rebind timeout in the past!\n"); } internal void CancelRebindTimeout() { rebindTimeout = DateTime.MaxValue; } internal void SetStateTimeout(DateTime when) { if (SetTimeout(ref stateTimeout, when) == false) DebugPrint("Set state timeout in the past!\n"); } internal void CancelStateTimeout() { stateTimeout = DateTime.MaxValue; } internal void CancelTimeouts() { CancelRenewalTimeout(); CancelRebindTimeout(); CancelStateTimeout(); } internal void ChangeState(DhcpClientState newState) { assert newState != null; @state = newState; newState.EnterEvent(); DebugPrint("ChangeState -> {0}\n", newState.Name); } internal bool InstallDhcpOptions(SortedList! dhcpOptions) { if (activeDhcpOptions != null) UninstallDhcpOptions(); IPModule ip = (IPModule) Core.Instance().GetProtocolByName("IP"); if (ip == null) { DebugPrint("Failed to get IP Module\n"); return false; } // // Add interface address binding // DhcpIPv4Option address = dhcpOptions[DhcpRequestedIPAddress.OptionCode] as DhcpIPv4Option; DhcpIPv4Option netmask = dhcpOptions[DhcpSubnetMask.OptionCode] as DhcpIPv4Option; DhcpMultiIPv4Option routers = dhcpOptions[DhcpRouter.OptionCode] as DhcpMultiIPv4Option; if (address == null || netmask == null || routers == null || routers.Values.Length == 0) { return false; } ip.HostConfiguration.Bindings.Add( adapter, new InterfaceIPConfiguration(address.Value, netmask.Value, routers.Values[0]) ); // // Register Domain name // DhcpStringOption domain = dhcpOptions[DhcpDomainName.OptionCode] as DhcpStringOption; // string domainName = ip.HostConfiguration.GetDomainName(); // never used if (domain != null) { ip.HostConfiguration.SetDomainName(domain.Value); } // // Add DNS servers // DhcpMultiIPv4Option dnsServers = dhcpOptions[DhcpDomainNameServer.OptionCode] as DhcpMultiIPv4Option; if (dnsServers != null) { foreach (IPv4 server in dnsServers.Values) { ip.HostConfiguration.AddNameServer(server); } } // // Install static routes // DhcpMultiIPv4Option staticRoutes = dhcpOptions[DhcpStaticRoutes.OptionCode] as DhcpMultiIPv4Option; if (staticRoutes != null) { int routeCount = staticRoutes.Values.Length & ~1; // pairs for (int i = 0; i < routeCount; i += 2) { IPv4 destination = staticRoutes.Values[i]; IPv4 gateway = staticRoutes.Values[i + 1]; IPv4 ifAddress = address.Value; ip.HostConfiguration.RoutingTable.AddRoute( new RouteEntry(destination, gateway, ifAddress, RouteEntry.DefaultRouteMetric, 0) ); } } activeDhcpOptions = dhcpOptions; return true; } internal void UninstallDhcpOptions() { // Back-out options IPModule ip = (IPModule!) Core.Instance().GetProtocolByName("IP"); DhcpIPv4Option address = (DhcpIPv4Option!) activeDhcpOptions[DhcpRequestedIPAddress.OptionCode]; IPv4 ifAddress = address.Value; // // Remove routes associated with interface address // ip.HostConfiguration.RoutingTable.DeleteInterfaceRoutes(ifAddress); // // Remove name server // DhcpMultiIPv4Option dnsServers = activeDhcpOptions[DhcpDomainNameServer.OptionCode] as DhcpMultiIPv4Option; if (dnsServers != null) { foreach (IPv4 server in dnsServers.Values) { ip.HostConfiguration.AddNameServer(server); } } // // Leave domain name in place // // If we wanted to remove it... DhcpStringOption domain = activeDhcpOptions[DhcpDomainName.OptionCode] as DhcpStringOption; string domainName = ip.HostConfiguration.GetDomainName(); if (domain != null && domainName == domain.Value) { ip.HostConfiguration.SetDomainName(""); } // // Remove interface address bindings // ip.HostConfiguration.Bindings.Flush(adapter, ifAddress); activeDhcpOptions = null; } internal void StartNewTransaction() { transactionID++; transactionStart = DateTime.Now; } private void WorkerMain() { DebugPrint("Worker starting\n"); Random r = new Random(); transactionID = (uint)r.Next(); DateTime startTime = DateTime.Now; // Enter "Init" state of FSM ChangeState(new DhcpClientStateInitialize(this)); while (workerDone == false) { // Check for timeouts DateTime now = DateTime.Now; if (now >= renewalTimeout) { CancelRenewalTimeout(); @state.RenewalTimeoutEvent(); } if (now >= rebindTimeout) { CancelRebindTimeout(); @state.RebindTimeoutEvent(); } if (now >= stateTimeout) { CancelStateTimeout(); @state.StateTimeoutEvent(); } // Poll for data try { byte [] data = udpSession.PollCopyData(PollInterval); if (data != null) { SimpleBuffer sb = new SimpleBuffer(data); DhcpFormat dhcp = DhcpFormat.Parse(sb); // Check transaction id is ours if (dhcp.TransactionID != transactionID) { continue; } // Check client address is ours if (dhcp.GetHardwareAddress() != MacAddress) { continue; } @state.ReceiveEvent(dhcp); } } catch (InvalidDhcpFormatException idfe) { DebugPrint(idfe.Message); } // XXX Temporary until process can run in background // from shell. ie we'd like to run and renew lease // but shell blocks on running process and cleans up // after it for the time being. if (activeDhcpOptions != null) { DebugPrint("Got options -- done\n"); break; } if (DateTime.Now - startTime > TimeSpan.FromSeconds(5)) { DebugPrint("Timed out\n"); break; } } } internal void ResetAdapterIPInfo() { } internal bool Send(EthernetAddress dstAddr, DhcpFormat! dhcp) { int packetSize = dhcp.Size + UdpFormat.Size + IPFormat.Size + EthernetFormat.Size; byte [] packet = new byte [packetSize]; // Write out DHCP packet int dhcpSize = dhcp.Size; int dhcpOffset = packet.Length - dhcpSize; dhcp.Write(packet, dhcpOffset); // Create UDP Header UdpFormat.UdpHeader udpHeader = new UdpFormat.UdpHeader(); udpHeader.srcPort = DhcpFormat.ClientPort; udpHeader.dstPort = DhcpFormat.ServerPort; udpHeader.length = (ushort)(UdpFormat.Size + dhcpSize); // Create IP Header IPFormat.IPHeader ipHeader = new NetStack.Protocols.IPFormat.IPHeader(); ipHeader.SetDefaults(IPFormat.Protocol.UDP); IPFormat.SetDontFragBit(ipHeader); ipHeader.Source = IPv4.Any; ipHeader.Destination = IPv4.Broadcast; ipHeader.totalLength = (ushort)(IPFormat.Size + UdpFormat.Size + dhcpSize); // Write out IP and Header int udpOffset = packet.Length - dhcp.Size - UdpFormat.Size; int ipOffset = udpOffset - IPFormat.Size; UdpFormat.WriteUdpPacket(packet, ipOffset, ipHeader, ref udpHeader, packet, dhcpOffset, dhcpSize); // Add Ethernet Header EthernetFormat.Write(packet, 0, adapter.HardwareAddress, dstAddr, EthernetFormat.PROTOCOL_IP); NetPacket np = new NetPacket(packet); return udpSession.WritePacket(np); } } }