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
Diff: resolve/nr.c
- 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; +}