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:
172:9bc3c7b2cca1
Child:
176:7eb916c22084
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/resolve/nr.c	Wed Dec 16 17:33:22 2020 +0000
@@ -0,0 +1,618 @@
+#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  "ip6addr.h"
+#include     "dhcp.h"
+#include      "dns.h"
+#include "dnsquery.h"
+#include "dnslabel.h"
+#include     "http.h"
+
+bool Nr4Trace = false; //Do not use
+bool NrTrace = false;
+
+#define NAME_MAX_LENGTH     20
+#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_ADDRESS4_FROM_NAME 2
+#define TODO_ADDRESS6_FROM_NAME 3
+
+struct record
+{
+    uint32_t replyMs;
+    uint32_t ageMs;
+    char     address[16];
+    uint8_t  todo;
+    uint8_t  state;
+    uint8_t  dnsProtocol;
+    uint16_t  ipProtocol;
+    char     name[NAME_MAX_LENGTH];
+};
+static struct record records[RECORDS_COUNT];
+
+static int getExistingAddress(char* address)
+{
+    for (int i = 0; i < RECORDS_COUNT; i++)
+    {
+        if (records[i].state == STATE_EMPTY) continue;
+        if (Ip6AddrIsSame(records[i].address, address)) return i;
+    }
+    return -1;
+}
+static int getExistingName(char* name)
+{
+    for (int i = 0; i < RECORDS_COUNT; i++)
+    {
+        if (records[i].state == STATE_EMPTY) continue;
+        if (DnsLabelIsSame(records[i].name, name)) return i;
+    }
+    return -1;
+}
+static int getNameOnly(char* name)
+{
+    for (int i = 0; i < RECORDS_COUNT; i++)
+    {
+        if (records[i].state == STATE_EMPTY) continue;
+        if (!Ip6AddrIsEmpty(records[i].address)) continue;
+        if (DnsLabelIsSame(records[i].name, name)) return i;
+    }
+    return -1;
+}
+static int getIpOnly(char* address)
+{
+    for (int i = 0; i < RECORDS_COUNT; i++)
+    {
+        if (records[i].state == STATE_EMPTY) continue;
+        if (records[i].name[0] != 0) continue;
+        if (Ip6AddrIsSame(records[i].address, address)) 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(char* address)
+{
+    //Don't treat non ips
+    if (Ip6AddrIsEmpty(address)) return;
+    int i;
+    
+    //If a valid record already exists then request an update
+    i = getExistingAddress(address);
+    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 - renew name of ");
+            Ip6AddrLog(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
+        if (NrTrace)
+        {
+            LogTimeF("NR - request name of ");
+            Ip6AddrLog(address);
+            Log("\r\n");
+        }
+        i = getOldest();
+        Ip6AddrCopy(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].state       = STATE_WANT;
+    records[i].replyMs     = MsTimerCount;
+    records[i].ipProtocol  = ETH_NONE;
+    records[i].dnsProtocol = DNS_PROTOCOL_NONE;
+}
+static void makeRequestForAddressFromName(char* name, int todo)
+{
+    //Don't treat non names
+    if (!name[0]) return;
+    int i;
+        
+    //If a valid record already exists then request an update
+    i = getExistingName(name);
+    if (i > -1)
+    {
+        if (records[i].state != STATE_VALID) return;
+        if (Ip6AddrIsEmpty(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 (todo == TODO_ADDRESS4_FROM_NAME) LogTimeF("NR - renew A of %s\r\n", name);
+            else                                 LogTimeF("NR - renew AAAA of %s\r\n", 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
+        if (NrTrace)
+        {
+            if (todo == TODO_ADDRESS4_FROM_NAME) LogTimeF("NR - request A of %s\r\n", name);
+            else                                 LogTimeF("NR - request AAAA of %s\r\n", name);
+        }
+        i = getOldest();
+        strncpy(records[i].name, name, NAME_MAX_LENGTH); //Set the name
+        records[i].name[NAME_MAX_LENGTH - 1] = 0;
+        Ip6AddrClear(records[i].address);             //Clear the address
+        records[i].ageMs  = MsTimerCount;                //Start age
+    }
+    records[i].todo        = todo;
+    records[i].state       = STATE_WANT;
+    records[i].replyMs     = MsTimerCount;
+    records[i].ipProtocol  = ETH_NONE;
+    records[i].dnsProtocol = DNS_PROTOCOL_NONE;
+}
+static void updateRecord(int i, char* address, char* name, int dnsProtocol, int ipProtocol)
+{
+    records[i].todo        = TODO_NONE;
+    records[i].ageMs       = MsTimerCount;
+    Ip6AddrCopy(records[i].address, address);
+    records[i].dnsProtocol = dnsProtocol;
+    records[i].ipProtocol  = ipProtocol;
+    records[i].state       = STATE_VALID;
+    strncpy(records[i].name, name, NAME_MAX_LENGTH);
+    records[i].name[NAME_MAX_LENGTH - 1] = 0;
+}
+void addEntry(char* address, char* name, int dnsProtocol, int ipProtocol)
+{
+    /*
+    A number of situations may need to be handled:
+    - An existing address with the same name         ; the usual situation                             : just reset the elapsed time and return
+    - An existing address with no or a different name; usual if we are resolving an address            : add or update the address of this record
+    - Same name with an empty address                ; usual if we are resolving a name                : add the address to this record
+    - Same name with another address                 ; normal situation                                : do nothing
+    - No existing address or name                    ; usual if another device has done a reolution    : add a new record and return
+    
+    Quite often we will simultaneously attempt to resolve both the name and the address of the same device.
+    When this happens there will be two entries: one with the name and an empty address; the other with the address but and an empty name.
+    In this case we first add the name to the existing address but also check for the name with an empty address and delete it.
+    */
+    
+    int i;
+    
+    //Print what is being handled
+    if (NrTrace)
+    {
+        LogTimeF("NR - received ");
+        Ip6AddrLog(address);
+        Log(" == '");
+        Log(name);
+        Log("'\r\n");
+    }
+
+    //Ignore records which do not have both address and name    
+    if (Ip6AddrIsEmpty(address) || name == 0 || name[0] == 0)
+    {
+        if (NrTrace) LogTimeF("NR -- ignoring invalid entry\r\n");
+        return;
+    }
+    
+    //Get existing address and, if found, add it then clear any name only entries
+    i = getExistingAddress(address);
+    if (i >= 0)
+    {
+        if (NrTrace)
+        {
+            if (DnsLabelIsSame(name, records[i].name))
+            {
+                LogTimeF("NR -- record %d - refresh existing entry\r\n", i);
+            }
+            else
+            {
+                LogTimeF("NR -- record %d - update entry name from '%s' to '%s'\r\n", i, records[i].name, name);
+            }
+        }
+        updateRecord(i, address, name, dnsProtocol, ipProtocol);
+        
+        i = getNameOnly(name);
+        if (i >= 0)
+        {
+            if (NrTrace) LogTimeF("NR -- record %d - clear name '%s' with no address\r\n", i, name);
+            records[i].state = STATE_EMPTY;
+        }
+        return;
+    }
+    
+    //Get name only entry and, if found, add it
+    i = getNameOnly(name);
+    if (i >= 0)
+    {
+        if (NrTrace)
+        {
+            LogTimeF("NR -- record %d - add address ", i);
+            Ip6AddrLog(address);
+            Log("\r\n");
+        }
+        updateRecord(i, address, name, dnsProtocol, ipProtocol);
+        return;
+    }
+    
+    //No other entry exists so just add it to the next available space
+    i = getOldest();
+    if (NrTrace)
+    {
+        LogTimeF("NR -- record %d - add entry\r\n", i);
+    }
+    updateRecord(i, address, name, dnsProtocol, ipProtocol);
+}
+static void addressToName(char* address, char* name)
+{
+    for (int i = 0; i < RECORDS_COUNT; i++)
+    {
+        if (records[i].state == STATE_EMPTY) continue;
+        if (Ip6AddrIsSame(records[i].address, address))
+        {
+            strcpy(name, records[i].name);
+            return;
+        }
+    }
+    name[0] = 0;
+}
+static void nameToAddress(char* name, char* address)
+{
+    uint32_t newest = 0xFFFFFFFF;
+    Ip6AddrClear(address);
+    for (int i = 0; i < RECORDS_COUNT; i++)
+    {
+        if (records[i].state == STATE_EMPTY) continue;
+        if(Ip6AddrIsEmpty(records[i].address)) continue;
+        if (!DnsLabelIsSame(records[i].name, name)) continue;
+        uint32_t age = MsTimerCount - records[i].ageMs;
+        if (age <= newest)
+        {
+            newest = age;
+            Ip6AddrCopy(address, records[i].address);
+        }
+    }
+}
+void NrMakeRequestForNameFromAddress6(char* address6)
+{
+    makeRequestForNameFromAddress(address6);
+}
+void NrMakeRequestForNameFromAddress4(uint32_t address4)
+{
+    char address6[16];
+    Ip6AddrFromIp4(address6, address4);
+    makeRequestForNameFromAddress(address6);
+}
+void NrMakeRequestForAddress6FromName(char* name)
+{
+    makeRequestForAddressFromName(name, TODO_ADDRESS6_FROM_NAME);
+}
+void NrMakeRequestForAddress4FromName(char* name)
+{
+    makeRequestForAddressFromName(name, TODO_ADDRESS4_FROM_NAME);
+}
+void NrAddAddress6(char* address, char* name, int dnsProtocol)
+{
+    addEntry(address, name, dnsProtocol, EthProtocol);
+}
+void NrAddAddress4(uint32_t address4, char* name, int dnsProtocol)
+{
+    char address6[16];
+    Ip6AddrFromIp4(address6, address4);
+    addEntry(address6, name, dnsProtocol, EthProtocol);
+}
+void NrNameToAddress6(char* name, char* address)
+{
+    nameToAddress(name, address);
+}
+void NrNameToAddress4(char* name, uint32_t* pAddress4)
+{
+    char address6[16];
+    nameToAddress(name, address6);
+    *pAddress4 = Ip6AddrToIp4(address6);
+}
+void NrAddress6ToName(char* address, char* name)
+{
+    addressToName(address, name);
+}
+void NrAddress4ToName(uint32_t address4, char* name)
+{
+    char address6[16];
+    Ip6AddrFromIp4(address6, address4);
+    addressToName(address6, 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 NrSendHttp()
+{
+    for (int i = 0; i < RECORDS_COUNT; i++)
+    {
+        if (records[i].state == STATE_EMPTY) continue;
+        if (!Ip6AddrIsEmpty(records[i].address) || records[i].name[0])
+        {
+            HttpAddF("%4u ", (MsTimerCount - records[i].ageMs) / 1000 / 60);
+            
+            int ipLen;
+            ipLen = Ip6AddrHttp(records[i].address);
+            HttpAddFillChar(' ', 40 - ipLen);
+
+            HttpAddChar(letterFromStateAndProtocol(records[i].state, records[i].dnsProtocol, records[i].ipProtocol));
+            
+            HttpAddChar(' ');
+            
+            HttpAddText(records[i].name);
+            
+            HttpAddChar('\r');
+            HttpAddChar('\n');
+        }
+    }
+}
+void NrSendAjax()
+{
+    for (int i = 0; i < RECORDS_COUNT; i++)
+    {
+        if (records[i].state == STATE_EMPTY) continue;
+        if (!Ip6AddrIsEmpty(records[i].address) || records[i].name[0])
+        {
+            HttpAddByteAsHex(i);
+            HttpAddChar('\t');
+            HttpAddInt32AsHex(MsTimerCount - records[i].ageMs);
+            HttpAddChar('\t');
+            for (int b = 0; b < 16; b++) HttpAddByteAsHex(records[i].address[b]);
+            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 (NrTrace)
+    {
+        LogTime("NR -- send ");
+        EthProtocolLog(pr->ipProtocol);
+        Log(" ");
+        DnsProtocolLog(pr->dnsProtocol);
+        Log(" request for name of ");
+        Ip6AddrLog(pr->address);
+        Log("\r\n");
+    }
+    if (Ip6AddrIsIp4(pr->address)) 
+    {
+        uint32_t address4 = Ip6AddrToIp4(pr->address);
+        DnsQueryNameFromIp4(address4, pr->dnsProtocol, pr->ipProtocol);
+    }
+    else
+    {
+        DnsQueryNameFromIp6(pr->address, pr->dnsProtocol, pr->ipProtocol);
+    }
+}
+static void queryIpFromName(struct record* pr, int todo)
+{
+    if (NrTrace)
+    {
+        LogTime("NR -- send ");
+        EthProtocolLog(pr->ipProtocol);
+        Log(" ");
+        DnsProtocolLog(pr->dnsProtocol);
+        if (todo == TODO_ADDRESS4_FROM_NAME) Log(" request for A of name '");
+        else                                 Log(" request for AAAA of name '");
+        Log(pr->name);
+        Log("'\r\n");
+    }
+    if (todo == TODO_ADDRESS4_FROM_NAME) DnsQueryIp4FromName(pr->name, pr->dnsProtocol, pr->ipProtocol);
+    else                                 DnsQueryIp6FromName(pr->name, pr->dnsProtocol, pr->ipProtocol);
+}
+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);
+            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 - request for name of ");
+                        Ip6AddrLog(pr->address);
+                        Log(" has timed out\r\n");
+                    }
+                    pr->name[0] = 0;
+                }
+                if (pr->todo == TODO_ADDRESS4_FROM_NAME || pr->todo == TODO_ADDRESS6_FROM_NAME)
+                {
+                    if (NrTrace)
+                    {
+                        LogTimeF("NR - request for address of '");
+                        Log(pr->name);
+                        Log("' has timed out\r\n");
+                        Log("\r\n");
+                    }
+                    Ip6AddrClear(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_ADDRESS4_FROM_NAME: queryIpFromName(pr, TODO_ADDRESS4_FROM_NAME); break;
+                    case TODO_ADDRESS6_FROM_NAME: queryIpFromName(pr, TODO_ADDRESS6_FROM_NAME); 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);
+}
+void NrInit()
+{
+    for (int i = 0; i < RECORDS_COUNT; i++) records[i].state = STATE_EMPTY;
+}