diff --git a/Makefile b/Makefile index aa1839c4..d7fbf131 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,8 @@ SRCS = \ rtlplayground.c \ boot.c \ sfp.c \ + dns.c \ + dns_cli.c \ syslog.c \ udp_apps.c diff --git a/cmd_parser.c b/cmd_parser.c index cb6b6c00..93a5af7c 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -19,6 +19,7 @@ #include "sfp.h" #include "dhcp.h" #include "syslog.h" +#include "dns.h" #include "uip/uip.h" #include "version.h" @@ -1802,6 +1803,8 @@ void cmd_parser(void) __banked parse_mtu(); } else if (cmd_compare(0, "syslog")) { parse_syslog(); + } else if (cmd_compare(0, "dns")) { + dns_parse(); } else if (cmd_compare(0, "ip")) { if (cmd_compare(1, "dhcp")) { dhcp_start(); diff --git a/dns.c b/dns.c new file mode 100644 index 00000000..2dc7e575 --- /dev/null +++ b/dns.c @@ -0,0 +1,226 @@ +/* + * DNS resolver for the RTL837x platform + * This code is in the Public Domain + */ + +#include +#include "rtl837x_common.h" +#include "cmd_parser.h" +#include "uip/uip.h" +#include "dhcp.h" +#include "dns.h" + +#pragma codeseg BANK3 +#pragma constseg BANK3 + +#define DNS_PORT 53 +#define DNS_TRIES 6 +#define DNS_TIMEOUT (2 * SYS_TICK_HZ) + +extern __xdata struct dhcp_state dhcp_state; +extern volatile __xdata uint32_t ticks; + +__xdata struct dns_state dns_state; +__xdata uip_ipaddr_t dns_ip; +uint8_t * __xdata dns_cand[3]; + + +static uint8_t dns_used(__xdata uint8_t *a) __reentrant +{ + return a[0] | a[1] | a[2] | a[3]; +} + + +static uint8_t dns_name_is_ip(void) __reentrant +{ + __xdata char *s = dns_state.name; + uint8_t i, n; + uint16_t v; + + for (i = 0; i < 4; i++) { + v = 0; + n = 0; + while (*s >= '0' && *s <= '9') { + v = v * 10 + (*s++ - '0'); + if (++n > 3 || v > 255) + return 0; + } + if (!n) + return 0; + dns_state.addr[i] = v; + if (i < 3 && *s++ != '.') + return 0; + } + return !*s; +} + + +static void dns_next(void) __reentrant +{ + uint8_t k = 0; + + if (dns_state.conn) { + uip_udp_remove(dns_state.conn); + dns_state.conn = 0; + } + if (dns_used(dns_state.server[0])) + dns_cand[k++] = dns_state.server[0]; + if (dns_used(dns_state.server[1])) + dns_cand[k++] = dns_state.server[1]; + if (dns_used(dhcp_state.dns)) + dns_cand[k++] = dhcp_state.dns; + if (!k || dns_state.tries >= DNS_TRIES) { + dns_state.status = DNS_FAILED; + if (dns_state.verbose) + print_string(k ? "DNS: no answer\n" : "DNS: no server\n"); + return; + } + k = dns_state.tries % k; + uip_ipaddr(dns_ip, dns_cand[k][0], dns_cand[k][1], dns_cand[k][2], dns_cand[k][3]); + dns_state.conn = uip_udp_new(&dns_ip, HTONS(DNS_PORT)); + if (!dns_state.conn) { + dns_state.status = DNS_FAILED; + print_string("DNS: no free UDP connection\n"); + return; + } + dns_state.send = 1; +} + + +static void dns_query(void) __reentrant +{ + __xdata uint8_t *p = (__xdata uint8_t *)uip_appdata; + __xdata char *n = dns_state.name; + __xdata uint8_t *label; + uint16_t len = 12; + + dns_state.id++; + p[0] = dns_state.id >> 8; + p[1] = dns_state.id; + p[2] = 0x01; + p[3] = 0x00; + p[4] = 0; + p[5] = 1; + p[6] = p[7] = p[8] = p[9] = p[10] = p[11] = 0; + while (*n) { + label = p + len++; + *label = 0; + while (*n && *n != '.') { + p[len++] = *n++; + (*label)++; + } + if (*n) + n++; + } + p[len++] = 0; + p[len++] = 0; + p[len++] = 1; + p[len++] = 0; + p[len++] = 1; + uip_udp_send(len); +} + + +static uint16_t dns_skip_name(__xdata uint8_t *p, uint16_t i, uint16_t len) __reentrant +{ + while (i < len) { + if (!p[i]) + return i + 1; + if ((p[i] & 0xc0) == 0xc0) + return i + 2; + i += p[i] + 1; + } + return len; +} + + +static void dns_answer(void) __reentrant +{ + __xdata uint8_t *p = (__xdata uint8_t *)uip_appdata; + uint16_t len = uip_datalen(); + uint16_t i, an; + + if (dns_state.status != DNS_PENDING || len < 12 || !(p[2] & 0x80) + || p[0] != (uint8_t)(dns_state.id >> 8) || p[1] != (uint8_t)dns_state.id) + return; + an = ((uint16_t)p[6] << 8) | p[7]; + i = dns_skip_name(p, 12, len) + 4; + while (!(p[3] & 0x0f) && an-- && i + 10 <= len) { + i = dns_skip_name(p, i, len); + if (i + 10 > len) + break; + if (!p[i] && p[i + 1] == 1 && !p[i + 8] && p[i + 9] == 4 && i + 14 <= len) { + memcpy(dns_state.addr, p + i + 10, 4); + dns_state.status = DNS_DONE; + uip_udp_remove(dns_state.conn); + dns_state.conn = 0; + if (dns_state.verbose) { + print_string("DNS: "); + print_string_x(dns_state.name); + print_string(" is "); + print_ip(dns_state.addr); + write_char('\n'); + } + return; + } + i += 10 + (((uint16_t)p[i + 8] << 8) | p[i + 9]); + } + dns_state.tries = DNS_TRIES; + dns_next(); +} + + +void dns_init(void) __banked +{ + memset(dns_state.server, 0, sizeof(dns_state.server)); + dns_state.name[0] = 0; + dns_state.status = DNS_IDLE; + dns_state.conn = 0; +} + + +void dns_lookup(void) __banked +{ + if (dns_name_is_ip()) { + dns_state.status = DNS_DONE; + return; + } + dns_state.tries = 0; + dns_state.status = DNS_PENDING; + dns_next(); +} + + +void dns_show(void) __banked +{ + print_string("DNS servers "); + print_ip(dns_state.server[0]); + write_char(' '); + print_ip(dns_state.server[1]); + print_string(", from DHCP "); + print_ip(dhcp_state.dns); + write_char('\n'); +} + + +void dns_callback(uint16_t lport) __banked __reentrant +{ + if (!dns_state.conn || lport != dns_state.conn->lport) + return; + if (uip_newdata()) { + dns_answer(); + return; + } + if (dns_state.status != DNS_PENDING) + return; + if (dns_state.send) { + dns_state.send = 0; + dns_state.sent = ticks; + dns_query(); + return; + } + if (ticks - dns_state.sent >= DNS_TIMEOUT) { + dns_state.tries++; + dns_next(); + } +} diff --git a/dns.h b/dns.h new file mode 100644 index 00000000..3407c09d --- /dev/null +++ b/dns.h @@ -0,0 +1,36 @@ +#ifndef _DNS_H_ +#define _DNS_H_ + +#include + +#define DNS_NAME_LEN 48 + +#define DNS_IDLE 0 +#define DNS_PENDING 1 +#define DNS_DONE 2 +#define DNS_FAILED 3 + +struct uip_udp_conn; + +struct dns_state { + uint8_t server[2][4]; /* configured servers, 0.0.0.0 = unused */ + char name[DNS_NAME_LEN]; /* name of the current or last lookup */ + uint8_t addr[4]; /* address found by the last lookup */ + uint8_t status; /* DNS_* */ + uint8_t verbose; /* print the result of the lookup */ + uint8_t tries; /* queries sent for the current lookup */ + uint8_t send; /* a query is due on the connection */ + uint16_t id; + uint32_t sent; /* ticks when the last query went out */ + struct uip_udp_conn *conn; +}; + +extern __xdata struct dns_state dns_state; + +void dns_init(void) __banked; +void dns_lookup(void) __banked; +void dns_show(void) __banked; +void dns_callback(uint16_t lport) __banked __reentrant; +void dns_parse(void) __banked __reentrant; + +#endif diff --git a/dns_cli.c b/dns_cli.c new file mode 100644 index 00000000..f3f4bb38 --- /dev/null +++ b/dns_cli.c @@ -0,0 +1,63 @@ +/* + * Command line interface of the DNS resolver for the RTL837x platform + * This code is in the Public Domain + */ + +#include +#include "rtl837x_common.h" +#include "cmd_parser.h" +#include "dns.h" + +#pragma codeseg BANK2 +#pragma constseg BANK2 + +extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; +extern __xdata uint8_t cmd_words_len; +extern __xdata uint8_t cmd_words_b[15]; +extern __xdata uint8_t ip[4]; +uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd); +uint8_t parse_ip(uint8_t idx); + + +void dns_parse(void) __banked __reentrant +{ + __xdata uint8_t *s; + uint8_t n; + + if (cmd_words_len < 2) { + dns_show(); + return; + } + if (cmd_compare(1, "server") && cmd_words_len >= 3 && parse_ip(cmd_words_b[2])) { + memcpy(dns_state.server[0], ip, 4); + memset(dns_state.server[1], 0, 4); + if (cmd_words_len >= 4) { + if (!parse_ip(cmd_words_b[3])) + goto err; + memcpy(dns_state.server[1], ip, 4); + } + return; + } + if (cmd_compare(1, "lookup") && cmd_words_len >= 3) { + if (dns_state.status == DNS_PENDING) { + print_string("DNS: a lookup is already running\n"); + return; + } + s = &cmd_buffer[cmd_words_b[2]]; + for (n = 0; s[n] && s[n] != ' '; n++) { + if (n == DNS_NAME_LEN - 1) + goto err; + dns_state.name[n] = s[n]; + } + dns_state.name[n] = 0; + dns_state.verbose = 1; + dns_lookup(); + if (dns_state.status == DNS_DONE) { + print_ip(dns_state.addr); + write_char('\n'); + } + return; + } +err: + print_string("Error: dns [server []|lookup ]\n"); +} diff --git a/doc/dns.md b/doc/dns.md new file mode 100644 index 00000000..3763a404 --- /dev/null +++ b/doc/dns.md @@ -0,0 +1,19 @@ +# DNS + +The switch can resolve host names to IPv4 addresses, for the NTP client and +for anything else that is given a name instead of an address. + +``` +dns server [] # one or two servers, 0.0.0.0 removes them +dns lookup # resolve a name and print the address +dns # show the servers +``` + +Without a configured server the resolver uses the one handed out by DHCP. +Only address records are looked up; a query goes to the servers in turn, +every two seconds, and gives up after six tries. A name that already is a +dotted address is taken as it is, without a query. + +The DNS servers are set in the Network card of the System page as well. +Settings made there or on the console are kept only once the configuration is +saved. diff --git a/html/app.js b/html/app.js index 021ebb22..da8374c1 100644 --- a/html/app.js +++ b/html/app.js @@ -83,7 +83,7 @@ sy_ip_err:"Invalid IP / netmask / gateway",sy_host_err:"Hostname: 1-23 printable sy_net_q:"Apply network settings?",sy_net_d:"The management IP changes to {ip}: this page will need to be reopened there.", sy_ip_changed:"IP changed, reconnect at http://{ip}/",sy_dhcp_q:"Switch to DHCP?", sy_dhcp_d:"The switch requests an address via DHCP. You must find its new IP to reconnect.", -sy_sysip_err:"Invalid syslog server IP",sy_sysport_err:"Syslog port must be 1-65535", +sy_sysip_err:"Invalid syslog server IP",sy_dns1:"DNS server",sy_dns2:"Second DNS",sy_dns_err:"Invalid DNS server IP",sy_dns_dhcp:"from DHCP: {ip}",sy_sysport_err:"Syslog port must be 1-65535", sy_pw_len:"Password: 1-20 characters",sy_pw_space:"Password cannot contain spaces",sy_pw_match:"Passwords do not match", sy_pw_q:"Change admin password?",sy_pw_d:"Takes effect immediately for new logins. Save to flash to persist.", sy_reboot_q:"Reboot the switch?",sy_reboot_d:"There are UNSAVED changes and they will be lost. Save to flash first if you want to keep them.", @@ -658,6 +658,7 @@ var CONF_CMDS=[ /^gw\s+(\d{1,3}\.){3}\d{1,3}$/,/^netmask\s+(\d{1,3}\.){3}\d{1,3}$/, /^syslog\s+(on|off)$/,/^syslog\s+ip\s+(\d{1,3}\.){3}\d{1,3}$/,/^syslog\s+port\s+\d{1,5}$/, /^session\s+\d{1,5}$/, + /^dns\s+server\s+(\d{1,3}\.){3}\d{1,3}(\s+(\d{1,3}\.){3}\d{1,3})?$/, /^passwd\s+\S+$/,/^hostname\s+\S{1,23}$/, /^vlan\s+\d{1,4}\s+d$/,/^vlan\s+\d{1,4}\s+mgmt$/, /^vlan\s+\d{1,4}(\s+[a-zA-Z]\w*)?(\s+\d{1,2}t?)+$/, @@ -1744,6 +1745,16 @@ function sysLoad(){ if(S.info.session_timeout)$("sy-sesstmo").value=S.info.session_timeout; }).catch(function(){}); cfgReload(); + dnsLoad(); +} +function dnsLoad(){ + return api("/cmd",{method:"POST",body:"dns"}).then(function(r){ + var m=r.body.match(/DNS servers ([\d.]+) ([\d.]+), from DHCP ([\d.]+)/); + if(!m)return; + function v(x){return x==="0.0.0.0"?"":x} + $("sy-dns1").value=v(m[1]);$("sy-dns2").value=v(m[2]); + $("sy-dnsdhcp").textContent=m[3]==="0.0.0.0"?"":t("sy_dns_dhcp",{ip:m[3]}); + }).catch(function(){}); } function cfgParseKnown(txt){ var igmp=false,syslog=false; @@ -1760,13 +1771,15 @@ function cfgParseKnown(txt){ $("sy-apply").addEventListener("click",function(){ var ip=$("sy-ip").value.trim(),mask=$("sy-mask").value.trim(),gw=$("sy-gw").value.trim(); if(!okIp(ip)||!okIp(mask)||!okIp(gw)){toast(t("sy_ip_err"),"err");return;} + var d1=$("sy-dns1").value.trim(),d2=$("sy-dns2").value.trim(); + if((d1&&!okIp(d1))||(d2&&!okIp(d2))){toast(t("sy_dns_err"),"err");return;} var cmds=[]; var hn=$("sy-host").value.trim(); if(hn&&hn!==S.info.hostname){ if(!/^[\x21-\x7e]{1,23}$/.test(hn)||/["\\]/.test(hn)){toast(t("sy_host_err"),"err");return;} cmds.push("hostname "+hn); } - cmds.push("ip "+ip+"\nnetmask "+mask+"\ngw "+gw); + cmds.push("ip "+ip+"\nnetmask "+mask+"\ngw "+gw+"\ndns server "+(d1||"0.0.0.0")+(d2?" "+d2:"")); var changingIp=ip!==S.info.ip_address; confirmModal(t("sy_net_q"),changingIp?t("sy_net_d",{ip:ip}):"",function(){ postCmds(cmds).then(function(){ @@ -1862,7 +1875,7 @@ tabHooks.system={enter:sysLoad}; var CONF_OVERWRITE=[ /^ip\b/,/^gw\b/,/^netmask\b/,/^hostname\b/, - /^syslog\s+ip\b/,/^syslog\s+port\b/,/^passwd\b/,/^session\b/, + /^syslog\s+ip\b/,/^syslog\s+port\b/,/^dns\s+server\b/,/^passwd\b/,/^session\b/, /^vlan\s+\d{1,4}\s+mgmt$/,/^vlan\s+\d{1,4}(?!\s+mgmt\b)/, /^pvid\s+\d{1,2}\b/, /^port\s+\d{1,2}(?!\s+name\b)/,/^port\s+\d{1,2}\s+name\b/, diff --git a/html/index.html b/html/index.html index 36f3650d..0afb30bc 100644 --- a/html/index.html +++ b/html/index.html @@ -360,6 +360,9 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent} + + +

diff --git a/rtlplayground.c b/rtlplayground.c index dbe39ab4..604abfae 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -25,6 +25,7 @@ #include "machine.h" #include "phy.h" #include "syslog.h" +#include "dns.h" #include "httpd/page_impl.h" #include "boot.h" #include "sfp.h" @@ -1756,6 +1757,7 @@ void main(void) check_and_flash_update_image(); syslog_init(); + dns_init(); #ifdef DEBUG // This register seems to work on the RTL8373 only if also the SDS diff --git a/udp_apps.c b/udp_apps.c index f2c9c55b..4292a105 100644 --- a/udp_apps.c +++ b/udp_apps.c @@ -6,4 +6,5 @@ void udp_callbacks(void) { dhcp_callback(uip_udp_conn->lport); // let the application decide if this is for it or not syslog_callback(uip_udp_conn->lport); // let the application decide if this is for it or not + dns_callback(uip_udp_conn->lport); } \ No newline at end of file diff --git a/udp_apps.h b/udp_apps.h index 1f842ce6..de9d6a18 100644 --- a/udp_apps.h +++ b/udp_apps.h @@ -3,6 +3,7 @@ #include "dhcp.h" #include "syslog.h" +#include "dns.h" void udp_callbacks(void);