From 6616a0c9cea8858e7cc17f0a858e0f476e807094 Mon Sep 17 00:00:00 2001 From: gnacho Date: Tue, 8 Sep 2026 22:46:14 +0200 Subject: [PATCH 1/3] feat(cli): add mac command to set the management MAC from the config Add a `mac ` console command (and a bare `mac` to show the current address) that overrides the management MAC at runtime. The address is validated (unicast, globally administered, non-zero OUI) and the static L2 management entry is re-registered so the change applies without a reboot. Because it is a normal command, a line in the startup config persists it across boots: execute_config() runs it before the final static L2 entry and the derived hostname are set up. Also point the keepLink KP-9000-9XHML-X V3.1 machine definition at the factory MAC area (0x1FC000, same location other machines use) so a valid factory MAC is picked up at boot instead of falling back to a locally administered address. The MAC parsing/printing lives in parse_mac()/print_mac() helpers next to parse_ip()/print_ip() so future commands can reuse them, and a memcmp() helper joins the project's own memcpy()/memset() (no libc is linked). --- cmd_parser.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ machine.c | 1 + rtl837x_common.h | 1 + rtlplayground.c | 13 +++++++ 4 files changed, 109 insertions(+) diff --git a/cmd_parser.c b/cmd_parser.c index 4fc2b0cf..d1a3e74a 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -364,6 +364,98 @@ void print_ip(__xdata uint8_t * ptr) } } +// Prints a MAC address. +void print_mac(__xdata uint8_t *ptr) +{ + uint8_t idx = 0; + uint8_t num; + + while (1) { + num = *ptr++; + print_byte(num); + if (++idx == 6) + break; + + write_char(':'); + } +} + +// Parses a MAC address (12 hex digits, ':' or '-' separators optional) from +// cmd_buffer starting at idx into mac[6]. Returns the number of bytes +// consumed, or 0 on error. +uint8_t parse_mac(uint8_t idx, __xdata uint8_t *mac) +{ + uint8_t b = 0; + uint8_t hi = 0xff; /* 0xff = waiting for the high nibble */ + uint8_t c; + + while (b < 6) { + c = cmd_buffer[idx]; + if (c == NUL || c == ' ' || c == '\r' || c == '\n') + break; + idx++; + if (c == ':' || c == '-') + continue; + c |= 0x20; + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'a' && c <= 'f') + c -= 'a' - 10; + else + break; + if (hi == 0xff) { + hi = c; + } else { + mac[b++] = (hi << 4) | c; + hi = 0xff; + } + } + if (b != 6 || hi != 0xff) + return 0; + return idx; +} + +// Sets the management MAC from a "mac " command (or prints +// the current one with a bare "mac"). Refuses blank/multicast/locally +// administered/all-zero-OUI addresses so the running MAC stays reachable, and +// re-registers the static L2 management entry so the change applies without a +// reboot. As a regular command a "mac ..." line in the startup config applies +// on every boot (execute_config runs before the final static L2 entry). +void parse_mac_cmd(void) +{ + __xdata uint8_t mac[6]; + + if (cmd_words_len == 1) { + print_string("Current MAC: "); + print_mac(uip_ethaddr.addr); + write_char('\n'); + return; + } + if (cmd_words_len != 2 || !parse_mac(cmd_words_b[1], mac)) { + print_string("Error: mac []\n"); + return; + } + if ((mac[0] & 0x03) || ((mac[0] | mac[1] | mac[2]) == 0)) { + print_string("refusing: must be unicast, globally administered\n"); + return; + } + if (memcmp(mac, uip_ethaddr.addr, 6) == 0) { + print_string("MAC unchanged\n"); + return; + } + + // Swap the static management entry over to the new address: remove the + // old one (still in uip_ethaddr) before overwriting it. + port_l2_static_mgmt(uip_ethaddr.addr, management_vlan, true); + memcpy(uip_ethaddr.addr, mac, 6); + port_l2_static_mgmt(uip_ethaddr.addr, management_vlan, false); + + print_string("MAC set to: "); + print_mac(uip_ethaddr.addr); + write_char('\n'); + print_string("Save the startup config to keep it across reboots\n"); +} + void parse_lag(void) { @@ -1694,6 +1786,8 @@ void cmd_parser(void) __banked igmp_show(); else print_string("Error: igmp on|off|show\n"); + } else if (cmd_compare(0, "mac")) { + parse_mac_cmd(); } else if (cmd_compare(0, "hostname")) { /* "hostname" alone reports the current name; "hostname " * sets it, sanitized to JSON-safe printable ASCII. A name with diff --git a/machine.c b/machine.c index 3ce13d33..dcc34844 100644 --- a/machine.c +++ b/machine.c @@ -247,6 +247,7 @@ __code const struct machine machine = { .led_mux = {0x00, 0x01, 0x04, 0x05, 0x08, 0x09, 0x0c, 0x09, 0x0d, 0x10, 0x11, 0x0e, 0x14, 0x11, 0x12, 0x15, 0x15, 0x16, 0x18, 0x19, 0x1a, 0x19, 0x1d, 0x1e, 0x1c, 0x1d, 0x20, 0x21}, + .mac_flash_offset = 0x1FC000, }; #elif defined MACHINE_SWGT024_V2_0_MANAGED diff --git a/rtl837x_common.h b/rtl837x_common.h index 0d4ccc01..e4b90fed 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -173,6 +173,7 @@ void reset_chip(void); void memcpy(__xdata void * __xdata dst, __xdata const void * __xdata src, uint16_t len); void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len); void memset(__xdata uint8_t *dst, __xdata uint8_t v, uint8_t len); +int memcmp(__xdata const void *a, __xdata const void *b, uint16_t len); uint16_t strlen(__code const char *s); uint16_t strlen_x(__xdata const char *s); uint16_t strtox(__xdata uint8_t *dst, __code const char *s); diff --git a/rtlplayground.c b/rtlplayground.c index 700df0b8..88993473 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -359,6 +359,19 @@ void memcpy(__xdata void * __xdata dst, __xdata const void * __xdata src, uint16 *d++ = *s++; } +int memcmp(__xdata const void *a, __xdata const void *b, uint16_t len) +{ + __xdata const uint8_t *x = a; + __xdata const uint8_t *y = b; + while (len--) { + if (*x != *y) + return *x - *y; + x++; + y++; + } + return 0; +} + void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len) { while (len--) From dc4fb5becc2210ccf2af5870cd449ae0fe10f84f Mon Sep 17 00:00:00 2001 From: gnacho Date: Thu, 10 Sep 2026 00:14:58 +0200 Subject: [PATCH 2/3] fix(parser): reject malformed MAC inputs and make print helpers banked parse_mac now accepts only a strict aa:bb:cc:dd:ee:ff (or '-' separators) form: each octet is exactly two hex digits, the 5 separators are a single consistent character, and nothing is allowed after the 6th octet. This rejects the malformed values the reviewer flagged (repeated separators, missing digits, trailing characters). A new inline hexval() helper does the digit-to-value conversion and can be reused by future commands. print_mac and print_ip are marked __banked to match cmd_parser(), which lives in codeseg BANK2 and is itself __banked, so callers across the code bank work correctly. cmd_parser.h is updated with the new prototypes and the __banked qualifier on print_ip. Verified with make MACHINE=KP_9000_9XHML_X_V3_1: the build links cleanly and the internal-RAM (DSEG) layout is unchanged against main; XSEG is unchanged and ROM grows by 184 bytes. A host-side test confirms parse_mac rejects the two malformed inputs from the review. --- cmd_parser.c | 66 +++++++++++++++++++++++++++++++++------------------- cmd_parser.h | 4 +++- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index d1a3e74a..b65ec18e 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -106,6 +106,18 @@ inline uint8_t isnumber(uint8_t l) return (l <= ('9'-'0')); } +/* Converts a single ASCII hex digit to its value, or 0xff if it is not a valid + * hex digit. Accepts '0'-'9', 'a'-'f' and 'A'-'F'. */ +inline uint8_t hexval(uint8_t c) +{ + if (isnumber(c)) + return c - '0'; + c |= 0x20; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return 0xff; +} + uint8_t cmd_compare(uint8_t start, __code uint8_t * cmd) { @@ -349,7 +361,7 @@ err: } // Prints an IPv4 address. -void print_ip(__xdata uint8_t * ptr) +void print_ip(__xdata uint8_t * ptr) __banked { uint8_t idx = 0; uint8_t num; @@ -365,7 +377,7 @@ void print_ip(__xdata uint8_t * ptr) } // Prints a MAC address. -void print_mac(__xdata uint8_t *ptr) +void print_mac(__xdata uint8_t *ptr) __banked { uint8_t idx = 0; uint8_t num; @@ -380,38 +392,44 @@ void print_mac(__xdata uint8_t *ptr) } } -// Parses a MAC address (12 hex digits, ':' or '-' separators optional) from -// cmd_buffer starting at idx into mac[6]. Returns the number of bytes -// consumed, or 0 on error. +// Parses a strict MAC address "aa:bb:cc:dd:ee:ff" (or "-" separators) from +// cmd_buffer starting at idx into mac[6]. Each of the 6 octets must be exactly +// two hex digits and the 5 separators must be the same single character, so +// malformed input like repeated separators ("aa::bb"), missing digits or a +// trailing hex digit ("aa:bb:cc:dd:ee:ff:0") is rejected. Returns the number of +// bytes consumed, or 0 on error. uint8_t parse_mac(uint8_t idx, __xdata uint8_t *mac) { + uint8_t sep = 0; uint8_t b = 0; - uint8_t hi = 0xff; /* 0xff = waiting for the high nibble */ uint8_t c; + uint8_t hi; + uint8_t lo; while (b < 6) { + hi = hexval(cmd_buffer[idx++]); + lo = hexval(cmd_buffer[idx++]); + if (hi == 0xff || lo == 0xff) + return 0; + mac[b++] = (hi << 4) | lo; + + if (b == 6) + break; c = cmd_buffer[idx]; - if (c == NUL || c == ' ' || c == '\r' || c == '\n') - break; + if (c != ':' && c != '-') + return 0; + if (sep == 0) + sep = c; + else if (c != sep) + return 0; idx++; - if (c == ':' || c == '-') - continue; - c |= 0x20; - if (c >= '0' && c <= '9') - c -= '0'; - else if (c >= 'a' && c <= 'f') - c -= 'a' - 10; - else - break; - if (hi == 0xff) { - hi = c; - } else { - mac[b++] = (hi << 4) | c; - hi = 0xff; - } } - if (b != 6 || hi != 0xff) + + // No trailing characters: the next byte must be a separator-like EOF. + c = cmd_buffer[idx]; + if (c != NUL && c != ' ') return 0; + return idx; } diff --git a/cmd_parser.h b/cmd_parser.h index 0afd0327..d8ad8832 100644 --- a/cmd_parser.h +++ b/cmd_parser.h @@ -13,7 +13,9 @@ void cmd_tokenize(void) __banked; void cmd_parser(void) __banked; void execute_config(void) __banked; void execute_commands(__xdata uint8_t *p) __banked; -void print_ip(__xdata uint8_t *ptr); +void print_ip(__xdata uint8_t *ptr) __banked; +void print_mac(__xdata uint8_t *ptr) __banked; +uint8_t parse_mac(uint8_t idx, __xdata uint8_t *mac); void print_sw_version(void) __banked; void clear_command_history(void) __banked; From 12fb6fd80996b1109d83860da79076fe277f3763 Mon Sep 17 00:00:00 2001 From: gnacho Date: Fri, 11 Sep 2026 23:42:01 +0200 Subject: [PATCH 3/3] cmd_parser: parse mac into a global result buffer parse_mac() now writes to the global mac_parse_result[6] and takes only the index, like the other parsers (ip[4]). This drops the local __xdata buffer and the now-unused hexval() helper. Build (KP_9000_9XHML_X_V3_1): ROM 110302 -> 110193 bytes, XSEG and internal RAM unchanged. --- cmd_parser.c | 97 ++++++++++++++++++++++++---------------------------- cmd_parser.h | 1 - 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index b65ec18e..bf7b5042 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -66,6 +66,7 @@ __xdata uint8_t cmd_available; __xdata char save_cmd; __xdata uint8_t ip[4]; +__xdata uint8_t mac_parse_result[6]; /* Scratch for parse_syslog(), in xdata: a local here would take internal RAM * the linker has none of. */ @@ -106,19 +107,6 @@ inline uint8_t isnumber(uint8_t l) return (l <= ('9'-'0')); } -/* Converts a single ASCII hex digit to its value, or 0xff if it is not a valid - * hex digit. Accepts '0'-'9', 'a'-'f' and 'A'-'F'. */ -inline uint8_t hexval(uint8_t c) -{ - if (isnumber(c)) - return c - '0'; - c |= 0x20; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - return 0xff; -} - - uint8_t cmd_compare(uint8_t start, __code uint8_t * cmd) { if (cmd_words_len == 0 || start > (cmd_words_len - 1)) { @@ -392,45 +380,52 @@ void print_mac(__xdata uint8_t *ptr) __banked } } -// Parses a strict MAC address "aa:bb:cc:dd:ee:ff" (or "-" separators) from -// cmd_buffer starting at idx into mac[6]. Each of the 6 octets must be exactly -// two hex digits and the 5 separators must be the same single character, so -// malformed input like repeated separators ("aa::bb"), missing digits or a -// trailing hex digit ("aa:bb:cc:dd:ee:ff:0") is rejected. Returns the number of -// bytes consumed, or 0 on error. -uint8_t parse_mac(uint8_t idx, __xdata uint8_t *mac) +// Parses a MAC address "aa:bb:cc:dd:ee:ff" (":" or "-" separators) from +// cmd_buffer starting at idx into the global mac_parse_result[6]. Each octet +// must be exactly two hex digits with a separator between octets and nothing +// trailing, so repeated separators, missing digits or a trailing hex digit are +// rejected. Returns the number of bytes consumed, or 0 on error. +uint8_t parse_mac(uint8_t idx) { - uint8_t sep = 0; uint8_t b = 0; - uint8_t c; - uint8_t hi; - uint8_t lo; + uint8_t c = ':'; - while (b < 6) { - hi = hexval(cmd_buffer[idx++]); - lo = hexval(cmd_buffer[idx++]); - if (hi == 0xff || lo == 0xff) - return 0; - mac[b++] = (hi << 4) | lo; - - if (b == 6) - break; - c = cmd_buffer[idx]; + do { if (c != ':' && c != '-') - return 0; - if (sep == 0) - sep = c; - else if (c != sep) - return 0; - idx++; - } + goto err; - // No trailing characters: the next byte must be a separator-like EOF. - c = cmd_buffer[idx]; - if (c != NUL && c != ' ') - return 0; + __bit again = true; + uint8_t v = 0; - return idx; + while(1) { + c = cmd_buffer[idx++]; + c -= '0'; + if (c > 9) { + c |= 0x20; + c += '0' - 'a'; + if (c > 5) + goto err; + c += 10; + } + // force swap-instr. + v = (v << 4) | (v >> 4); + v |= c; + + if (again) + again = false; + else + break; + } + mac_parse_result[b++] = v; + + c = cmd_buffer[idx++]; + } while(b < 6); + + if (c == ' ' || c == NUL) + return idx; + +err: + return 0; } // Sets the management MAC from a "mac " command (or prints @@ -441,23 +436,21 @@ uint8_t parse_mac(uint8_t idx, __xdata uint8_t *mac) // on every boot (execute_config runs before the final static L2 entry). void parse_mac_cmd(void) { - __xdata uint8_t mac[6]; - if (cmd_words_len == 1) { print_string("Current MAC: "); print_mac(uip_ethaddr.addr); write_char('\n'); return; } - if (cmd_words_len != 2 || !parse_mac(cmd_words_b[1], mac)) { + if (cmd_words_len != 2 || !parse_mac(cmd_words_b[1])) { print_string("Error: mac []\n"); return; } - if ((mac[0] & 0x03) || ((mac[0] | mac[1] | mac[2]) == 0)) { + if ((mac_parse_result[0] & 0x03) || ((mac_parse_result[0] | mac_parse_result[1] | mac_parse_result[2]) == 0)) { print_string("refusing: must be unicast, globally administered\n"); return; } - if (memcmp(mac, uip_ethaddr.addr, 6) == 0) { + if (memcmp(mac_parse_result, uip_ethaddr.addr, 6) == 0) { print_string("MAC unchanged\n"); return; } @@ -465,7 +458,7 @@ void parse_mac_cmd(void) // Swap the static management entry over to the new address: remove the // old one (still in uip_ethaddr) before overwriting it. port_l2_static_mgmt(uip_ethaddr.addr, management_vlan, true); - memcpy(uip_ethaddr.addr, mac, 6); + memcpy(uip_ethaddr.addr, mac_parse_result, 6); port_l2_static_mgmt(uip_ethaddr.addr, management_vlan, false); print_string("MAC set to: "); diff --git a/cmd_parser.h b/cmd_parser.h index d8ad8832..7564b458 100644 --- a/cmd_parser.h +++ b/cmd_parser.h @@ -15,7 +15,6 @@ void execute_config(void) __banked; void execute_commands(__xdata uint8_t *p) __banked; void print_ip(__xdata uint8_t *ptr) __banked; void print_mac(__xdata uint8_t *ptr) __banked; -uint8_t parse_mac(uint8_t idx, __xdata uint8_t *mac); void print_sw_version(void) __banked; void clear_command_history(void) __banked;