From 67b8c718fbe36bff0bd24e6b6120866523d89dd4 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Wed, 23 Sep 2026 22:29:25 +0200 Subject: [PATCH 1/2] dns: resolve host names A small resolver for IPv4 address records, so that clients such as NTP can be given a host name: "dns server []", "dns lookup " and "dns". Without a configured server it uses the one DHCP handed out. A query goes to the servers in turn every two seconds and gives up after six tries; answers with compressed names and CNAME records ahead of the address are understood. A name that already is a dotted address is taken without a query. The resolver sits in BANK3, the command line in BANK2 next to the helpers it uses. The System page shows and sets the servers. --- Makefile | 2 + cmd_parser.c | 3 + dns.c | 226 ++++++++++++++++++++++++++++++++++++++++++++++++ dns.h | 36 ++++++++ dns_cli.c | 63 ++++++++++++++ doc/dns.md | 19 ++++ html/app.js | 19 +++- html/index.html | 3 + rtlplayground.c | 2 + udp_apps.c | 1 + udp_apps.h | 1 + 11 files changed, 372 insertions(+), 3 deletions(-) create mode 100644 dns.c create mode 100644 dns.h create mode 100644 dns_cli.c create mode 100644 doc/dns.md 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); From de0d413c0c6641580520ff5bee4bbbbeff30db8f Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sat, 10 Oct 2026 17:54:55 +0200 Subject: [PATCH 2/2] dns: show the last lookup with the servers The answer to a lookup arrives after the command has returned, so the console of the web interface never shows it. Plain "dns" now prints the name and the address, or whether it is still pending or failed. --- dns.c | 12 ++++++++++++ doc/dns.md | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/dns.c b/dns.c index 2dc7e575..68776c72 100644 --- a/dns.c +++ b/dns.c @@ -200,6 +200,18 @@ void dns_show(void) __banked print_string(", from DHCP "); print_ip(dhcp_state.dns); write_char('\n'); + if (!dns_state.name[0]) + return; + print_string("Last lookup "); + print_string_x(dns_state.name); + print_string(": "); + if (dns_state.status == DNS_DONE) + print_ip(dns_state.addr); + else if (dns_state.status == DNS_PENDING) + print_string("pending"); + else + print_string("failed"); + write_char('\n'); } diff --git a/doc/dns.md b/doc/dns.md index 3763a404..8a86418d 100644 --- a/doc/dns.md +++ b/doc/dns.md @@ -6,9 +6,13 @@ 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 +dns # show the servers and the last lookup ``` +The answer to a lookup arrives after the command has returned, so it is +printed on the serial console and in the syslog. In the console of the web +interface, run `dns` afterwards to see it. + 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