Andrew Boyson / net

Dependents:   oldheating gps motorhome heating

resolve/nr6.c

Committer:
andrewboyson
Date:
2020-12-12
Revision:
171:f708d6776752
Parent:
170:96c637dc3f52

File content as of revision 171:f708d6776752:

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

#include      "log.h"
#include  "mstimer.h"
#include      "net.h"
#include      "mac.h"
#include  "ip6addr.h"
#include     "dhcp.h"
#include      "dns.h"
#include "dnsquery.h"
#include "dnslabel.h"
#include     "http.h"

bool Nr6Trace = 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 20

#define STATE_EMPTY 0
#define STATE_WANT  1
#define STATE_SENT  2
#define STATE_VALID 3

#define TODO_NONE          0
#define TODO_NAME_FROM_IP  1
#define TODO_IP4_FROM_NAME 2
#define TODO_IP6_FROM_NAME 3

struct record
{
    uint32_t replyMs;
    uint32_t ageMs;
    char     ip[16];
    uint8_t  todo;
    uint8_t  state;
    uint8_t  dnsProtocol;
    uint8_t   ipProtocol;
    char     name[NAME_MAX_LENGTH];
};
static struct record records[RECORDS_COUNT];

static int getExistingIp(char* ip)
{
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (Ip6AddressIsSame(records[i].ip, ip)) 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 (!Ip6AddressIsEmpty(records[i].ip)) continue;
        if (DnsLabelIsSame(records[i].name, name)) return i;
    }
    return -1;
}
static int getIpOnly(char* ip)
{
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (records[i].name[0] != 0) continue;
        if (Ip6AddressIsSame(records[i].ip, ip)) 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 makeRequestForNameFromIp(char* ip)
{
    //Don't treat non ips
    if (!ip[0]) return;
    int i;
    
    //If a record already exists then request an update
    i = getExistingIp(ip);
    if (i > -1)
    {
        if (records[i].state == STATE_WANT || records[i].state == STATE_SENT) 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 (Nr6Trace)
        {
            LogTimeF("NR - renew name of ");
            Ip6AddressLog(ip);
            Log("\r\n");
        }
        //Leave the ip 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 (Nr6Trace)
        {
            LogTimeF("NR - request name of ");
            Ip6AddressLog(ip);
            Log("\r\n");
        }
        i = getOldest();
        Ip6AddressCopy(records[i].ip, ip);  //Set the ip
        records[i].name[0]  = 0;            //Clear the name
        records[i].ageMs    = MsTimerCount; //Start age
    }
    records[i].todo        = TODO_NAME_FROM_IP;
    records[i].state       = STATE_WANT;
    records[i].dnsProtocol = DnsGetNextProtocol(DNS_PROTOCOL_NONE);
}
static void makeRequestForIpFromName(char* name, int todo)
{
    //Don't treat non names
    if (!name[0]) return;
    int i;
        
    //If a record already exists then request an update
    i = getExistingName(name);
    if (i > -1)
    {
        if (records[i].state == STATE_WANT || records[i].state == STATE_SENT) return;
        if (Ip6AddressIsEmpty(records[i].ip))
        {
            if (!MsTimerRelative(records[i].ageMs, EMPTY_TIMEOUT_MS)) return;
        }
        else
        {
            if (!MsTimerRelative(records[i].ageMs, STALE_TIMEOUT_MS)) return;
        }
        if (Nr6Trace)
        {
            LogTimeF("NR - renew IPv6 of %s\r\n", name);
        }
        //Leave name as is
        //Leave the ip 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 (Nr6Trace)
        {
            LogTimeF("NR - request IPv6 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;
        Ip6AddressClear(records[i].ip);                  //Clear the ip
        records[i].ageMs  = MsTimerCount;                //Start age
    }
    records[i].todo        = todo;
    records[i].state       = STATE_WANT;
    records[i].dnsProtocol = DnsGetNextProtocol(DNS_PROTOCOL_NONE);
}
static void updateIpRecord(int i, char* ip, char* name, int dnsProtocol)
{
    records[i].todo        = TODO_NONE;
    records[i].ageMs       = MsTimerCount;
    Ip6AddressCopy(records[i].ip, ip);
    records[i].dnsProtocol = dnsProtocol;
    records[i].state       = STATE_VALID;
    strncpy(records[i].name, name, NAME_MAX_LENGTH);
    records[i].name[NAME_MAX_LENGTH - 1] = 0;
}
void addIpRecord(char* ip, char* name, int dnsProtocol)
{
    /*
    A number of situations may need to be handled:
    - An existing ip with the same name         ; the usual situation                             : just reset the elapsed time and return
    - An existing ip with no or a different name; usual if we are resolving an ip                 : add or update the ip of this record
    - Same name with an empty ip                ; usual if we are resolving a name                : add the ip to this record
    - Same name with another ip                 ; normal situation                                : do nothing
    - No existing ip 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 ip of the same device.
    When this happens there will be two entriesone with the ame and an empty ip; the othert with the ip but and an empty name.
    In this case we first add the name to the existing ip but also check for the name with an empty ip and delete it.
    */
    
    int i;
    
    //Print what is being handled
    if (Nr6Trace)
    {
        LogTimeF("NR - received ");
        Ip6AddressLog(ip);
        Log(" == '");
        Log(name);
        Log("'\r\n");
    }

    //Ignore records which do not have both ip and name    
    if (Ip6AddressIsEmpty(ip) || name == 0 || name[0] == 0)
    {
        if (Nr6Trace) LogTimeF("NR - ignoring invalid entry\r\n");
        return;
    }
    
    //Get existing ip and, if found, add it then clear any name only entries
    i = getExistingIp(ip);
    if (i >= 0)
    {
        if (Nr6Trace)
        {
            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);
            }
        }
        updateIpRecord(i, ip, name, dnsProtocol);
        
        i = getNameOnly(name);
        if (i >= 0)
        {
            if (Nr6Trace) LogTimeF("NR record %d - clear name '%s' with no ip\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 (Nr6Trace)
        {
            LogTimeF("NR record %d - add ip ", i);
            Ip6AddressLog(ip);
            Log("\r\n");
        }
        updateIpRecord(i, ip, name, dnsProtocol);
        return;
    }
    
    //No other entry exists so just add it to the next available space
    i = getOldest();
    if (Nr6Trace)
    {
        LogTimeF("NR record %d - add entry\r\n", i);
    }
    updateIpRecord(i, ip, name, dnsProtocol);
}
static void ipToName(char* ip, char* name)
{
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (Ip6AddressIsSame(records[i].ip, ip))
        {
            strcpy(name, records[i].name);
            return;
        }
    }
    name[0] = 0;
}
static void nameToIp(char* name, char* ip)
{
    uint32_t newest = 0xFFFFFFFF;
    Ip6AddressClear(ip);
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if(Ip6AddressIsEmpty(records[i].ip)) continue;
        if (!DnsLabelIsSame(records[i].name, name)) continue;
        uint32_t age = MsTimerCount - records[i].ageMs;
        if (age <= newest)
        {
            newest = age;
            Ip6AddressCopy(ip, records[i].ip);
        }
    }
}
void Nr6MakeRequestForNameFromIp(char* ip)
{
    makeRequestForNameFromIp(ip);
}
void Nr4MakeRequestForNameFromIp(uint32_t ip4)
{
    char ip6[16];
    Ip6AddressFromIp4(ip6, ip4);
    makeRequestForNameFromIp(ip6);
}
void Nr6MakeRequestForIpFromName(char* name)
{
    makeRequestForIpFromName(name, TODO_IP6_FROM_NAME);
}
void Nr4MakeRequestForIpFromName(char* name)
{
    makeRequestForIpFromName(name, TODO_IP4_FROM_NAME);
}
void Nr6AddIpRecord(char* ip, char* name, int dnsProtocol)
{
    addIpRecord(ip, name, dnsProtocol);
}
void Nr4AddIpRecord(uint32_t ip4, char* name, int dnsProtocol)
{
    char ip6[16];
    Ip6AddressFromIp4(ip6, ip4);
    addIpRecord(ip6, name, dnsProtocol);
}
void Nr6NameToIp(char* name, char* ip)
{
    nameToIp(name, ip);
}
void Nr4NameToIp(char* name, uint32_t* pIp)
{
    char ip6[16];
    nameToIp(name, ip6);
    *pIp = Ip6AddressToIp4(ip6);
}
void Nr6IpToName(char* ip, char* name)
{
    ipToName(ip, name);
}
void Nr4IpToName(uint32_t ip4, char* name)
{
    char ip6[16];
    Ip6AddressFromIp4(ip6, ip4);
    ipToName(ip6, name);
}
static char letterFromStateAndProtocol(uint8_t dnsState, uint8_t dnsProtocol)
{
    switch (dnsState)
    {
        case STATE_WANT:
        case STATE_SENT:                 return '>';
        
        case STATE_VALID:
            switch (dnsProtocol)
            {
                case DNS_PROTOCOL_UDNS:  return 'd';
                case DNS_PROTOCOL_MDNS:  return 'm';
                case DNS_PROTOCOL_LLMNR: return 'l';
                case DNS_PROTOCOL_NONE:  return '-';
                default:                 return '?';
            }
        default:                         return '~';
    }
}
void Nr6SendHttp()
{
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (!Ip6AddressIsEmpty(records[i].ip) || records[i].name[0])
        {
            HttpAddF("%4u ", (MsTimerCount - records[i].ageMs) / 1000 / 60);
            
            int ipLen;
            ipLen = Ip6AddressHttp(records[i].ip);
            HttpAddFillChar(' ', 40 - ipLen);

            HttpAddChar(letterFromStateAndProtocol(records[i].state, records[i].dnsProtocol));
            
            HttpAddChar(' ');
            
            HttpAddText(records[i].name);
            
            HttpAddChar('\r');
            HttpAddChar('\n');
        }
    }
}
void Nr6SendAjax()
{
    for (int i = 0; i < RECORDS_COUNT; i++)
    {
        if (records[i].state == STATE_EMPTY) continue;
        if (!Ip6AddressIsEmpty(records[i].ip) || 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].ip[b]);
            HttpAddChar('\t');
            HttpAddChar(letterFromStateAndProtocol(records[i].state, records[i].dnsProtocol));
            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 nextProtocol(struct record* pr)
{
    if (pr->state == STATE_SENT && MsTimerRelative(pr->replyMs, REPLY_TIMEOUT_MS) && pr->dnsProtocol)
    {
        pr->dnsProtocol = DnsGetNextProtocol(pr->dnsProtocol);
        if (pr->dnsProtocol)
        {
            pr->state = STATE_WANT;
        }
        else //We have tried everything and resolution has failed
        {
            if (pr->todo == TODO_NAME_FROM_IP) pr->name[0] = 0;
            if (pr->todo == TODO_IP4_FROM_NAME || pr->todo == TODO_IP6_FROM_NAME) Ip6AddressClear(pr->ip);
            pr->todo   = TODO_NONE;
            pr->state  = STATE_VALID;
            pr->ageMs  = MsTimerCount;
        }
        pr->replyMs = MsTimerCount;
    }
}
static void queryNameFromIp(struct record* pr)
{
    if (Nr6Trace)
    {
        LogTime("NR - send ");
        DnsProtocolLog(pr->dnsProtocol);
        if (Ip6AddrIsIp4(pr->ip)) Log(" request for name from IP4 ");
        else                      Log(" request for name from IP6 ");
        Ip6AddressLog(pr->ip);
        Log("\r\n");
    }
    if (Ip6AddrIsIp4(pr->ip)) 
    {
        uint32_t ip4 = Ip6AddressToIp4(pr->ip);
        DnsQueryNameFromIp4(ip4, pr->dnsProtocol, pr->ipProtocol);
    }
    else
    {
        DnsQueryNameFromIp6(pr->ip, pr->dnsProtocol, pr->ipProtocol);
    }
}
static void queryIpFromName(struct record* pr, int todo)
{
    if (Nr6Trace)
    {
        LogTime("NR - send ");
        DnsProtocolLog(pr->dnsProtocol);
        if (todo == TODO_IP4_FROM_NAME) Log(" request for IP4 from name '");
        else                            Log(" request for IP6 from name '");
        Log(pr->name);
        Log("'\r\n");
    }
    if (todo == TODO_IP4_FROM_NAME) DnsQueryIp4FromName(pr->name, pr->dnsProtocol, pr->ipProtocol);
    else                            DnsQueryIp6FromName(pr->name, pr->dnsProtocol, pr->ipProtocol);
}
static void sendRequest(struct record* pr)
{
    if ( DnsQueryIsBusy            ) return;
    if ( pr->state != STATE_WANT   ) return;
    if (!pr->dnsProtocol           ) return;
    
    if (pr->todo == TODO_NAME_FROM_IP ) queryNameFromIp(pr);
    if (pr->todo == TODO_IP4_FROM_NAME) queryIpFromName(pr, TODO_IP4_FROM_NAME);
    if (pr->todo == TODO_IP6_FROM_NAME) queryIpFromName(pr, TODO_IP6_FROM_NAME);
    
    pr->state = STATE_SENT;
    pr->replyMs = MsTimerCount;
}
void Nr6Main()
{
    static int i = -1;
    i++;
    if (i >= RECORDS_COUNT) i = 0;
    
    struct record* pr = &records[i];
    
    clearCache  (pr);
    nextProtocol(pr);
    sendRequest (pr);
}
void Nr6Init()
{
    for (int i = 0; i < RECORDS_COUNT; i++) records[i].state = STATE_EMPTY;
}