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

udp/dhcp/dhcp.c

Committer:
andrewboyson
Date:
2019-03-20
Revision:
132:db2174b36a6d
Parent:
128:79052cb4a41c
Child:
133:a37eb35a03f1

File content as of revision 132:db2174b36a6d:

#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#include      "log.h"
#include  "mstimer.h"
#include      "net.h"
#include   "action.h"
#include      "eth.h"
#include  "ip4addr.h"
#include      "mac.h"
#include      "udp.h"
#include "dnslabel.h"

bool DhcpTrace = false;

#define REQUEST   1
#define REPLY     2

#define DHCPDISCOVER 1
#define DHCPOFFER    2
#define DHCPREQUEST  3
#define DHCPDECLINE  4
#define DHCPACK      5
#define DHCPNAK      6
#define DHCPRELEASE  7
#define DHCPINFORM   8

#define ID 75648
#define COOKIE 0x63825363
#define HEADER_LENGTH 240

__packed struct header
{
    uint8_t  op;
    uint8_t  htype;
    uint8_t  hlen;
    uint8_t  hops;
    
    uint32_t xid;
    
    uint16_t secs;
    uint16_t flags;
    
    uint32_t ciaddr;
    uint32_t yiaddr;
    uint32_t siaddr;
    uint32_t giaddr;
    
    uint8_t  chaddr[16];
    
    char     legacy[192];
    
    uint32_t cookie;
};

#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

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;
}

static uint8_t  dhcpMessageType = 0;
uint32_t DhcpLeaseTime   = 0;
uint32_t DhcpServerIp    = 0;
uint32_t DhcpRouterIp    = 0;
uint32_t DhcpSubnetMask  = 0;
uint32_t DhcpNtpIp       = 0;
uint32_t DhcpRenewalT1   = 0;
uint32_t DhcpRenewalT2   = 0;
uint32_t DhcpBroadcastIp = 0;
uint32_t DhcpLocalIp     = 0;
uint32_t DhcpDnsServerIp = 0;
char     DhcpDomainName[DNS_MAX_LABEL_LENGTH+1];
char     DhcpHostName  [DNS_MAX_LABEL_LENGTH+1];

bool DhcpIpNeedsToBeRouted(uint32_t ip)
{   
    return ip & DhcpSubnetMask != DhcpBroadcastIp & DhcpSubnetMask;
}

static uint32_t readOption32(char** pp)
{
    uint32_t value = 0;
    char* p = *pp;
    int len = *++p;
    if (len >= 4)
    {
        value  = *++p << 24;
        value |= *++p << 16;
        value |= *++p <<  8;
        value |= *++p <<  0;
    }
    *pp += len + 1;
    return value;
}
static uint32_t readIp(char** pp)
{
    uint32_t value = 0;
    char* p = *pp;
    int len = *++p;
    if (len >= 4)
    {
        value  = *++p <<  0;
        value |= *++p <<  8;
        value |= *++p << 16;
        value |= *++p << 24;
    }
    *pp += len + 1;
    return value;
}
static void readString(char** pp, char* pText)
{
    char* p = *pp;
    int len = *++p;
    for (int i = 0; i < len; i++) pText[i] = *++p;
    *pp += len + 1;
}
static void readOptions(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 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
            default:
                if (DhcpTrace) LogTimeF("Ignoring option %d\r\n", *p);
                len = *++p;
                p += len;
                return;
        }
        p++;
    }
}
static void writeIp(uint8_t code, uint32_t value, char** pp)
{
    if (!value) return;
    
    char* p = *pp;
    
    *p++ = code;
    *p++ = 4;
    *p++ = (value & 0x000000FF) >>  0;
    *p++ = (value & 0x0000FF00) >>  8;
    *p++ = (value & 0x00FF0000) >> 16;
    *p++ = (value & 0xFF000000) >> 24;
    
    *pp += 6;
}
int sendRequest(void* pPacket, uint8_t code, uint32_t srvIp, uint32_t reqIp)
{
    
    switch (code)
    {
        case DHCPDISCOVER:
            if (DhcpTrace) LogTimeF("DHCP -> discover");
            break;
        case DHCPREQUEST:
            if (DhcpTrace) LogTimeF("DHCP -> request");
            break;
        default:
            LogTimeF("DHCP -> unknown message %d", code);
            break;
    }
    if (DhcpTrace)
    {
        Log(" server=" ); Ip4AddressLog(srvIp);
        Log(" request="); Ip4AddressLog(reqIp);
        Log(" local="  ); Ip4AddressLog(DhcpLocalIp);
        Log("\r\n");
    }
    
    bool broadcast = DhcpLocalIp == 0;
    uint16_t flags = 0;
    if (broadcast) flags |= 0x0080; //0x8000 on the net == 0x0080 in little endian
    struct header* pHeader = (struct header*)pPacket;
    pHeader->op       = REQUEST;
    pHeader->htype    = ETHERNET;
    pHeader->hlen     = 6;
    pHeader->hops     = 0;
    pHeader->xid      = ID;                      //Randomly chosed transaction id used to associate messages to responses
    pHeader->secs     = 0;                       //Seconds since started to boot
    pHeader->flags    = flags;                   //Broadcast (1) Unicast (0)
    pHeader->ciaddr   = DhcpLocalIp;             //'Client' address set by client or 0 if don't know address
    pHeader->yiaddr   = 0;                       //'Your' address returned by server
    pHeader->siaddr   = srvIp;                   //'Server' address to use if required
    pHeader->giaddr   = 0;                       //'Gateway' address
    memcpy(pHeader->chaddr, MacLocal, 6);        //'Client hardware' address. 6 bytes for ethernet
    memset(pHeader->legacy, 0, 192);             //BootP legacy fill with zeros
    pHeader->cookie   = NetToHost32(COOKIE);     //Magic cookie
    
    char* pOptions = (char*)pPacket + HEADER_LENGTH;
    char* p = pOptions;
    *p++ = 53;                        //Message code
    *p++ = 1;
    *p++ = code;
    
    writeIp(50, reqIp, &p);     //Requested IP
    writeIp(54, srvIp, &p);     //Server ip
    
    *p++ = 255;                       //End of options
    
    return HEADER_LENGTH + p - pOptions;
}
int DhcpHandleResponse(void (*traceback)(void), int sizeRx, void* pPacketRx, int* pSizeTx, void* pPacketTx)
{
    struct header* pHeaderRx = (struct header*)pPacketRx;

    uint8_t  op      = pHeaderRx->op;
    uint8_t  htype   = pHeaderRx->htype;
    uint8_t  hlen    = pHeaderRx->hlen;
    
    uint32_t xid     = pHeaderRx->xid;    //Randomly chosen transaction id used to associate messages to responses
    
    uint16_t secs    = NetToHost16(pHeaderRx->secs);
    uint16_t flags   = NetToHost16(pHeaderRx->flags);
    
    uint32_t yiaddr  = pHeaderRx->yiaddr;
    uint32_t siaddr  = pHeaderRx->siaddr;
    uint32_t cookie  = NetToHost32(pHeaderRx->cookie);
        
    if (op     != REPLY)                        return DO_NOTHING;
    if (htype  != ETHERNET)                     return DO_NOTHING;
    if (hlen   != 6)                            return DO_NOTHING;
    if (memcmp(pHeaderRx->chaddr, MacLocal, 6)) return DO_NOTHING;
    if (xid    != ID)                           return DO_NOTHING;
    if (cookie != COOKIE)                       return DO_NOTHING;
    
    char* pOptions = (char*)pPacketRx + HEADER_LENGTH;
    readOptions(sizeRx - HEADER_LENGTH, pOptions);
    
    switch (dhcpMessageType)
    {
        case DHCPOFFER:
            if (DhcpTrace) { LogTime("DHCP <- offer ip "); Ip4AddressLog(yiaddr); Log("\r\n"); }
            *pSizeTx = sendRequest(pPacketTx, DHCPREQUEST, siaddr, yiaddr);
            return BROADCAST;
        case DHCPACK:
            if (DhcpTrace) { LogTime("DHCP <- ack ip ");   Ip4AddressLog(yiaddr); Log("\r\n"); }
            DhcpLocalIp = yiaddr;
            elapsedLifeMsTimer = MsTimerCount;  //Start the life timer
            delayMs = MIN_REPEAT_DELAY_TIME_MS; //Set the delay time back to minimum
            break;
        case DHCPNAK:
            if (DhcpTrace) { LogTime("DHCP <- nack ip ");  Ip4AddressLog(yiaddr); Log("\r\n"); }
            break;
        default:
            LogTimeF("DHCP <- unknown message %d\r\n", dhcpMessageType);
            break;
    }
    return DO_NOTHING;
}

int DhcpPollForRequestToSend(void* pPacket, int* pSize)
{
    //Check if time to update
    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
    
    //Limit retries with a backoff delay
    if (!MsTimerInterval(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
    
    //Send the renewal request
    *pSize = 0;
    int dest = DO_NOTHING;
    if (DhcpLocalIp && elapsedTimeMs <  leaseTimeMs)
    {
        *pSize = sendRequest(pPacket, DHCPREQUEST, DhcpServerIp, DhcpLocalIp); //if within T2 then send request to the server - not broadcast
        dest = UNICAST_DHCP;
    }
    else
    {
        if (DhcpTrace) LogTimeF("DHCP lease has expired\r\n");
        DhcpLocalIp = 0;
        DhcpServerIp = 0;
        *pSize = sendRequest(pPacket, DHCPDISCOVER, 0, 0); //If outside T2 then start from scratch to do a full DHCP
        dest = BROADCAST;
    }
    return ActionMakeFromDestAndTrace(dest, DhcpTrace);
}