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

resolve/nr.c

Committer:
andrewboyson
Date:
2021-05-20
Revision:
200:5acbc41bf469
Parent:
193:47a953ab571b

File content as of revision 200:5acbc41bf469:

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

#include      "log.h"
#include  "mstimer.h"
#include      "net.h"
#include      "eth.h"
#include      "mac.h"
#include     "dhcp.h"
#include      "dns.h"
#include "dnsquery.h"
#include "dnslabel.h"
#include     "http.h"
#include  "ip6addr.h"
#include  "ip4addr.h"
#include       "nr.h"
#include   "nrtest.h"
#include      "ndp.h"
#include      "ar6.h"

bool Nr4Trace = false; //Do not use
bool NrTrace = false;

#define CACHE_TIMEOUT_MS 3600 * 1000
#define STALE_TIMEOUT_MS 1800 * 1000
#define EMPTY_TIMEOUT_MS  300 * 1000
#define REPLY_TIMEOUT_MS         100

#define RECORDS_COUNT 50

#define STATE_EMPTY        0
#define STATE_WANT         1
#define STATE_WAIT_FOR_ETH 2
#define STATE_WAIT_TIMEOUT 3
#define STATE_VALID        4

#define TODO_NONE              0
#define TODO_NAME_FROM_ADDRESS 1
#define TODO_ADDRESS_FROM_NAME 2

#define ADDR_TYPE_A    4
#define ADDR_TYPE_AAAA 6

struct record
{
    uint32_t replyMs; //Need this in addition to the ageMs for when refreshing an existing entry
    uint32_t ageMs;
    union
    {
        uint32_t A;
        uint8_t  AAAA[16];
        char     address[16];
    };
    uint8_t  addrType; //ADDR_TYPE_A (4) or ADDR_TYPE_AAAA (6)
    uint8_t  todo;
    uint8_t  state;
    uint8_t  dnsProtocol;
    uint16_t  ipProtocol;
    char     name[NR_NAME_MAX_LENGTH];
};
static struct record records[RECORDS_COUNT];

static void addrClear(const uint8_t addrType, char* ip)
{
    switch (addrType)
    {
        case ADDR_TYPE_A   : *(uint32_t*)ip = 0; break;
        case ADDR_TYPE_AAAA: Ip6AddrClear(ip);   break;
        default:             LogTimeF("NR - addrClear - Unknown addrType %d\r\n", addrType); break;
    }
}
static bool addrIsEmpty(const uint8_t addrType, const char* ip)
{
    switch (addrType)
    {
        case ADDR_TYPE_A   : return *(uint32_t*)ip == 0;
        case ADDR_TYPE_AAAA: return Ip6AddrIsEmpty(ip);
        default:             return LogTimeF("NR - addrIsEmpty - Unknown addrType %d\r\n", addrType);
    }
}
static int addrLog(uint8_t addrType, const char* ip)
{
    switch (addrType)
    {
        case ADDR_TYPE_A   : return Ip4AddrLog(*(uint32_t*)ip);
        case ADDR_TYPE_AAAA: return Ip6AddrLog(ip);
        default:             return LogTimeF("NR - addrLog - Unknown addrType %d\r\n", addrType);
    }
}

static bool addrIsSame(uint8_t addrType, const char* ipA, const char* ipB)
{
    switch (addrType)
    {
        case ADDR_TYPE_A   : return *(uint32_t*)ipA == *(uint32_t*)ipB;
        case ADDR_TYPE_AAAA: return Ip6AddrIsSame(ipA, ipB);
        default:             return LogTimeF("NR - addrIsSame - Unknown addrType %d\r\n", addrType);
    }
}

static void addrCopy(uint8_t addrType, char* ipTo, const char* ipFrom)
{
    switch (addrType)
    {
        case ADDR_TYPE_A   : *(uint32_t*)ipTo = *(uint32_t*)ipFrom; break;
        case ADDR_TYPE_AAAA: Ip6AddrCopy(ipTo, ipFrom);             break;
        default:             LogTimeF("NR - addrCopy - Unknown addrType %d\r\n", addrType); break;
    }
}

static int getExistingAddress(char* address, uint8_t addrType)
{
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (records[i].addrType != addrType) continue;
        if (addrIsSame(addrType, records[i].address, address)) return i;
    }
    return -1;
}
static int getExistingName(char* name, uint8_t addrType)
{
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (records[i].addrType != addrType) continue;
        if (DnsLabelIsSame(records[i].name, name)) return i;
    }
    return -1;
}
static int getOldest()
{
    int        iOldest = 0;
    uint32_t ageOldest = 0;
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) return i; //Found an empty slot so just return it
        uint32_t age = MsTimerCount - records[i].ageMs;
        if (age >= ageOldest)
        {
            ageOldest = age;
              iOldest = i;
        }
    }  
    return iOldest;                            //Otherwise return the oldest
}
static void makeRequestForNameFromAddress(uint8_t addrType, void* address )
{
    //Don't treat non ips
    if (addrIsEmpty(addrType, address)) return;
    int i;
    
    //If a valid record already exists then request an update
    i = getExistingAddress(address, addrType);
    if (i > -1)
    {
        if (records[i].state != STATE_VALID) return;
        if (records[i].name[0] == 0)
        {
            if (!MsTimerRelative(records[i].ageMs, EMPTY_TIMEOUT_MS)) return;
        }
        else
        {
            if (!MsTimerRelative(records[i].ageMs, STALE_TIMEOUT_MS)) return;
        }
        if (NrTrace)
        {
            LogTimeF("NR - record %2d - renew name of ", i);
            addrLog(addrType, address);
            Log("\r\n");
        }
        //Leave the address as is
        //Leave the name as is
        //Leave age as is
    }
    else
    {
        //If a record does not exist then find the first empty slot and add the IP and date
        i = getOldest();
        if (NrTrace)
        {
            LogTimeF("NR - record %2d - request name of ", i);
            addrLog(addrType, address);
            Log("\r\n");
        }
        addrCopy(addrType, records[i].address, address);  //Set the address
        records[i].name[0]  = 0;                          //Clear the name
        records[i].ageMs    = MsTimerCount;               //Start age
    }
    records[i].todo        = TODO_NAME_FROM_ADDRESS;
    records[i].addrType    = addrType;
    records[i].state       = STATE_WANT;
    records[i].replyMs     = MsTimerCount;
    records[i].ipProtocol  = ETH_NONE;
    records[i].dnsProtocol = DNS_PROTOCOL_NONE;
}
static void makeRequestForAddressFromName(char* name, uint8_t addrType)
{
    //Don't treat non names
    if (!name[0]) return;
    int i;
        
    //If a valid record already exists then request an update
    i = getExistingName(name, addrType);
    if (i > -1)
    {
        if (records[i].state != STATE_VALID) return;
        if (addrIsEmpty(records[i].addrType, records[i].address))
        {
            if (!MsTimerRelative(records[i].ageMs, EMPTY_TIMEOUT_MS)) return;
        }
        else
        {
            if (!MsTimerRelative(records[i].ageMs, STALE_TIMEOUT_MS)) return;
        }
        if (NrTrace)
        {
            if (addrType == ADDR_TYPE_A) LogTimeF("NR - record %2d - renew A of %s\r\n", i, name);
            else                         LogTimeF("NR - record %2d - renew AAAA of %s\r\n", i, name);
        }
        //Leave name as is
        //Leave the address as is
        //Leave age as is
    }
    else
    {   
        //If a record does not exist then find the first empty slot and add the name and date
        i = getOldest();
        if (NrTrace)
        {
            if (addrType == ADDR_TYPE_A) LogTimeF("NR - record %2d - request A of %s\r\n", i, name);
            else                         LogTimeF("NR - record %2d - request AAAA of %s\r\n", i, name);
        }
        strncpy(records[i].name, name, NR_NAME_MAX_LENGTH); //Set the name
        records[i].name[NR_NAME_MAX_LENGTH - 1] = 0;
        addrClear(addrType, records[i].address);            //Clear the address
        records[i].ageMs  = MsTimerCount;                   //Start age
    }
    records[i].todo        = TODO_ADDRESS_FROM_NAME;
    records[i].addrType    = addrType;
    records[i].state       = STATE_WANT;
    records[i].replyMs     = MsTimerCount;
    records[i].ipProtocol  = ETH_NONE;
    records[i].dnsProtocol = DNS_PROTOCOL_NONE;
}
static void addEntry(uint8_t addrType, void* address, char* name, int dnsProtocol, int ipProtocol)
{

    //Ignore records which do not have both address and name    
    if (addrIsEmpty(addrType, address) || name == 0 || name[0] == 0)
    {
        if (NrTrace) LogTimeF("NR - ignoring invalid entry\r\n");
        return;
    }
    //Ignore records with the name 'UNKNOWN'
    if (strcmp(name, "UNKNOWN") == 0) return;
    
    //Delete any existing records linked to the new entry
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (records[i].addrType != addrType) continue;
        bool sameAddress = addrIsSame(addrType, records[i].address, address);
        bool sameName = DnsLabelIsSame(records[i].name, name);
        bool noAddress = addrIsEmpty(addrType, records[i].address);
        bool requesting = records[i].state != STATE_VALID;
        if (sameAddress || (sameName && (noAddress || requesting))) records[i].state = STATE_EMPTY;
    }

    //Add the new entry
    int i = getOldest();
    if (NrTrace)
    {
        LogTimeF("NR - record %2d - received ", i);
        EthProtocolLog(ipProtocol);
        Log(" ");
        DnsProtocolLog(dnsProtocol);
        Log(" ");
        addrLog(addrType, address);
        Log(" == '");
        Log(name);
        Log("'\r\n");
    }
    records[i].todo        = TODO_NONE;
    records[i].ageMs       = MsTimerCount;
    records[i].addrType    = addrType;
    addrCopy(addrType, records[i].address, address);
    records[i].dnsProtocol = dnsProtocol;
    records[i].ipProtocol  = ipProtocol;
    records[i].state       = STATE_VALID;
    strncpy(records[i].name, name, NR_NAME_MAX_LENGTH);
    records[i].name[NR_NAME_MAX_LENGTH - 1] = 0;
}
static void addressToName(uint8_t addrType, void* address, char* name)
{
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (addrIsSame(addrType, records[i].address, address))
        {
            strcpy(name, records[i].name);
            return;
        }
    }
    name[0] = 0;
}
static void nameToAddress(char* name, uint8_t addrType, void* address)
{
    uint32_t newest = 0xFFFFFFFF;
    addrClear(addrType, address);
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (records[i].addrType != addrType) continue;
        if(addrIsEmpty(records[i].addrType, records[i].address)) continue;
        if (!DnsLabelIsSame(records[i].name, name)) continue;
        uint32_t age = MsTimerCount - records[i].ageMs;
        if (age <= newest)
        {
            newest = age;
            addrCopy(addrType, address, records[i].address);
        }
    }
}
void NrMakeRequestForNameFromAddress6(char*   pAddress) { makeRequestForNameFromAddress(ADDR_TYPE_AAAA, pAddress); }
void NrMakeRequestForNameFromAddress4(uint32_t address) { makeRequestForNameFromAddress(ADDR_TYPE_A,    &address); }
void NrMakeRequestForAddress6FromName(char* name)       { makeRequestForAddressFromName(name, ADDR_TYPE_AAAA); }
void NrMakeRequestForAddress4FromName(char* name)       { makeRequestForAddressFromName(name, ADDR_TYPE_A   ); }
void NrAddAddress6(char*   pAddress, char* name, int dnsProtocol) { addEntry(ADDR_TYPE_AAAA, pAddress, name, dnsProtocol, EthProtocol); }
void NrAddAddress4(uint32_t address, char* name, int dnsProtocol) { addEntry(ADDR_TYPE_A,    &address, name, dnsProtocol, EthProtocol); }
void NrNameToAddress6(char* name,      char* address) { nameToAddress(name, ADDR_TYPE_AAAA, address); }
void NrNameToAddress4(char* name, uint32_t* pAddress) { nameToAddress(name, ADDR_TYPE_A,   pAddress); }
void NrAddress6ToName(char*   pAddress, char* name)   { addressToName(ADDR_TYPE_AAAA, pAddress, name); }
void NrAddress4ToName(uint32_t address, char* name)   { addressToName(ADDR_TYPE_A,    &address, name); }

static char letterFromStateAndProtocol(uint8_t dnsState, uint8_t dnsProtocol, uint16_t ipProtocol)
{
    switch (dnsState)
    {
        case STATE_WANT:         return '}';
        case STATE_WAIT_FOR_ETH: return ']';
        case STATE_WAIT_TIMEOUT: return '>';
        
        case STATE_VALID:
            switch (dnsProtocol)
            {
                case DNS_PROTOCOL_UDNS:  if (ipProtocol == ETH_IPV4) return 'd' ; else return 'D';
                case DNS_PROTOCOL_MDNS:  if (ipProtocol == ETH_IPV4) return 'm' ; else return 'M';
                case DNS_PROTOCOL_LLMNR: if (ipProtocol == ETH_IPV4) return 'l' ; else return 'L';
                case DNS_PROTOCOL_NONE:  return '-';
                default:                 return '?';
            }
        default:                         return '~';
    }
}
void NrSendAjax()
{
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (!addrIsEmpty(records[i].addrType, records[i].address) || records[i].name[0])
        {
            HttpAddByteAsHex(i);
            HttpAddChar('\t');
            HttpAddInt32AsHex(MsTimerCount - records[i].ageMs);
            HttpAddChar('\t');
            HttpAddNibbleAsHex(records[i].addrType);
            HttpAddChar('\t');
            switch (records[i].addrType)
            {
                case ADDR_TYPE_A:
                {
                    HttpAddInt32AsHex(records[i].A);
                    break;
                }
                case ADDR_TYPE_AAAA:
                    for (int b = 0; b < 16; b++) HttpAddByteAsHex(records[i].AAAA[b]);
                    break;
            }
            HttpAddChar('\t');
            HttpAddChar(letterFromStateAndProtocol(records[i].state, records[i].dnsProtocol, records[i].ipProtocol));
            HttpAddChar('\t');
            HttpAddText(records[i].name);
            HttpAddChar('\n');
        }
    }
}
static void clearCache(struct record* pr)
{
    if (MsTimerRelative(pr->ageMs, CACHE_TIMEOUT_MS)) pr->state = STATE_EMPTY;
}
static void queryNameFromIp(struct record* pr)
{
    if (addrIsEmpty(pr->addrType, pr->address))
    {
        LogTimeF("NR - record %2d - queryNameFromIp has no address\r\n", pr - records);
        return;
    }
    if (NrTrace)
    {
        LogTimeF("NR - record %2d - send ", pr - records);
        EthProtocolLog(pr->ipProtocol);
        Log(" ");
        DnsProtocolLog(pr->dnsProtocol);
        Log(" request for name of ");
        addrLog(pr->addrType, pr->address);
        Log("\r\n");
    }
    if (pr->addrType == ADDR_TYPE_A) 
    {
        //uint32_t address4 = addrToIp4(pr->address);
        DnsQueryNameFromIp4(pr->A, pr->dnsProtocol, pr->ipProtocol);
    }
    else
    {
        DnsQueryNameFromIp6(pr->address, pr->dnsProtocol, pr->ipProtocol);
    }
}
static void queryIpFromName(struct record* pr)
{
    if (NrTrace)
    {
        LogTimeF("NR - record %2d - send ", pr - records);
        EthProtocolLog(pr->ipProtocol);
        Log(" ");
        DnsProtocolLog(pr->dnsProtocol);
        if (pr->addrType == ADDR_TYPE_A) Log(" request for A of name '");
        else                             Log(" request for AAAA of name '");
        Log(pr->name);
        Log("'\r\n");
    }
    if (pr->addrType == ADDR_TYPE_A) DnsQueryIp4FromName(pr->name, pr->dnsProtocol, pr->ipProtocol);
    else                             DnsQueryIp6FromName(pr->name, pr->dnsProtocol, pr->ipProtocol);
}
static bool getIsExternal(struct record* pr)
{
    switch(pr->todo)
    {
        case TODO_NAME_FROM_ADDRESS:
            
            if (pr->addrType == ADDR_TYPE_AAAA)
            {
                return Ip6AddrIsExternal((char*)pr->AAAA);
            }
            else
            {
                return Ip4AddrIsExternal(pr->A);
            }
        case TODO_ADDRESS_FROM_NAME:
            return DnsLabelIsExternal(pr->name);
        default:
            LogTimeF("NR - getIsExternal - undefined todo '%d'\r\n", pr->todo);
            return false;
    }
}
static bool protocolIsAvailable(struct record* pr)
{
    bool isExternal = getIsExternal(pr);
    switch(pr->dnsProtocol)
    {
        case DNS_PROTOCOL_MDNS: return !isExternal;
        case DNS_PROTOCOL_LLMNR: return !isExternal;
        case DNS_PROTOCOL_UDNS:
            if (pr->ipProtocol == ETH_IPV6)
            {
                if (Ip6AddrIsEmpty(NdpDnsServer)) return false; //No DNS server so not ok
                if (isExternal) return true; //External and have DNS server so ok
                return false; //Internal but have no DHCP6 domain name so not ok
            }
            else //ETH_IPV4
            {
                if (DhcpDnsServerIp == 0) return false; //No DNS server so not ok
                if (isExternal) return true; //External and have DNS server so ok
                return DhcpDomainName[0] != 0; //Internal and have domain name so ok
            }
        case DNS_PROTOCOL_NONE: return true; //No protocol is valid as it designates 'not found'
        default:                return false;
    }
}
static void makeNextProtocol(struct record* pr)
{
    //If current protocol is empty or unknown then return with the first.
    switch (pr->ipProtocol)
    {
        case ETH_IPV6: break;
        case ETH_IPV4: break;
        default:
            pr->ipProtocol  = ETH_IPV6;
            pr->dnsProtocol = DNS_PROTOCOL_MDNS;
            return;
    }
    switch (pr->dnsProtocol)
    {
        case DNS_PROTOCOL_MDNS:  break;
        case DNS_PROTOCOL_LLMNR: break;
        case DNS_PROTOCOL_UDNS:  break;
        default:
            pr->ipProtocol  = ETH_IPV6;
            pr->dnsProtocol = DNS_PROTOCOL_MDNS;
            return;

    }
    switch(pr->dnsProtocol)
    {
        case DNS_PROTOCOL_MDNS: 
            if (pr->ipProtocol == ETH_IPV6)
            {
                pr->ipProtocol = ETH_IPV4;
            }
            else
            {
                pr->ipProtocol = ETH_IPV6;
                pr->dnsProtocol = DNS_PROTOCOL_LLMNR;
            }
            break;
        case DNS_PROTOCOL_LLMNR:
            if (pr->ipProtocol == ETH_IPV6)
            {
                pr->ipProtocol = ETH_IPV4;
            }
            else
            {
                pr->ipProtocol = ETH_IPV6;
                pr->dnsProtocol = DNS_PROTOCOL_UDNS;
            }
            break;
        case DNS_PROTOCOL_UDNS:
            if (pr->ipProtocol == ETH_IPV6)
            {
                pr->ipProtocol = ETH_IPV4;
            }
            else
            {
                pr->ipProtocol  = ETH_NONE;
                pr->dnsProtocol = DNS_PROTOCOL_NONE;
            }
            break;
    }
}

static void sendRequest(struct record* pr)
{
    //if (DnsQueryIsBusy) return;
    
    switch (pr->state)
    {
        case STATE_WANT:
            makeNextProtocol(pr);
            while (!protocolIsAvailable(pr)) makeNextProtocol(pr);
            if (!pr->dnsProtocol || !pr->ipProtocol) //No more protocols to try so resolution has failed
            {
                if (pr->todo == TODO_NAME_FROM_ADDRESS)
                {
                    if (NrTrace)
                    {
                        LogTimeF("NR - record %2d - request for name of ", pr - records);
                        addrLog(pr->addrType, pr->address);
                        Log(" has timed out\r\n");
                    }
                    pr->name[0] = 0;
                }
                if (pr->todo == TODO_ADDRESS_FROM_NAME)
                {
                    if (NrTrace)
                    {
                        LogTimeF("NR - record %2d - request for address of '", pr - records);
                        Log(pr->name);
                        Log("' has timed out\r\n");
                        Log("\r\n");
                    }
                    addrClear(pr->addrType, pr->address);
                }
                pr->todo   = TODO_NONE;
                pr->state  = STATE_VALID;
                pr->ageMs  = MsTimerCount;
            }
            else
            {
                pr->state  = STATE_WAIT_FOR_ETH;
            }
            break;
        case STATE_WAIT_FOR_ETH:
            if (!DnsQueryIsBusy)
            {   
                switch (pr->todo)
                {
                    case TODO_NAME_FROM_ADDRESS: queryNameFromIp(pr); break;
                    case TODO_ADDRESS_FROM_NAME: queryIpFromName(pr); break;
                }
                pr->state = STATE_WAIT_TIMEOUT;
                pr->replyMs = MsTimerCount;
            }
            break;
        case STATE_WAIT_TIMEOUT:
            if (MsTimerRelative(pr->replyMs, REPLY_TIMEOUT_MS))
            {
                pr->state = STATE_WANT;
            }
            break;
        default:
            break;
    }
}
void NrMain()
{
    static int i = -1;
    i++;
    if (i >= RECORDS_COUNT) i = 0;
    
    struct record* pr = &records[i];
    
    clearCache  (pr);
    sendRequest (pr);
    
    NrTestMain();
}
void NrInit()
{
    for (int i = 0; i < RECORDS_COUNT; i++) records[i].state = STATE_EMPTY;
}