A stack which works with or without an Mbed os library. Provides IPv4 or IPv6 with a full 1500 byte buffer.

Dependents:   oldheating gps motorhome heating

Revision:
183:ee809769bf89
Parent:
181:169881ecd52f
Child:
184:ad80a63e3002
--- a/udp/dhcp/dhcp.c	Sun Jan 10 11:52:19 2021 +0000
+++ b/udp/dhcp/dhcp.c	Tue Jan 12 19:10:22 2021 +0000
@@ -28,23 +28,37 @@
 #define DHCPRELEASE  7
 #define DHCPINFORM   8
 
-#define ID 0x12345
 #define COOKIE 0x63825363
 
 #define MAX_REPEAT_DELAY_TIME_MS 60000
 #define MIN_REPEAT_DELAY_TIME_MS   900
-static uint32_t repeatDelayMsTimer = (uint32_t)-MIN_REPEAT_DELAY_TIME_MS; //Initial value ensures no delay at startup
-static uint32_t delayMs            =            MIN_REPEAT_DELAY_TIME_MS; //Doubles on failure up to max; reset to min whenever an IP address request has been acknowledged
+
+#define STATE_NONE     0
+#define STATE_DISCOVER 1
+#define STATE_SELECT   2
+#define STATE_BOUND    3
+#define STATE_RENEW    4
+#define STATE_REBIND   5
+#define STATE_EXPIRED  6
 
-static uint32_t elapsedLifeMsTimer  = 0;              //Started whenever an IP address request has been acknowledged
+static uint32_t       _xid = 0;
+static int          _state = STATE_NONE;
+static uint32_t _offeredIp = 0;
+static uint32_t  _serverId = 0;
+static bool _awaitResponse = false;
+
+static uint32_t _repeatDelayMsTimer = (uint32_t)-MIN_REPEAT_DELAY_TIME_MS; //Initial value ensures no delay at startup
+static uint32_t _delayMs            =            MIN_REPEAT_DELAY_TIME_MS; //Doubles on failure up to max; reset to min whenever an IP address request has been acknowledged
+
+static uint32_t _elapsedLifeMsTimer  = 0;              //Started whenever an IP address request has been acknowledged
 
 uint32_t DhcpGetElapsedLife()
 {
-    if (!elapsedLifeMsTimer) return 0;
-    return (MsTimerCount - elapsedLifeMsTimer) / 1000;
+    if (!_elapsedLifeMsTimer) return 0;
+    return (MsTimerCount - _elapsedLifeMsTimer) / 1000;
 }
 
-static uint8_t  dhcpMessageType = 0;
+static uint8_t  _dhcpMessageType = 0;
 uint32_t DhcpLeaseTime   = 0;
 uint32_t DhcpServerIp    = 0;
 uint32_t DhcpRouterIp    = 0;
@@ -72,6 +86,8 @@
     DhcpDnsServerIp   = 0;
     DhcpDomainName[0] = 0;
     DhcpHostName  [0] = 0;
+    _offeredIp        = 0;
+    _serverId         = 0;
 }
 
 bool DhcpIpNeedsToBeRouted(uint32_t ip)
@@ -129,25 +145,25 @@
     {
         switch (*p)
         {
-            case 0:                                                     break;  //NOP
-            case 255:                                                   return; //End of options
-            case 1:               DhcpSubnetMask  = readIp(&p);         break;  //Subnet Mask
-            case 3:               DhcpRouterIp    = readIp(&p);         break;  //Router
-            case 6:               DhcpDnsServerIp = readIp(&p);         break;  //DNS server
-            case 12:              readString(&p, DhcpHostName);         break;  //Host name
-            case 15:              readString(&p, DhcpDomainName);       break;  //Domain name
-            case 19:  len = *++p; p+= len;                              break;  //IP forwarding yes/no
-            case 28:              DhcpBroadcastIp = readIp(&p);         break;  //Broadcast IP
-            case 42:              DhcpNtpIp       = readIp(&p);         break;  //NTP
-            case 44:  len = *++p; p+= len;                              break;  //NetBIOS name server
-            case 45:  len = *++p; p+= len;                              break;  //NetBIOS datagram server
-            case 46:  len = *++p; p+= len;                              break;  //NetBIOS node type
-            case 47:  len = *++p; p+= len;                              break;  //NetBIOS scope
-            case 53:  len = *++p; dhcpMessageType = *++p;               break;  //DHCP message type
-            case 51:              DhcpLeaseTime   = readOption32(&p);   break;  //Address lease time
-            case 54:              DhcpServerIp    = readIp(&p);         break;  //DHCP server
-            case 58:              DhcpRenewalT1   = readOption32(&p);   break;  //T1
-            case 59:              DhcpRenewalT2   = readOption32(&p);   break;  //T2
+            case 0:                                                      break;  //NOP
+            case 255:                                                    return; //End of options
+            case 1:                DhcpSubnetMask  = readIp(&p);         break;  //Subnet Mask
+            case 3:                DhcpRouterIp    = readIp(&p);         break;  //Router
+            case 6:                DhcpDnsServerIp = readIp(&p);         break;  //DNS server
+            case 12:               readString(&p, DhcpHostName);         break;  //Host name
+            case 15:               readString(&p, DhcpDomainName);       break;  //Domain name
+            case 19:  len = *++p; p+= len;                               break;  //IP forwarding yes/no
+            case 28:               DhcpBroadcastIp = readIp(&p);         break;  //Broadcast IP
+            case 42:               DhcpNtpIp       = readIp(&p);         break;  //NTP
+            case 44:  len = *++p; p+= len;                               break;  //NetBIOS name server
+            case 45:  len = *++p; p+= len;                               break;  //NetBIOS datagram server
+            case 46:  len = *++p; p+= len;                               break;  //NetBIOS node type
+            case 47:  len = *++p; p+= len;                               break;  //NetBIOS scope
+            case 53:  len = *++p; _dhcpMessageType = *++p;               break;  //DHCP message type
+            case 51:               DhcpLeaseTime   = readOption32(&p);   break;  //Address lease time
+            case 54:               _serverId       = readIp(&p);         break;  //DHCP server
+            case 58:               DhcpRenewalT1   = readOption32(&p);   break;  //T1
+            case 59:               DhcpRenewalT2   = readOption32(&p);   break;  //T2
             default:
                 if (DhcpTrace) LogTimeF("Ignoring option %d\r\n", *p);
                 len = *++p;
@@ -157,6 +173,40 @@
         p++;
     }
 }
+static void readOptionMessageType(int size, char * pOptions)
+{
+    int len = 0;
+    char* p  = pOptions;
+    char* pE = pOptions + size;
+    while( p < pE)
+    {
+        switch (*p)
+        {
+            case 0:                                                      break;  //NOP
+            case 255:                                                    return; //End of options
+            case 53:  len = *++p; _dhcpMessageType = *++p;               break;  //DHCP message type
+            default:  len = *++p; p += len;                              break;
+        }
+        p++;
+    }
+}
+static void readOptionServerId(int size, char * pOptions)
+{
+    int len = 0;
+    char* p  = pOptions;
+    char* pE = pOptions + size;
+    while( p < pE)
+    {
+        switch (*p)
+        {
+            case 0:                                                      break;  //NOP
+            case 255:                                                    return; //End of options
+            case 54:               _serverId       = readIp(&p);         break;  //DHCP server
+            default:  len = *++p; p += len;                              break;
+        }
+        p++;
+    }
+}
 static void writeIp(uint8_t code, uint32_t value, char** pp)
 {
     if (!value) return;
@@ -193,42 +243,60 @@
     
     *pp += *pLength + 2;
 }
-int sendRequest(void* pPacket, uint8_t code, uint32_t srvIp, uint32_t reqIp)
+int sendRequest(void* pPacket)
 {
-
-    switch (code)
+    uint8_t         type = 0;
+    uint32_t   currentIp = 0; //goes in ciaddr 
+    uint32_t requestedIp = 0; //goes in option 54
+    uint32_t    serverIp = 0; //goes in option 50 and is the server id for the accepted offer
+    
+    switch (_state)
     {
-        case DHCPDISCOVER:
-            if (DhcpTrace) LogTimeF("DHCP -> discover");
+        case STATE_DISCOVER:
+            if (DhcpTrace) LogTimeF("DHCP -> discover\r\n");
+                   type = DHCPDISCOVER;
+              currentIp = 0;
+            requestedIp = 0;
+               serverIp = 0;
+            break;
+        case STATE_SELECT:
+            if (DhcpTrace) LogTimeF("DHCP -> select "); Ip4AddressLog(_offeredIp); Log(" from server "); Ip4AddressLog(_serverId); Log("\r\n");
+                   type = DHCPREQUEST;
+              currentIp = 0;
+            requestedIp = _offeredIp;
+               serverIp = _serverId;
             break;
-        case DHCPREQUEST:
-            if (DhcpTrace) LogTimeF("DHCP -> request");
+        case STATE_RENEW:
+            if (DhcpTrace) LogTimeF("DHCP -> renew (T1)\r\n");
+                   type = DHCPREQUEST;
+              currentIp = DhcpLocalIp;
+            requestedIp = 0;
+               serverIp = 0;
+            break;
+        case STATE_REBIND:
+            if (DhcpTrace) LogTimeF("DHCP -> rebind (T2)\r\n");
+                   type = DHCPREQUEST;
+              currentIp = 0;
+            requestedIp = 0;
+               serverIp = 0;
             break;
         default:
-            LogTimeF("DHCP -> unknown message %d", code);
-            break;
-    }
-    if (DhcpTrace)
-    {
-        Log(" server=" ); Ip4AddressLog(srvIp);
-        Log(" request="); Ip4AddressLog(reqIp);
-        Log("\r\n");
+            LogTimeF("DHCP -> unknown state %d\r\n", _state);
+            return 0;
     }
 
-    bool broadcast = DhcpLocalIp == 0;
+    bool broadcast = currentIp == 0;
     uint16_t flags = 0;
     if (broadcast) flags |= 0x8000;
     DhcpHdrSetOp    (pPacket, REQUEST            );
     DhcpHdrSetHtype (pPacket, ETHERNET           );
     DhcpHdrSetHlen  (pPacket, 6                  );
     DhcpHdrSetHops  (pPacket, 0                  );
-    DhcpHdrSetXid   (pPacket, ID                 ); //Randomly chosed transaction id used to associate messages to responses
+    DhcpHdrSetXid   (pPacket, _xid               ); //Randomly chosed transaction id used to associate messages to responses
     DhcpHdrSetSecs  (pPacket, 0                  ); //Seconds since started to boot
     DhcpHdrSetFlags (pPacket, flags              ); //Broadcast (1) Unicast (0)
-    //DhcpHdrSetCiaddr(pPacket, reqIp              ); //'Client' address set by client or 0 if don't know address
-    DhcpHdrSetCiaddr(pPacket, 0                  ); //'Client' address set by client or 0 if don't know address
+    DhcpHdrSetCiaddr(pPacket, currentIp          ); //'Client' address set by client or 0 if don't know address
     DhcpHdrSetYiaddr(pPacket, 0                  ); //'Your' address returned by server
-    //DhcpHdrSetSiaddr(pPacket, srvIp              ); //'Server' address to use if required
     DhcpHdrSetSiaddr(pPacket, 0                  ); //'Server' address to use if required
     DhcpHdrSetGiaddr(pPacket, 0                  ); //'Gateway' address
     memcpy(DhcpHdrPtrChaddr(pPacket), MacLocal, 6); //'Client hardware' address. 6 bytes for ethernet
@@ -237,17 +305,17 @@
 
     char* pOptions = (char*)pPacket + DHCP_HEADER_LENGTH;
     char* p = pOptions;
-    *p++ = 53;                        //Message code
-    *p++ = 1;
-    *p++ = code;
+    *p++ = 53; *p++ = 1; *p++ = type;                  //DHCP message type
+    if (requestedIp) writeIp(50, requestedIp, &p);     //Requested IP
+    if (   serverIp) writeIp(54,    serverIp, &p);     //Server ip
+    writeText(12, NET_NAME, &p);                       //Host name
+    *p++ = 255;                                        //End of options
 
-    if (reqIp) writeIp(50, reqIp, &p);     //Requested IP
-    if (srvIp) writeIp(54, srvIp, &p);     //Server ip
+    _awaitResponse = true;
+    _delayMs <<= 1;                                                                 //Backoff (double) the delay time after each attempt
+    if (_delayMs > MAX_REPEAT_DELAY_TIME_MS) _delayMs = MAX_REPEAT_DELAY_TIME_MS;   //Don't go beyond a maximum
+    _repeatDelayMsTimer = MsTimerCount;                                             //Start the delay timer
     
-    writeText(12, NET_NAME, &p);
-
-    *p++ = 255;                       //End of options
-
     return DHCP_HEADER_LENGTH + p - pOptions;
 }
 int DhcpHandleResponse(void (*traceback)(void), int sizeRx, char* pPacketRx, int* pSizeTx, char* pPacketTx)
@@ -264,31 +332,61 @@
     if (htype  != ETHERNET)                               return DO_NOTHING;
     if (hlen   != 6)                                      return DO_NOTHING;
     if (memcmp(DhcpHdrPtrChaddr(pPacketRx), MacLocal, 6)) return DO_NOTHING;
-    if (xid    != ID)                                     return DO_NOTHING;
+    if (!_xid)                                            return DO_NOTHING;
+    if (xid    != _xid)                                   return DO_NOTHING;
     if (cookie != COOKIE)                                 return DO_NOTHING;
 
     char* pOptions = (char*)pPacketRx + DHCP_HEADER_LENGTH;
-    readOptions(sizeRx - DHCP_HEADER_LENGTH, pOptions);
-
-    switch (dhcpMessageType)
+    readOptionMessageType(sizeRx - DHCP_HEADER_LENGTH, pOptions);
+    
+    switch (_dhcpMessageType)
     {
         case DHCPOFFER:
-            if (DhcpTrace) { LogTime("DHCP <- offer ip "); Ip4AddressLog(yiaddr); Log(" server ip "); Ip4AddressLog(DhcpServerIp); Log("\r\n"); }
-            //*pSizeTx = sendRequest(pPacketTx, DHCPREQUEST, siaddr, yiaddr);
-            *pSizeTx = sendRequest(pPacketTx, DHCPREQUEST, DhcpServerIp, yiaddr);
-            return BROADCAST;
+            if (_state == STATE_DISCOVER)
+            {
+                _offeredIp = yiaddr;
+                readOptionServerId(sizeRx - DHCP_HEADER_LENGTH, pOptions);
+                if (DhcpTrace) { LogTime("DHCP <- offer  "); Ip4AddressLog(_offeredIp); Log(" from server "); Ip4AddressLog(_serverId); Log("\r\n"); }
+                _awaitResponse = false;
+                _delayMs = MIN_REPEAT_DELAY_TIME_MS; //Set the delay time back to minimum
+            }
+            else
+            {
+                if (DhcpTrace) { LogTime("DHCP <- offer  "); Ip4AddressLog(_offeredIp); Log(" from server "); Ip4AddressLog(_serverId); Log(" ignored\r\n"); }
+            }
+            break;
         case DHCPACK:
-            if (DhcpTrace) { LogTime("DHCP <- ack ip ");   Ip4AddressLog(yiaddr); Log(" server ip "); Ip4AddressLog(DhcpServerIp); Log("\r\n"); }
-            DhcpLocalIp = yiaddr; //Save our IP
-            elapsedLifeMsTimer = MsTimerCount;  //Start the life timer
-            delayMs = MIN_REPEAT_DELAY_TIME_MS; //Set the delay time back to minimum
+            if (_state == STATE_SELECT || _state == STATE_RENEW || _state == STATE_REBIND)
+            {
+                DhcpLocalIp = yiaddr;
+                readOptions(sizeRx - DHCP_HEADER_LENGTH, pOptions);
+                DhcpServerIp = _serverId;
+                if (DhcpTrace) { LogTime("DHCP <- ack    ");   Ip4AddressLog(DhcpLocalIp); Log(" from server "); Ip4AddressLog(_serverId); Log("\r\n"); }
+                _elapsedLifeMsTimer = MsTimerCount;  //Start the life timer
+                _awaitResponse = false;
+                _delayMs = MIN_REPEAT_DELAY_TIME_MS; //Set the delay time back to minimum
+            }
+            else
+            {
+                if (DhcpTrace) { LogTime("DHCP <- ack    ");   Ip4AddressLog(DhcpLocalIp); Log(" from server "); Ip4AddressLog(_serverId); Log(" ignored\r\n"); }
+            }
             break;
         case DHCPNAK:
-            if (DhcpTrace) { LogTime("DHCP <- nack ip ");  Ip4AddressLog(yiaddr); Log(" server ip "); Ip4AddressLog(DhcpServerIp); Log("\r\n"); }
-            clearAll();
+            if (_state == STATE_SELECT || _state == STATE_RENEW || _state == STATE_REBIND)
+            {
+                readOptionServerId(sizeRx - DHCP_HEADER_LENGTH, pOptions);
+                if (DhcpTrace) { LogTime("DHCP <- nack   ");  Ip4AddressLog(yiaddr); Log(" from server "); Ip4AddressLog(_serverId); Log("\r\n"); }
+                clearAll();
+                _awaitResponse = false;
+                _delayMs = MIN_REPEAT_DELAY_TIME_MS; //Set the delay time back to minimum
+            }
+            else
+            {
+                if (DhcpTrace) { LogTime("DHCP <- nack   ");  Ip4AddressLog(yiaddr); Log(" from server "); Ip4AddressLog(_serverId); Log(" ignored\r\n"); }
+            }
             break;
         default:
-            LogTimeF("DHCP <- unknown message %d\r\n", dhcpMessageType);
+            LogTimeF("DHCP <- unknown message %d\r\n", _dhcpMessageType);
             break;
     }
     return DO_NOTHING;
@@ -296,32 +394,77 @@
 
 int DhcpPollForRequestToSend(void* pPacket, int* pSize)
 {
+    
+    if (!_xid)
+    {
+        _xid  = (uint32_t)MacLocal[2] << 24;
+        _xid += (uint32_t)MacLocal[3] << 16;
+        _xid += (uint32_t)MacLocal[4] <<  8;
+        _xid += (uint32_t)MacLocal[5] <<  0;
+    }
+    
     //Check if time to update
-    uint32_t elapsedTimeMs = MsTimerCount - elapsedLifeMsTimer;
+    uint32_t elapsedTimeMs = MsTimerCount - _elapsedLifeMsTimer;
     uint32_t   leaseTimeMs = DhcpLeaseTime * 1000;
-
-    if (DhcpLocalIp && elapsedTimeMs < (leaseTimeMs >> 1)) return DO_NOTHING;      //Do nothing if have address and within T1
+    
+    uint32_t T1 =  leaseTimeMs >> 1;                                            //0.5
+    uint32_t T2 = (leaseTimeMs >> 1) + (leaseTimeMs >> 2) + (leaseTimeMs >> 3); //0.875 = 0.5 + 0.25 + 0.125
+    
+    if (!DhcpLocalIp)
+    {
+        if (!_offeredIp) _state = STATE_DISCOVER;
+        else             _state = STATE_SELECT;
+    }
+    else
+    {
+        if (elapsedTimeMs < T1)
+        {
+            _state = STATE_BOUND;
+        }
+        else if (elapsedTimeMs < T2)
+        {
+            _state = STATE_RENEW; 
+        }
+        else if (elapsedTimeMs < leaseTimeMs)
+        {
+            _state = STATE_REBIND;
+        }
+        else
+        {
+            _state = STATE_EXPIRED;
+        }
+    }
+    static int stateLastScan = STATE_NONE;
+    bool stateHasChanged = _state != stateLastScan;
+    stateLastScan = _state;
+    
+    bool needToResendRequest = _awaitResponse && MsTimerRelative(_repeatDelayMsTimer, _delayMs);
 
-    //Limit retries with a backoff delay
-    if (!MsTimerRelative(repeatDelayMsTimer, delayMs)) return DO_NOTHING;        //Don't retry within the delay time
-    delayMs <<= 1;                                                                 //Backoff (double) the delay time after each attempt
-    if (delayMs > MAX_REPEAT_DELAY_TIME_MS) delayMs = MAX_REPEAT_DELAY_TIME_MS;    //Don't go beyond a maximum
-    repeatDelayMsTimer = MsTimerCount;                                             //Start the delay timer
+    bool need = stateHasChanged || needToResendRequest;
+    
+    if (!need) return DO_NOTHING;
 
     //Send the renewal request
     *pSize = 0;
     int dest = DO_NOTHING;
-    if (DhcpLocalIp && elapsedTimeMs <  leaseTimeMs)
+    switch (_state)
     {
-        *pSize = sendRequest(pPacket, DHCPREQUEST, DhcpServerIp, DhcpLocalIp); //if within T2 then send request to the server - not broadcast
-        dest = UNICAST_DHCP;
+        case STATE_NONE:     clearAll();          return DO_NOTHING;
+        case STATE_DISCOVER: dest = BROADCAST;    break;
+        case STATE_SELECT:   dest = BROADCAST;    break;
+        case STATE_BOUND:                         return DO_NOTHING;
+        case STATE_RENEW:    dest = UNICAST_DHCP; break;
+        case STATE_REBIND:   dest = BROADCAST;    break;
+        case STATE_EXPIRED:
+            if (DhcpTrace) LogTimeF("DHCP lease has expired\r\n");
+            clearAll();
+            return DO_NOTHING;
+        default:
+            LogTimeF("DHCP -> unknown state %d", _state);
+            return DO_NOTHING;
     }
-    else
-    {
-        if (DhcpTrace) LogTimeF("DHCP lease has expired\r\n");
-        clearAll();
-        *pSize = sendRequest(pPacket, DHCPDISCOVER, 0, 0); //If outside T2 then start from scratch to do a full DHCP
-        dest = BROADCAST;
-    }
+    
+    *pSize = sendRequest(pPacket);
+    
     return ActionMakeFromDestAndTrace(dest, DhcpTrace && NetTraceStack);
 }
\ No newline at end of file