diff --git a/Makefile b/Makefile index 24b9e793..9d701347 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 @@ -84,6 +86,7 @@ SRCS += \ rtl837x_pins.c\ rtl837x_port.c \ rtl837x_stp.c \ + rtl837x_lldp.c \ rtl837x_storm.c SRCS += \ httpd/httpd.c \ diff --git a/cmd_parser.c b/cmd_parser.c index 73c61758..93c0cade 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -16,10 +16,12 @@ #include "rtl837x_stp.h" #include "rtl837x_igmp.h" #include "rtl837x_bandwidth.h" +#include "rtl837x_lldp.h" #include "rtl837x_storm.h" #include "sfp.h" #include "dhcp.h" #include "syslog.h" +#include "dns.h" #include "uip/uip.h" #include "version.h" @@ -33,6 +35,7 @@ extern __code const struct machine machine; extern __xdata bool stp_enabled; extern __code const uint8_t log_to_phys_port[9]; +extern __xdata bool lldp_enabled; extern volatile __xdata uint32_t ticks; extern volatile __xdata uint8_t sfr_data[4]; @@ -880,6 +883,7 @@ void parse_port(void) cmd_error("\nUsage:" \ "\nport [show|on|off]" \ "\nport [10m|100m|1g|2g5|duplex] [half|full]" \ + "\nport lldp [block|permit]" \ "\nport name [custom port name]\n"); return; } @@ -912,6 +916,14 @@ void parse_port(void) print_string("\nName set to: \""); print_string_x(port_names[phy_settings.port]); print_string("\"\n"); + } else if (cmd_compare(2, "lldp")) { + if (cmd_compare(3, "block")){ + lldp_logical_port_status &= ~((uint16_t)1 << phy_settings.port); + } else if (cmd_compare(3, "permit")) { + lldp_logical_port_status |= (uint16_t)1 << phy_settings.port; + } else { + print_string ("Unknown port lldp [block|permit] command\n"); + } } else if (machine.is_sfp[phy_settings.port]) { print_string(" is SFP no PHY information available.\n"); } else if (cmd_compare(2, "10m")) { @@ -1855,6 +1867,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(); @@ -2027,6 +2041,14 @@ void cmd_parser(void) __banked parse_ingress(); } else if (cmd_compare(0, "session")) { parse_session(); + } else if (cmd_compare(0, "lldp")) { + if (cmd_compare(1, "on")) { + print_string("LLDP enabled\n"); + lldp_enabled = 1; + } else { + print_string("LLDP disabled\n"); + lldp_enabled = 0; + } } else { cmd_error("Unknown command\n"); diff --git a/cmd_parser.h b/cmd_parser.h index f96c72ae..2359eb16 100644 --- a/cmd_parser.h +++ b/cmd_parser.h @@ -20,6 +20,7 @@ extern __xdata uint8_t err_status; #define HEALTH_PH_STP 4 #define HEALTH_PH_CMD 5 #define HEALTH_PHASES 6 +#define HEALTH_LLDP 7 extern __xdata uint16_t health_rx_frames; #define health_rx_frame() health_rx_frames++ diff --git a/dns.c b/dns.c new file mode 100644 index 00000000..68776c72 --- /dev/null +++ b/dns.c @@ -0,0 +1,238 @@ +/* + * 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'); + 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'); +} + + +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..8a86418d --- /dev/null +++ b/doc/dns.md @@ -0,0 +1,23 @@ +# 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 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 +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 e506c6be..d470f661 100644 --- a/html/app.js +++ b/html/app.js @@ -3,7 +3,7 @@ var LANG={ en:{ nav_dash:"Dashboard",nav_ports:"Ports",nav_stp:"Spanning tree",nav_stats:"Statistics", nav_vlan:"VLANs",nav_l2:"MAC table",nav_mirror:"Mirroring",nav_isolate:"Isolation",nav_lag:"LAG",nav_eee:"EEE", -nav_bw:"Bandwidth",nav_system:"System",nav_fw:"Firmware", +nav_bw:"Bandwidth",nav_system:"System",nav_fw:"Firmware",nav_lldp:"LLDP", hdr_dirty:"unsaved changes",hdr_dirty_t:"Running config differs from startup config", hdr_save:"Save to flash",hdr_save_t:"Persist running configuration to flash", th_auto:"System",th_auto_sel:"System (Selenized)",sy_display:"Display",sy_theme:"Theme",sy_display_note:"Stored in this browser only.",th_light:"Light",th_dark:"Dark", @@ -40,6 +40,7 @@ stp_en_q:"Enable spanning tree?", stp_en_d:"Ports start blocked and take up to twice the forward delay to reach forwarding; edge ports recover immediately.", stp_dis_q:"Disable spanning tree?",stp_dis_d:"All ports go straight to forwarding; loop protection is lost.", stp_cost_err:"Path cost must be 0-200000000", +lldp_enabled: "LLDP Enabled",lldp_title:"Service Status",lldp_portname:"Name",lldp_enable:"Send Frames", st_title:"Port statistics",st_h:"totals since boot",st_txg:"TX good",st_txb:"TX bad",st_rxg:"RX good", st_rxb:"RX bad",st_details:"Details",st_counters:"MIB counters",st_nonzero:"non-zero only", st_autoref:"auto-refresh",st_counter:"Counter",st_value:"Value",st_fail:"failed to load counters", @@ -89,7 +90,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.", @@ -108,6 +109,7 @@ fw_intro:"Upload an RTLPlayground image (512 KiB .bin). The image is staged to f fw_upload:"Upload",fw_checking:"{f}: {n} bytes. Checking...",fw_size_err:"size is {n}, expected 524288 (512 KiB)", fw_magic_err:"missing bank header / LJMP magic: not a firmware image",fw_crc_err:"CRC16 check failed: corrupt image", fw_valid:"valid RTLPlayground image (size, magic and CRC16 OK)",fw_q:"Upload firmware?", +fw_other:"This image was not built for this switch ({m}): its SFP, LED and button pins may differ. Upload it only if you are sure.", fw_d:"On a good checksum the switch resets itself and applies the image during boot (the startup config is preserved). Do not power it off until it comes back.", fw_finishing:"finishing flash write... {s} s",fw_uploading:"uploading... {p}% / {s} s", fw_verified:"checksum verified, the switch is rebooting...", @@ -678,6 +680,7 @@ fw_intro:"Ein RTLPlayground-Image hochladen (.bin, 512 KiB). Das Image wird im F fw_upload:"Hochladen",fw_checking:"{f}: {n} Bytes. Wird geprüft...",fw_size_err:"Grösse ist {n}, erwartet 524288 (512 KiB)", fw_magic_err:"Bank-Header / LJMP-Kennung fehlt: kein Firmware-Image",fw_crc_err:"CRC16-Prüfung fehlgeschlagen: Image beschädigt", fw_valid:"gültiges RTLPlayground-Image (Grösse, Kennung und CRC16 in Ordnung)",fw_q:"Firmware hochladen?", +fw_other:"Dieses Image wurde nicht für diesen Switch ({m}) gebaut: SFP-, LED- und Tasten-Pins können abweichen. Nur hochladen, wenn du sicher bist.", fw_d:"Ist die Prüfsumme korrekt, startet der Switch neu und übernimmt das Image beim Hochfahren (die Startkonfiguration bleibt erhalten). Nicht vom Strom trennen, bis er wieder erreichbar ist.", fw_finishing:"Flash wird fertig beschrieben... {s} s",fw_uploading:"wird hochgeladen... {p}% / {s} s", fw_verified:"Prüfsumme bestätigt, der Switch startet neu...", @@ -784,6 +787,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?)+$/, @@ -791,6 +795,7 @@ var CONF_CMDS=[ /^ingress(\s+\d{1,2}[tua])+$/,/^ingress\s+[tua]$/, /^port\s+\d{1,2}\s+(10m|100m|1g|2g5|5g|10g|auto|on|off)(\s+(half|full))?$/, /^port\s+\d{1,2}\s+name\s+\S+$/, + /^port\s+\d{1,2}\s+lldp\s+(permit|block)+$/, /^eee\s+(on|off)(\s+\d{1,2})?(\s+(100m|1g|2g5))?$/, /^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,/^mirror\s+off$/, /^lag\s+[1-4](\s+\d{1,2})+$/,/^lag\s+[1-4]\s+d$/,/^laghash\s+[1-4](\s+\w+)+$/, @@ -801,7 +806,7 @@ var CONF_CMDS=[ /^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+cost\s+\d{1,9}$/,/^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+prio\s+\d{1,3}$/, /^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+guard\s+(none|bpdu|root)$/,/^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+filter\s+(on|off)$/, /^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+p2p\s+(auto|on|off)$/, - /^igmp\s+(on|off)$/,/^mtu\s+\d{1,2}\s+\d+$/, + /^igmp\s+(on|off)$/,/^mtu\s+\d{1,2}\s+\d+$/,/^lldp\s+(on|off)$/, /^bw\s+(in|out)\s+\d{1,2}\s+\S+$/, /^storm\s+\d{1,2}\s+(bcast|mcast|ucast|umcast)\s+(off|\d{1,8}\s+(pps|kbps))$/, ]; @@ -863,6 +868,7 @@ var TABS=[ {id:"mirror",icon:"M12 3v18M7 8l-4 4 4 4M17 8l4 4-4 4"}, {id:"isolate",icon:"M4 4h6v6H4zM14 14h6v6h-6zM14 4h6v6h-6zM4 14h6v6H4z"}, {id:"lag", icon:"M7 8a4 4 0 100 8h3M17 8a4 4 0 110 8h-3M9 12h6"}, + {id:"lldp", icon:"M11 12a1 1 0 002 0 1 1 0 00-2 0M8.5 8.5a5 5 0 000 7M15.5 8.5a5 5 0 010 7M5.6 5.6a9 9 0 000 12.8M18.4 5.6a9 9 0 010 12.8"}, {id:"eee", icon:"M13 2L4 14h6l-1 8 9-12h-6z"}, {id:"bw", icon:"M4 18a8 8 0 0116 0M12 18l4-6"}, {id:"system",icon:"M12 8a4 4 0 100 8 4 4 0 000-8zM4 12h2M18 12h2M12 4v2M12 18v2M6 6l1.5 1.5M16.5 16.5L18 18M18 6l-1.5 1.5M7.5 16.5L6 18"}, @@ -1051,7 +1057,11 @@ function renderInfo(){ if(v==null||v==="")return; tb.appendChild(h("tr",null,[h("td",{class:"mut",text:t(r[0])}),h("td",{class:"mono",text:String(v)})])); }); - if(S.info.hostname)$("brandname").textContent=$("brandname").title=S.info.hostname; + if(S.info.hostname){ + $("brandname").textContent=$("brandname").title=document.title=S.info.hostname; + // Remembered per switch (each has its own origin) for the login page tab + try{localStorage.setItem("rtl_host",S.info.hostname);}catch(e){} + } if(S.info.sw_ver)$("fver").textContent="RTLPlayground "+S.info.sw_ver; } function pollInfo(){ @@ -1838,6 +1848,32 @@ function lagApply(g){ } tabHooks.lag={enter:function(){needPorts(function(){buildLag();lagLoad().catch(function(){})})}}; + +function lldpLoad(){ + return getJSON("/lldp.json").then(function(s){ + $("lldp_en").checked=s.on; + var tb=$("lldp_table").tBodies[0];tb.innerHTML=""; + byPort(s.portStatus).forEach(function(p){ + var tr=tb.insertRow(); + tr.insertCell().textContent=p.portNum; + tr.insertCell().textContent=p.name; + var sw=h("label",{class:"switch"},[ + h("input",{type:"checkbox",onchange:function(){ + postCmd("port "+p.portNum+" lldp "+(this.checked?"permit":"block")) + .then(function(){setTimeout(lldpLoad,300)}).catch(function(){}); + }}),h("i")]); + sw.firstChild.checked=p.permitted; + tr.insertCell().appendChild(sw); + }); + }); +} + +$("lldp_en").addEventListener("change",function(){ + var el=this; + postCmd("lldp "+(el.checked?"on":"off")).catch(function(){el.checked=!el.checked}); +}); +tabHooks.lldp={enter:lldpLoad}; + function eeeFlags(bits){ var b=parseInt(bits,2); return["100M","1G","2.5G"].map(function(s,i){ @@ -1996,6 +2032,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; @@ -2012,13 +2058,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(){ @@ -2114,7 +2162,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/, @@ -2234,10 +2282,26 @@ $("saveBtn").addEventListener("click",function(){ }); }); -var fwBuf=null; +var fwBuf=null,fwOther=false; +// Every image carries its machine name as a NUL-terminated string, the same +// one the running switch reports as hw_ver: an image without it was built +// for another board (SFP, LED and button pins differ between machines). +function fwForThisSwitch(u){ + var name=S.info.hw_ver; + if(!name)return true; + var n=[]; + for(var i=0;i"; + fwOther=!fwForThisSwitch(u); + info.innerHTML='\u2713 '+esc(t("fw_valid"))+""+ + (fwOther?'
\u26a0 '+esc(t("fw_other",{m:S.info.hw_ver}))+"":""); $("fwup").disabled=false; }); }); $("fwup").addEventListener("click",function(){ if(!fwBuf)return; - confirmModal(t("fw_q"),t("fw_d"),function(){ + confirmModal(t("fw_q"),(fwOther?t("fw_other",{m:S.info.hw_ver})+" ":"")+t("fw_d"),function(){ var form=new FormData(); form.append("uploadedfile",fwBuf,fwBuf.name); var xhr=new XMLHttpRequest(); diff --git a/html/index.html b/html/index.html index 54ab11d2..1f9c9759 100644 --- a/html/index.html +++ b/html/index.html @@ -331,6 +331,17 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent} +
+

LLDP

+ +
+ +
Port NumberNameSend Frames
+
+
+

Port isolation

A frame entering a port may only leave through the ports ticked in its row.

@@ -378,6 +389,9 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent} + + +

diff --git a/html/login.html b/html/login.html index 9490f77a..042a5c45 100644 --- a/html/login.html +++ b/html/login.html @@ -92,8 +92,12 @@ button:hover{filter:brightness(1.1)} try{l=localStorage.getItem("rtl_lang");}catch(e){} if(!L[l])l=(navigator.language||"en").slice(0,2); var s=L[l]||L.en; - document.title=s[0]; - document.getElementById("hn").textContent=s[0]; + // Name the tab after the switch: the hostname seen at the last login here, + // otherwise the address, so several open switches can be told apart + var host=null; + try{host=localStorage.getItem("rtl_host");}catch(e){} + document.title=(host||location.hostname)+" – "+s[0]; + document.getElementById("hn").textContent=host||s[0]; document.getElementById("sub").textContent=s[1]; document.getElementById("lp").textContent=s[2]; document.getElementById("lb").textContent=s[3]; diff --git a/httpd/httpd.c b/httpd/httpd.c index 770eaa39..aec2eaf1 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -1034,6 +1034,8 @@ void httpd_appcall(void) send_lag(); } else if (is_word(q, "/stp.json")) { send_stp(); + } else if (is_word(q, "/lldp.json")) { + send_lldp(); } else if (is_word(q, "/vlanlist")) { send_vlanlist(); } else if (is_word(q, "/config")) { diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 26a7eb94..2d58457e 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -49,6 +49,9 @@ extern __xdata char sfp_module_model[2][17]; extern __xdata char sfp_module_serial[2][17]; extern __xdata uint8_t sfp_options[2]; +extern __xdata uint16_t lldp_logical_port_status; +extern __xdata bool lldp_enabled; + __code const uint8_t * __code const HTTP_RESPONCE_JSON = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n"; __code const uint8_t * __code const HTTP_RESPONCE_TXT = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n"; @@ -619,7 +622,6 @@ static void bridge_to_html(void) byte_to_html(pi_mac[i]); } - void send_stp(void) { uint8_t i, j, st, dsg; @@ -734,8 +736,37 @@ void send_stp(void) slen += strtox(outbuf + slen, "]}"); } +void send_lldp(void) +{ + dbg_string("send_lldp called\n"); + slen = strtox(outbuf, HTTP_RESPONCE_JSON); + slen += strtox(outbuf + slen, "{\"on\":"); + bool_to_html(lldp_enabled); + slen += strtox(outbuf + slen, ",\"portStatus\":["); + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + slen += strtox(outbuf + slen, "{\"portNum\":"); + itoa_html(machine.log_to_phys_port[i]); + + slen += strtox(outbuf + slen, ",\"logPort\":"); + itoa_html(i); + slen += strtox(outbuf + slen, ",\"name\":\""); + for (uint8_t j = 0; j < PORT_NAME_SIZE && port_names[i][j]; j++) { + json_char_to_html(port_names[i][j]); + } + + slen += strtox(outbuf + slen, "\",\"permitted\":"); + bool_to_html(1 & (lldp_logical_port_status >> i)); + slen += strtox(outbuf + slen, "}"); + + if (i < machine.max_port) + char_to_html(','); + else + char_to_html(']'); + } + char_to_html('}'); +} void send_eee(void) { diff --git a/httpd/page_impl.h b/httpd/page_impl.h index d6690dc6..fd51c8c6 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -23,6 +23,7 @@ void send_config(void); void send_cmd_log(void); void send_lag(void); void send_stp(void); +void send_lldp(void); void send_vlanlist(void); /* Convert only the lower nibble to ascii HEX char. diff --git a/rtl837x_common.h b/rtl837x_common.h index 9b601730..a36d41fc 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -131,6 +131,7 @@ struct flash_region_t { }; extern __xdata char port_names[9][PORT_NAME_SIZE]; +extern __xdata uint16_t lldp_logical_port_status; extern __xdata bool stp_enabled; diff --git a/rtl837x_flash.c b/rtl837x_flash.c index c1357b04..1c2f65fc 100644 --- a/rtl837x_flash.c +++ b/rtl837x_flash.c @@ -269,6 +269,7 @@ void flash_read_security(void) // Set slow read mode SFR_FLASH_CMD_R = CMD_READ_SECURITY_REGS; // read security register + SFR_FLASH_DUMMYCYCLES = 8; // Transfer 4 bytes (command + 3byte address) SFR_FLASH_TCONF = 4; diff --git a/rtl837x_lldp.c b/rtl837x_lldp.c new file mode 100644 index 00000000..84208433 --- /dev/null +++ b/rtl837x_lldp.c @@ -0,0 +1,161 @@ +#pragma codeseg BANK3 +#pragma constseg BANK3 + +#include "rtl837x_common.h" +#include "rtl837x_lldp.h" +#include "rtl837x_sfr.h" +#include "rtl837x_common.h" +#include "dhcp.h" +#include "uip.h" +#include "uip/uip.h" +#include "machine.h" + +extern __xdata bool lldp_enabled; +extern __code const struct machine machine; + +__xdata uint16_t lldp_ticks; +__xdata uint16_t lldp_logical_port_status = 0xffff; + +void lldp_init(void) __banked +{ + lldp_ticks = 0; +} + +void lldp_tick(void) __banked +{ + lldp_ticks++; + + if (lldp_ticks < LLDP_TX_INTERVAL_TICKS) + return; + + lldp_ticks = 0; + + if(lldp_enabled == 1) + lldp_send(); +} + +//Outgoing LLDP packet. + +struct lldp_pkt { + struct uip_eth_addr dst; + struct uip_eth_addr src; + struct rtl_tag rtl_tag; + uint16_t ether_type; + + uint8_t payload[64]; +}; + +#define LLDP_O ((__xdata struct lldp_pkt *)&uip_buf[RTL_FRAME_DESC_SIZE]) + +// The type is 7 bits and the length is 9 bits +// we can represent this as the 7 most significant +// bits of the type bit and leave the least significant +// bit as 0 always as we're not going to need 2^9 for +// the length at any point +#define LLDP_TYPE(value) (value << 1) + +void lldp_send(void) __banked __reentrant +{ + __xdata uint8_t *p; + uint8_t port_position; + + lldp_set_addresses(); + lldp_set_rtl_wrapper(); + + p = LLDP_O->payload; + + //Chassis ID + *p++ = LLDP_TYPE(LLDP_CHASSIS_ID_TLV_TYPE); + *p++ = LLDP_CHASSIS_ID_TLV_LENGTH; + *p++ = LLDP_CHASSIS_ID_TLV_SUBTYPE; + + memcpy(p, uip_ethaddr.addr, MAC_ADDR_LEN); + p += MAC_ADDR_LEN; + + //Port ID + *p++ = LLDP_TYPE(LLDP_PORT_ID_TLV_TYPE); + *p++ = LLDP_PORT_ID_TLV_LENGTH; + *p++ = LLDP_PORT_ID_TLV_SUBTYPE; + port_position = (p-LLDP_O->payload); + *p++ = '0'; // filled in per port below + + //TTL + *p++ = LLDP_TYPE(LLDP_TTL_TLV_TYPE); + *p++ = LLDP_TTL_TLV_LENGTH; + *p++ = 0x00; //padding + *p++ = LLDP_TTL_TTL_SECONDS; + + p += lldp_sysname(p); + p += lldp_sysdesc(p); + + // End of LLDPDU TLV + *p++ = 0x00; //padding + *p++ = 0x00; //padding + + while (p < (LLDP_O->payload + LLDP_MIN_ETHERNET_PAYLOAD_LENGTH)) + *p++ = 0x00; + + /* + * uip_len is the Ethernet frame length excluding FCS. + * + * The frame consists of: + * + * dst mac + * src mac + * rtl_tag sizeof(struct rtl_tag) + * EtherType (2 bytes) + * payload (how far we've moved p pointer) + */ + uip_len = MAC_ADDR_LEN + MAC_ADDR_LEN + sizeof(struct rtl_tag) + LLDP_ETHERTYPE_LENGTH + (p - LLDP_O->payload); + + for (uint8_t port = machine.min_port; port <= machine.max_port; port++) { + + if ((1 << port) & lldp_logical_port_status) { + LLDP_O->payload[port_position] = '0' + machine.log_to_phys_port[port]; + LLDP_O->rtl_tag.pmask = HTONS((uint16_t)1 << port); + + tcpip_output(); + } + } +} + +void lldp_set_addresses(void) __banked { + // LLDP destination multicast addr: 01:80:c2:00:00:0e + memcpyc(LLDP_O->dst.addr, "\x01\x80\xc2\x00\x00\x0e", MAC_ADDR_LEN); + memcpy(LLDP_O->src.addr, uip_ethaddr.addr, MAC_ADDR_LEN); +} + +void lldp_set_rtl_wrapper(void) __banked { + /* + * This is the RTL CPU tag, not the Ethernet EtherType. + * port 0 -> 0x0001 + * port 1 -> 0x0002 + * port 2 -> 0x0004 + */ + LLDP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); + LLDP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; + LLDP_O->rtl_tag.reason = 0x00; + LLDP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS); + + LLDP_O->ether_type = HTONS(LLDP_ETHERTYPE); +} + +uint8_t lldp_sysname(__xdata uint8_t *p) __banked +{ + uint8_t hostname_length = strlen_x(hostname); + + *p++ = LLDP_TYPE(LLDP_SYSNAME_TLV_TYPE); + *p++ = hostname_length; + memcpy(p, hostname, hostname_length); + return hostname_length + 2; +} + +uint8_t lldp_sysdesc(__xdata uint8_t *p) __banked +{ + uint16_t machine_name_length = strlen(machine.machine_name); + + *p++ = LLDP_TYPE(LLDP_SYSDESC_TLV_TYPE); + *p++ = machine_name_length; + memcpyc(p, machine.machine_name, machine_name_length); + return machine_name_length + 2; +} diff --git a/rtl837x_lldp.h b/rtl837x_lldp.h new file mode 100644 index 00000000..298775c6 --- /dev/null +++ b/rtl837x_lldp.h @@ -0,0 +1,36 @@ +#ifndef _LLDP_H_ +#define _LLDP_H_ + +#include + +#define LLDP_MIN_ETHERNET_PAYLOAD_LENGTH 46 +#define LLDP_ETHERTYPE 0x88cc +#define LLDP_ETHERTYPE_LENGTH 2 +#define LLDP_MAX_FRAME 256 +#define MAC_ADDR_LEN 6 +#define LLDP_TX_INTERVAL_TICKS 6000 + +#define LLDP_PORT_ID_TLV_TYPE 2 +#define LLDP_PORT_ID_TLV_LENGTH 0x02 +#define LLDP_PORT_ID_TLV_SUBTYPE 0x07 + +#define LLDP_TTL_TLV_TYPE 3 +#define LLDP_TTL_TLV_LENGTH 2 +#define LLDP_TTL_TTL_SECONDS 120 + +#define LLDP_CHASSIS_ID_TLV_TYPE 1 +#define LLDP_CHASSIS_ID_TLV_LENGTH 7 +#define LLDP_CHASSIS_ID_TLV_SUBTYPE 4 + +#define LLDP_SYSNAME_TLV_TYPE 5 +#define LLDP_SYSDESC_TLV_TYPE 6 + +void lldp_init(void) __banked; +void lldp_tick(void) __banked; +void lldp_send(void) __banked; +void lldp_set_addresses(void) __banked; +void lldp_set_rtl_wrapper(void) __banked; +uint8_t lldp_sysname(__xdata uint8_t *p) __banked; +uint8_t lldp_sysdesc(__xdata uint8_t *p) __banked; + +#endif diff --git a/rtlplayground.c b/rtlplayground.c index 37d47312..c181720f 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -16,6 +16,7 @@ #include "rtl837x_leds.h" #include "rtl837x_bandwidth.h" #include "rtl837x_init.h" +#include "rtl837x_lldp.h" #include "dhcp.h" #include "cmd_parser.h" #include "cmd_editor.h" @@ -25,6 +26,7 @@ #include "machine.h" #include "phy.h" #include "syslog.h" +#include "dns.h" #include "httpd/page_impl.h" #include "boot.h" #include "sfp.h" @@ -127,6 +129,7 @@ __xdata uint8_t tx_seq; __xdata bool stp_enabled; __xdata uint8_t igmpEnabled; +__xdata bool lldp_enabled; __xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */ __code const uint16_t bit_mask[16] = { @@ -1286,6 +1289,8 @@ static void handle_tick(void) } } health_phase(HEALTH_PH_STP); + lldp_tick(); + health_phase(HEALTH_LLDP); } @@ -1763,6 +1768,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 @@ -1792,6 +1798,7 @@ void main(void) uip_init(); uip_arp_init(); httpd_init(); + lldp_init(); management_vlan = 1; // Default management VLAN is 1 diff --git a/test/env_tables.c b/test/env_tables.c index 274a67d5..41694b72 100644 --- a/test/env_tables.c +++ b/test/env_tables.c @@ -84,6 +84,9 @@ char port_names[9][PORT_NAME_SIZE]; struct flash_region_t flash_region; struct syslog_state syslog_state; bool stp_enabled; +bool lldp_enabled; +uint16_t lldp_logical_port_status; + uip_ipaddr_t uip_hostaddr, uip_draddr, uip_netmask; struct uip_eth_addr uip_ethaddr = { .addr = { 0x02, 0x11, 0x22, 0x33, 0x44, 0x55 } }; diff --git a/test/test_httpd_tx.c b/test/test_httpd_tx.c index c7ccf09c..4df05472 100644 --- a/test/test_httpd_tx.c +++ b/test/test_httpd_tx.c @@ -152,6 +152,7 @@ void send_lag(void) { } void send_stp(void) { } void send_stp_counters(void) { } void send_vlanlist(void) { } +void send_lldp(void) {} extern uint8_t authenticated; diff --git a/tools/httpd_sim.c b/tools/httpd_sim.c index 76c088c3..5fea029f 100644 --- a/tools/httpd_sim.c +++ b/tools/httpd_sim.c @@ -447,13 +447,41 @@ void send_mtu(int s) json_object_object_add(v, "mtu", json_object_new_string(mtu)); json_object_array_add(mtus, v); } - write(s, header, strlen(header)); + write(s, header, strlen(header)); jstring = json_object_to_json_string_ext(mtus, JSON_C_TO_STRING_PLAIN); write(s, jstring, strlen(jstring)); json_object_put(mtus); } +void send_lldp(int s) +{ + struct json_object *c, *portStatuses, *p; + const char *jstring; + char *header = "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json; charset=UTF-8\r\n\r\n"; + + c = json_object_new_object(); + json_object_object_add(c, "on", json_object_new_int(1)); + portStatuses = json_object_new_array_ext(PORTS); + + for (int i = 1; i <= PORTS; i++) { + p = json_object_new_object(); + json_object_object_add(p, "portNum", json_object_new_int(i)); + json_object_object_add(p, "logPort", json_object_new_int(i-1)); + json_object_object_add(p, "name", json_object_new_string("")); + json_object_object_add(p, "permitted", json_object_new_int(1)); + json_object_array_add(portStatuses, p); + } + + json_object_object_add(c, "portStatus", portStatuses); + + write(s, header, strlen(header)); + + jstring = json_object_to_json_string_ext(c, JSON_C_TO_STRING_PLAIN); + write(s, jstring, strlen(jstring)); + json_object_put(c); +} void send_cmd_log(int s) { @@ -468,7 +496,6 @@ void send_cmd_log(int s) } } - void send_config(int s) { char *header = "HTTP/1.1 200 OK\r\n" @@ -701,6 +728,13 @@ void launch(struct Server *server) else send_vlan(new_socket, vlan); goto done; + } else if (!strncmp(&buffer[4], "/lldp.json", 10)) { + printf("LLDP request"); + if (!authenticated) + send_unauthorized(new_socket); + else + send_lldp(new_socket); + goto done; } else if (!strncmp(&buffer[4], "/cmd_log", 8)) { printf("Request cmd_log\n"); if (!authenticated) diff --git a/tools/httpd_sim.h b/tools/httpd_sim.h index 49c078aa..df59ff19 100644 --- a/tools/httpd_sim.h +++ b/tools/httpd_sim.h @@ -3,7 +3,7 @@ #include -#define BUFFER_SIZE 131072 +#define BUFFER_SIZE 262144 struct Server { int domain; 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);