bool SendDhcpRequestAndWaitForAck(DhcpOffer dhcpOffer, UInt16 secondsElapsed, UInt32 dhcpServerIPAddress, UInt32 clientIPAddress, Int64 timeoutInMachineTicks) { bool success = false; // obtain an exclusive handle to the reserved socket int socketHandle = _ipv4Layer.CreateSocket(IPv4Layer.ProtocolType.Udp, timeoutInMachineTicks, true); // instantiate the reserved socket UdpSocket socket = (UdpSocket)_ipv4Layer.GetSocket(socketHandle); try { // bind the reserved socket to the DHCPv4 client port socket.Bind(0 /* IP_ADDRESS_ANY */, DHCP_CLIENT_PORT); // we will retry the DHCP request up to four times. first delay will be 4 +/-1 seconds; second delay will be 8 +/-1 seconds; third delay will be 16 +/-1 seconds; fourth delay will be 32 +/-1 seconds. // if our current timeoutInMachineTicks is longer than 64 seconds (the maximum wait for DHCP transmission) then reduce it to the maximum timeoutInMachineTicks = (Int64)System.Math.Max(timeoutInMachineTicks, Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks + (TimeSpan.TicksPerSecond * 64)); byte nextRetrySeconds = 4; Int64 nextRetryInMachineTicks = (Int64)(Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks + (((double)nextRetrySeconds + GenerateRandomPlusMinusOne()) * TimeSpan.TicksPerSecond)); // set our clientIdentifier byte[] clientIdentifier = new byte[1 + HARDWARE_ADDRESS_SIZE]; clientIdentifier[0] = HARDWARE_TYPE_ETHERNET; clientIdentifier[1] = (byte)((_physicalAddress >> 40) & 0xFF); clientIdentifier[2] = (byte)((_physicalAddress >> 32) & 0xFF); clientIdentifier[3] = (byte)((_physicalAddress >> 24) & 0xFF); clientIdentifier[4] = (byte)((_physicalAddress >> 16) & 0xFF); clientIdentifier[5] = (byte)((_physicalAddress >> 8) & 0xFF); clientIdentifier[6] = (byte)(_physicalAddress & 0xFF); byte[] parameterRequestList; if (_isDhcpDnsConfigEnabled) { parameterRequestList = new byte[] { (byte)DhcpOptionCode.SubnetMask, (byte)DhcpOptionCode.Router, (byte)DhcpOptionCode.DomainNameServer }; } else { parameterRequestList = new byte[] { (byte)DhcpOptionCode.SubnetMask, (byte)DhcpOptionCode.Router }; } byte[] maximumDhcpMessageSize = new byte[2]; maximumDhcpMessageSize[0] = (byte)((DHCP_FRAME_BUFFER_LENGTH >> 8) & 0xFF); maximumDhcpMessageSize[1] = (byte)(DHCP_FRAME_BUFFER_LENGTH & 0xFF); byte[] requestedIPAddress = new byte[4]; requestedIPAddress[0] = (byte)((dhcpOffer.IPAddress >> 24) & 0xFF); requestedIPAddress[1] = (byte)((dhcpOffer.IPAddress >> 16) & 0xFF); requestedIPAddress[2] = (byte)((dhcpOffer.IPAddress >> 8) & 0xFF); requestedIPAddress[3] = (byte)(dhcpOffer.IPAddress & 0xFF); byte[] serverIdentifier = new byte[4]; serverIdentifier[0] = (byte)((dhcpOffer.ServerIdentifier >> 24) & 0xFF); serverIdentifier[1] = (byte)((dhcpOffer.ServerIdentifier >> 16) & 0xFF); serverIdentifier[2] = (byte)((dhcpOffer.ServerIdentifier >> 8) & 0xFF); serverIdentifier[3] = (byte)(dhcpOffer.ServerIdentifier & 0xFF); while (timeoutInMachineTicks > Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks) { // assemble options DhcpOption[] options = new DhcpOption[5]; options[0] = new DhcpOption(DhcpOptionCode.ClientIdentifier, clientIdentifier); options[1] = new DhcpOption(DhcpOptionCode.ParameterRequestList, parameterRequestList); options[2] = new DhcpOption(DhcpOptionCode.MaximumDhcpMessageSize, maximumDhcpMessageSize); if (dhcpOffer.IPAddress != 0) options[3] = new DhcpOption(DhcpOptionCode.RequestedIPAddress, requestedIPAddress); if (dhcpOffer.ServerIdentifier != 0) options[4] = new DhcpOption(DhcpOptionCode.ServerIdentifier, serverIdentifier); // send DHCP message SendDhcpMessage(socket, DhcpMessageType.DHCPREQUEST, dhcpServerIPAddress, dhcpOffer.TransactionID, secondsElapsed, clientIPAddress, _physicalAddress, options, timeoutInMachineTicks); // wait for ACK/NAK bool responseIsAck; bool ackNakReceived = RetrieveAckNak(socket, dhcpOffer.TransactionID, _physicalAddress, ref dhcpOffer, out responseIsAck, nextRetryInMachineTicks); if (ackNakReceived) { success = responseIsAck; break; } secondsElapsed += nextRetrySeconds; nextRetrySeconds *= 2; nextRetryInMachineTicks = (Int64)(Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks + (((double)nextRetrySeconds + GenerateRandomPlusMinusOne()) * TimeSpan.TicksPerSecond)); } } finally { // close the reserved socket _ipv4Layer.CloseSocket(socketHandle); } return success; }
bool RetrieveAckNak(UdpSocket socket, UInt32 transactionID, UInt64 clientHardwareAddress, ref DhcpOffer dhcpOffer, out bool responseIsAck, Int64 timeoutInMachineTicks) { // set default ack/nak response responseIsAck = false; UInt32 assignedIPAddress; DhcpOption[] options; bool success = RetrieveDhcpMessage(socket, new DhcpMessageType[] { DhcpMessageType.DHCPACK, DhcpMessageType.DHCPNAK }, transactionID, clientHardwareAddress, out assignedIPAddress, out options, timeoutInMachineTicks); if (success == false) { return false; /* timeout */ } // analyze return value and update offer values if necessary for (int iOption = 0; iOption < options.Length; iOption++) { switch (options[iOption].Code) { case DhcpOptionCode.DhcpMessageType: { if (options[iOption].Value.Length >= 1) { responseIsAck = ((DhcpMessageType)options[iOption].Value[0] == DhcpMessageType.DHCPACK); } } break; case DhcpOptionCode.SubnetMask: { if (options[iOption].Value.Length >= 4) { dhcpOffer.SubnetMask = ( (UInt32)(options[iOption].Value[0] << 24) + (UInt32)(options[iOption].Value[1] << 16) + (UInt32)(options[iOption].Value[2] << 8) + (UInt32)(options[iOption].Value[3]) ); } } break; case DhcpOptionCode.Router: { /* NOTE: this option may include multiple router addresses; we select the first entry as our default gateway */ if (options[iOption].Value.Length >= 4) { dhcpOffer.GatewayAddress = ( (UInt32)(options[iOption].Value[0] << 24) + (UInt32)(options[iOption].Value[1] << 16) + (UInt32)(options[iOption].Value[2] << 8) + (UInt32)(options[iOption].Value[3]) ); } } break; case DhcpOptionCode.DomainNameServer: { /* NOTE: this option may include multiple DNS servers; we will only select up to the first two servers as our default DNS servers */ System.Collections.ArrayList dnsServerArrayList = new System.Collections.ArrayList(); for (int iDnsServer = 0; iDnsServer < options[iOption].Value.Length; iDnsServer += 4) { if (options[iOption].Value.Length >= iDnsServer + 4) { UInt32 dnsServerAddress = ( (UInt32)(options[iOption].Value[iDnsServer + 0] << 24) + (UInt32)(options[iOption].Value[iDnsServer + 1] << 16) + (UInt32)(options[iOption].Value[iDnsServer + 2] << 8) + (UInt32)(options[iOption].Value[iDnsServer + 3]) ); dnsServerArrayList.Add(dnsServerAddress); } } dhcpOffer.DnsAddresses = (UInt32[])dnsServerArrayList.ToArray(typeof(UInt32)); } break; case DhcpOptionCode.IPAddressLeaseTime: { if (options[iOption].Value.Length >= 4) { dhcpOffer.LeaseExpirationTimeInSeconds = ( (UInt32)(options[iOption].Value[0] << 24) + (UInt32)(options[iOption].Value[1] << 16) + (UInt32)(options[iOption].Value[2] << 8) + (UInt32)(options[iOption].Value[3]) ); } } break; case DhcpOptionCode.ServerIdentifier: { if (options[iOption].Value.Length >= 4) { dhcpOffer.ServerIdentifier = ( (UInt32)(options[iOption].Value[0] << 24) + (UInt32)(options[iOption].Value[1] << 16) + (UInt32)(options[iOption].Value[2] << 8) + (UInt32)(options[iOption].Value[3]) ); } } break; case DhcpOptionCode.RenewalTimeValue: { if (options[iOption].Value.Length >= 4) { dhcpOffer.LeaseRenewalTimeInSeconds = ( (UInt32)(options[iOption].Value[0] << 24) + (UInt32)(options[iOption].Value[1] << 16) + (UInt32)(options[iOption].Value[2] << 8) + (UInt32)(options[iOption].Value[3]) ); } } break; case DhcpOptionCode.RebindingTimeValue: { if (options[iOption].Value.Length >= 4) { dhcpOffer.LeaseRebindingTimeInSeconds = ( (UInt32)(options[iOption].Value[0] << 24) + (UInt32)(options[iOption].Value[1] << 16) + (UInt32)(options[iOption].Value[2] << 8) + (UInt32)(options[iOption].Value[3]) ); } } break; case DhcpOptionCode.ClientIdentifier: /* NOTE: we ignore this since our hardware address matches, although if we ever need to verify it then we can do so here */ break; } } return true; /* success */ }
void DhcpStateMachine() { while (true) { // wait for a change which requires the DHCPv4 state machine to process data _dhcpStateMachineEvent.WaitOne(); if (_isDisposed) return; if (_linkState == false) { if (_isDhcpIpConfigEnabled && _dhcpStateMachineState != DhcpStateMachineState.InitReboot) { if (IpConfigChanged != null) IpConfigChanged(this, 0, 0, 0); if (_isDhcpDnsConfigEnabled) { if (DnsConfigChanged != null) DnsConfigChanged(this, new UInt32[] { }); } } _dhcpStateMachineState = DhcpStateMachineState.InitReboot; } else /* if(_linkState == true) */ { // if DHCPv4 config is enabled, process our state machine now. if (_isDhcpIpConfigEnabled) { Int64 currentMachineTicks = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; if (currentMachineTicks > _leaseExpirationTimeInMachineTicks) { // lease has expired _ipConfigIPAddress = 0; _ipConfigSubnetMask = 0; _ipConfigGatewayAddress = 0; _dhcpServerAddress = 0; _leaseRenewalTimeInMachineTicks = Int64.MaxValue; _leaseRebindingTimeInMachineTicks = Int64.MaxValue; _leaseExpirationTimeInMachineTicks = Int64.MaxValue; _dhcpStateMachineState = DhcpStateMachineState.InitReboot; _dhcpStateMachineEvent.Set(); /* restart DHCP inquiry process */ continue; } switch (_dhcpStateMachineState) { case DhcpStateMachineState.InitReboot: { /* we are now discovering DhcpServers and collecting offers */ UInt16 secondsElapsed; DhcpOffer[] dhcpOffers = SendDhcpDiscoverAndCollectOffers(out secondsElapsed, Int64.MaxValue); if (dhcpOffers.Length == 0) { _dhcpStateMachineState = DhcpStateMachineState.InitReboot; _dhcpStateMachineEvent.Set(); /* restart DHCP inquiry process */ break; } // we received one or more dhcp offers; generate a DhcpRequest now. DhcpOffer dhcpOffer = dhcpOffers[0]; bool success = SendDhcpRequestAndWaitForAck(dhcpOffer, secondsElapsed, 0xFFFFFFFF, 0, Int64.MaxValue); if (!success) { _dhcpStateMachineState = DhcpStateMachineState.InitReboot; _dhcpStateMachineEvent.Set(); /* restart DHCP inquiry process */ break; } // we have set our new IP address! _dhcpServerAddress = dhcpOffer.ServerIdentifier; _ipConfigIPAddress = dhcpOffer.IPAddress; _ipConfigSubnetMask = dhcpOffer.SubnetMask; _ipConfigGatewayAddress = dhcpOffer.GatewayAddress; _dhcpStateMachineState = DhcpStateMachineState.Bound; /* configure our renewing/rebinding/expiration timers */ if (dhcpOffer.LeaseExpirationTimeInSeconds == UInt32.MaxValue) { _leaseExpirationTimeInMachineTicks = Int64.MaxValue; _leaseRenewalTimeInMachineTicks = Int64.MaxValue; _leaseRebindingTimeInMachineTicks = Int64.MaxValue; } else { _leaseExpirationTimeInMachineTicks = currentMachineTicks + (dhcpOffer.LeaseExpirationTimeInSeconds * TimeSpan.TicksPerSecond); _leaseRenewalTimeInMachineTicks = currentMachineTicks + (dhcpOffer.LeaseRenewalTimeInSeconds * TimeSpan.TicksPerSecond); _leaseRebindingTimeInMachineTicks = currentMachineTicks + (dhcpOffer.LeaseRebindingTimeInSeconds * TimeSpan.TicksPerSecond); _leaseUpdateTimer.Change((Int32)((_leaseRenewalTimeInMachineTicks - currentMachineTicks) / TimeSpan.TicksPerMillisecond), System.Threading.Timeout.Infinite); } if (IpConfigChanged != null) { IpConfigChanged(this, _ipConfigIPAddress, _ipConfigGatewayAddress, _ipConfigSubnetMask); } if ((_isDhcpDnsConfigEnabled) && (DnsConfigChanged != null)) { DnsConfigChanged(this, dhcpOffer.DnsAddresses); } } break; case DhcpStateMachineState.Bound: { if (currentMachineTicks > _leaseRenewalTimeInMachineTicks) { _dhcpStateMachineState = DhcpStateMachineState.Renewing; DhcpOffer dhcpOffer = new DhcpOffer(GenerateRandomTransactionID(), 0, 0, 0, 0, new UInt32[0]); bool success = SendDhcpRequestAndWaitForAck(dhcpOffer, 0, _dhcpServerAddress, _ipConfigIPAddress, Int64.MaxValue); if (success) { _dhcpStateMachineState = DhcpStateMachineState.Bound; /* configure our renewing/rebinding/expiration timers */ if (dhcpOffer.LeaseExpirationTimeInSeconds == UInt32.MaxValue) { _leaseExpirationTimeInMachineTicks = Int64.MaxValue; _leaseRenewalTimeInMachineTicks = Int64.MaxValue; _leaseRebindingTimeInMachineTicks = Int64.MaxValue; } else { _leaseExpirationTimeInMachineTicks = currentMachineTicks + (dhcpOffer.LeaseExpirationTimeInSeconds * TimeSpan.TicksPerSecond); _leaseRenewalTimeInMachineTicks = currentMachineTicks + (dhcpOffer.LeaseRenewalTimeInSeconds * TimeSpan.TicksPerSecond); _leaseRebindingTimeInMachineTicks = currentMachineTicks + (dhcpOffer.LeaseRebindingTimeInSeconds * TimeSpan.TicksPerSecond); } } else { _leaseUpdateTimer.Change((Int32)((_leaseRebindingTimeInMachineTicks - currentMachineTicks) / TimeSpan.TicksPerMillisecond), System.Threading.Timeout.Infinite); } } else { _leaseUpdateTimer.Change((Int32)((_leaseRenewalTimeInMachineTicks - currentMachineTicks) / TimeSpan.TicksPerMillisecond), System.Threading.Timeout.Infinite); } } break; case DhcpStateMachineState.Renewing: { if (currentMachineTicks > _leaseRebindingTimeInMachineTicks) { _dhcpStateMachineState = DhcpStateMachineState.Rebinding; DhcpOffer dhcpOffer = new DhcpOffer(GenerateRandomTransactionID(), 0, 0, 0, 0, new UInt32[0]); bool success = SendDhcpRequestAndWaitForAck(dhcpOffer, 0, 0xFFFFFFFF, _ipConfigIPAddress, Int64.MaxValue); if (success) { _dhcpStateMachineState = DhcpStateMachineState.Bound; /* configure our renewing/rebinding/expiration timers */ if (dhcpOffer.LeaseExpirationTimeInSeconds == UInt32.MaxValue) { _leaseExpirationTimeInMachineTicks = Int64.MaxValue; _leaseRenewalTimeInMachineTicks = Int64.MaxValue; _leaseRebindingTimeInMachineTicks = Int64.MaxValue; } else { _leaseExpirationTimeInMachineTicks = currentMachineTicks + (dhcpOffer.LeaseExpirationTimeInSeconds * TimeSpan.TicksPerSecond); _leaseRenewalTimeInMachineTicks = currentMachineTicks + (dhcpOffer.LeaseRenewalTimeInSeconds * TimeSpan.TicksPerSecond); _leaseRebindingTimeInMachineTicks = currentMachineTicks + (dhcpOffer.LeaseRebindingTimeInSeconds * TimeSpan.TicksPerSecond); } } else { _leaseUpdateTimer.Change((Int32)((_leaseExpirationTimeInMachineTicks - currentMachineTicks) / TimeSpan.TicksPerMillisecond), System.Threading.Timeout.Infinite); } } else { _leaseUpdateTimer.Change((Int32)((_leaseRebindingTimeInMachineTicks - currentMachineTicks) / TimeSpan.TicksPerMillisecond), System.Threading.Timeout.Infinite); } } break; //case DhcpStateMachineState.Rebinding: // break; } } } } }