diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md
index f212913d8..7aae695f9 100644
--- a/CHANGELOG_LATEST.md
+++ b/CHANGELOG_LATEST.md
@@ -28,6 +28,7 @@ This release is based on the latest Espressif/Arduino core version 3. It brings
- Ethernet MAC address changed with the new SDK, breaking DHCP reservations (currently disabled)
- "IPv4 nameserver" showed an IPv6 address when IPv6 was in use
- the Network status page showed at most one IPv6 address and could hide the IPv4 DNS server entirely. It only read the link-local address, so a dual-stack host's ULA and global addresses were never reported, and it only read two of the three DNS slots, which are shared between IPv4 and IPv6. It now lists every address the interface holds, each labelled with its IPv6 scope, and every configured DNS server [#3246](https://github.com/emsesp/EMS-ESP32/issues/3246)
+- the Gateway was only ever reported for IPv4, so on a dual-stack network the IPv6 default router was missing from the Network status page, `show system` and the MQTT `info` topic. Routers are learnt from router advertisements rather than configured, so all of them are now listed [#3246](https://github.com/emsesp/EMS-ESP32/issues/3246)
- `show system` had the same gaps, reporting a link-local IPv6 address only and a single IPv4 DNS server. The WiFi and Ethernet sections now list every IPv6 address with its scope, and every DNS server labelled with its own family [#3246](https://github.com/emsesp/EMS-ESP32/issues/3246)
- the Access Point status page reported whichever interface happened to be active, so with the AP down it showed the station's IP and MAC address under an "Inactive" heading. It now always reports the soft-AP's own details, and shows `none` when the AP isn't running
- the MQTT `info` topic published no addresses at all when connected over Ethernet, and over WiFi only an IPv4 address plus a link-local IPv6 one. Both interfaces now report their MAC, IPv4 address and gateway, every IPv6 address and every DNS server. Note that `IPv6 address` is now an array, and `IPv4 DNS Server` is replaced by a `DNS Server` array that covers both families [#3246](https://github.com/emsesp/EMS-ESP32/issues/3246)
diff --git a/interface/package.json b/interface/package.json
index e174c0392..d20b5736e 100644
--- a/interface/package.json
+++ b/interface/package.json
@@ -42,11 +42,11 @@
"@eslint/js": "^10.0.1",
"@preact/preset-vite": "^2.10.6",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
- "@types/node": "^26.6.1",
+ "@types/node": "^26.6.2",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"concurrently": "^10.0.5",
- "eslint": "^10.10.0",
+ "eslint": "^10.11.0",
"eslint-config-prettier": "^10.1.8",
"etag": "^1.8.1",
"mime-types": "^3.0.2",
diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml
index 394afc10a..5bd89b220 100644
--- a/interface/pnpm-lock.yaml
+++ b/interface/pnpm-lock.yaml
@@ -211,16 +211,16 @@ importers:
devDependencies:
'@eslint/js':
specifier: ^10.0.1
- version: 10.0.1(eslint@10.10.0(supports-color@10.2.2))
+ version: 10.0.1(eslint@10.11.0(supports-color@10.2.2))
'@preact/preset-vite':
specifier: ^2.10.6
- version: 2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))
+ version: 2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))
'@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2
version: 6.0.2(prettier@3.9.8)(supports-color@10.2.2)
'@types/node':
- specifier: ^26.6.1
- version: 26.6.1
+ specifier: ^26.6.2
+ version: 26.6.2
'@types/react':
specifier: ^19.3.0
version: 19.3.0
@@ -231,11 +231,11 @@ importers:
specifier: ^10.0.5
version: 10.0.5
eslint:
- specifier: ^10.10.0
- version: 10.10.0(supports-color@10.2.2)
+ specifier: ^10.11.0
+ version: 10.11.0(supports-color@10.2.2)
eslint-config-prettier:
specifier: ^10.1.8
- version: 10.1.8(eslint@10.10.0(supports-color@10.2.2))
+ version: 10.1.8(eslint@10.11.0(supports-color@10.2.2))
etag:
specifier: ^1.8.1
version: 1.8.1
@@ -256,13 +256,13 @@ importers:
version: 6.0.3
typescript-eslint:
specifier: ^8.70.0
- version: 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ version: 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
vite:
specifier: ^8.3.0
- version: 8.3.0(@types/node@26.6.1)(terser@5.51.2)
+ version: 8.3.0(@types/node@26.6.2)(terser@5.51.2)
vite-plugin-imagemin:
specifier: ^0.6.1
- version: 0.6.1(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))
+ version: 0.6.1(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))
packages:
@@ -332,8 +332,8 @@ packages:
resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
engines: {node: '>=6.9.0'}
- '@babel/parser@7.29.8':
- resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
+ '@babel/parser@7.29.9':
+ resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -843,8 +843,8 @@ packages:
resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==}
deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed.
- '@types/node@26.6.1':
- resolution: {integrity: sha512-VqGJBMCtdhqkBUCcBLvywI0NJ+KLuVzgNnlBUNFOQjqVxzo2lxLUNg1DSey8+u2u6ktswSAxg+s68QLzWHNOuA==}
+ '@types/node@26.6.2':
+ resolution: {integrity: sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -1339,8 +1339,8 @@ packages:
duplexer3@0.1.5:
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
- electron-to-chromium@1.5.431:
- resolution: {integrity: sha512-AAVihz2YwJeOdAynX8MUtqpvjY0gaiARp/7+r4kwqgzqXMrfG1qDN7vwT80uxAoOQRPcZH8t73EI+CA/1OJN8A==}
+ electron-to-chromium@1.5.433:
+ resolution: {integrity: sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -1525,8 +1525,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- eslint@10.10.0:
- resolution: {integrity: sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw==}
+ eslint@10.11.0:
+ resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -3105,8 +3105,8 @@ packages:
yaml:
optional: true
- which-typed-array@1.1.23:
- resolution: {integrity: sha512-JMh8aK+1B/0bk/YNupICmH5MgCq6yNLKYQUfsZtSDLLCCmxUkHjUY+oOlIa90lO7lHN1woAfpenLdlKpXy9o+A==}
+ which-typed-array@1.1.24:
+ resolution: {integrity: sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==}
engines: {node: '>= 0.4'}
which@1.3.1:
@@ -3197,7 +3197,7 @@ snapshots:
'@babel/helper-compilation-targets': 7.29.7
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
'@babel/helpers': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/template': 7.29.7
'@babel/traverse': 7.29.8(supports-color@10.2.2)
'@babel/types': 7.29.8
@@ -3212,7 +3212,7 @@ snapshots:
'@babel/generator@7.29.8':
dependencies:
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/types': 7.29.8
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
@@ -3261,7 +3261,7 @@ snapshots:
'@babel/template': 7.29.7
'@babel/types': 7.29.8
- '@babel/parser@7.29.8':
+ '@babel/parser@7.29.9':
dependencies:
'@babel/types': 7.29.8
@@ -3293,7 +3293,7 @@ snapshots:
'@babel/template@7.29.7':
dependencies:
'@babel/code-frame': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/types': 7.29.8
'@babel/traverse@7.29.8(supports-color@10.2.2)':
@@ -3301,7 +3301,7 @@ snapshots:
'@babel/code-frame': 7.29.7
'@babel/generator': 7.29.8
'@babel/helper-globals': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/template': 7.29.7
'@babel/types': 7.29.8
debug: 4.4.3(supports-color@10.2.2)
@@ -3411,9 +3411,9 @@ snapshots:
'@esbuild/linux-loong64@0.14.54':
optional: true
- '@eslint-community/eslint-utils@4.10.1(eslint@10.10.0(supports-color@10.2.2))':
+ '@eslint-community/eslint-utils@4.10.1(eslint@10.11.0(supports-color@10.2.2))':
dependencies:
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@@ -3434,9 +3434,9 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
- '@eslint/js@10.0.1(eslint@10.10.0(supports-color@10.2.2))':
+ '@eslint/js@10.0.1(eslint@10.11.0(supports-color@10.2.2))':
optionalDependencies:
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
'@eslint/object-schema@3.0.5': {}
@@ -3596,19 +3596,19 @@ snapshots:
'@popperjs/core@2.11.8': {}
- '@preact/preset-vite@2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))':
+ '@preact/preset-vite@2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))':
dependencies:
'@babel/core': 7.29.7(supports-color@10.2.2)
'@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
'@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
- '@prefresh/vite': 2.4.12(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))
+ '@prefresh/vite': 2.4.12(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))
'@rollup/pluginutils': 5.4.0
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7(supports-color@10.2.2))
debug: 4.4.3(supports-color@10.2.2)
magic-string: 0.30.21
picocolors: 1.1.1
- vite: 8.3.0(@types/node@26.6.1)(terser@5.51.2)
- vite-prerender-plugin: 0.5.13(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))
+ vite: 8.3.0(@types/node@26.6.2)(terser@5.51.2)
+ vite-prerender-plugin: 0.5.13(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))
zimmerframe: 1.1.5
transitivePeerDependencies:
- preact
@@ -3625,7 +3625,7 @@ snapshots:
'@prefresh/utils@1.2.1': {}
- '@prefresh/vite@2.4.12(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2))':
+ '@prefresh/vite@2.4.12(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2))':
dependencies:
'@babel/core': 7.29.7(supports-color@10.2.2)
'@prefresh/babel-plugin': 0.5.4(@babel/core@7.29.7(supports-color@10.2.2))
@@ -3633,7 +3633,7 @@ snapshots:
'@prefresh/utils': 1.2.1
'@rollup/pluginutils': 4.2.1
preact: 10.29.8
- vite: 8.3.0(@types/node@26.6.1)(terser@5.51.2)
+ vite: 8.3.0(@types/node@26.6.2)(terser@5.51.2)
transitivePeerDependencies:
- supports-color
@@ -3711,7 +3711,7 @@ snapshots:
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.8)(supports-color@10.2.2)':
dependencies:
'@babel/generator': 7.29.8
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/traverse': 7.29.8(supports-color@10.2.2)
'@babel/types': 7.29.8
javascript-natural-sort: 0.7.1
@@ -3729,7 +3729,7 @@ snapshots:
'@types/glob@7.2.0':
dependencies:
'@types/minimatch': 6.0.0
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
'@types/imagemin-gifsicle@7.0.4':
dependencies:
@@ -3758,19 +3758,19 @@ snapshots:
'@types/imagemin@7.0.1':
dependencies:
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
'@types/json-schema@7.0.15': {}
'@types/keyv@3.1.4':
dependencies:
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
'@types/minimatch@6.0.0':
dependencies:
minimatch: 10.2.6
- '@types/node@26.6.1':
+ '@types/node@26.6.2':
dependencies:
undici-types: 8.9.0
@@ -3792,21 +3792,21 @@ snapshots:
'@types/responselike@1.0.3':
dependencies:
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
'@types/svgo@2.6.4':
dependencies:
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
- '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
+ '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.70.0
- '@typescript-eslint/type-utils': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
- '@typescript-eslint/utils': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/type-utils': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.70.0
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
ignore: 7.0.9
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
@@ -3814,14 +3814,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
+ '@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.70.0
'@typescript-eslint/types': 8.70.0
'@typescript-eslint/typescript-estree': 8.70.0(supports-color@10.2.2)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.70.0
debug: 4.4.3(supports-color@10.2.2)
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -3844,13 +3844,13 @@ snapshots:
dependencies:
typescript: 6.0.3
- '@typescript-eslint/type-utils@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
+ '@typescript-eslint/type-utils@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.70.0
'@typescript-eslint/typescript-estree': 8.70.0(supports-color@10.2.2)(typescript@6.0.3)
- '@typescript-eslint/utils': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
debug: 4.4.3(supports-color@10.2.2)
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
@@ -3873,13 +3873,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
+ '@typescript-eslint/utils@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.10.1(eslint@10.10.0(supports-color@10.2.2))
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@10.2.2))
'@typescript-eslint/scope-manager': 8.70.0
'@typescript-eslint/types': 8.70.0
'@typescript-eslint/typescript-estree': 8.70.0(supports-color@10.2.2)(typescript@6.0.3)
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -4012,7 +4012,7 @@ snapshots:
dependencies:
baseline-browser-mapping: 2.11.25
caniuse-lite: 1.0.30001810
- electron-to-chromium: 1.5.431
+ electron-to-chromium: 1.5.433
node-releases: 2.0.56
update-browserslist-db: 1.3.3(browserslist@4.29.0)
@@ -4377,7 +4377,7 @@ snapshots:
duplexer3@0.1.5: {}
- electron-to-chromium@1.5.431: {}
+ electron-to-chromium@1.5.433: {}
emoji-regex@10.6.0: {}
@@ -4491,9 +4491,9 @@ snapshots:
escape-string-regexp@4.0.0: {}
- eslint-config-prettier@10.1.8(eslint@10.10.0(supports-color@10.2.2)):
+ eslint-config-prettier@10.1.8(eslint@10.11.0(supports-color@10.2.2)):
dependencies:
- eslint: 10.10.0(supports-color@10.2.2)
+ eslint: 10.11.0(supports-color@10.2.2)
eslint-scope@9.1.2:
dependencies:
@@ -4506,9 +4506,9 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
- eslint@10.10.0(supports-color@10.2.2):
+ eslint@10.11.0(supports-color@10.2.2):
dependencies:
- '@eslint-community/eslint-utils': 4.10.1(eslint@10.10.0(supports-color@10.2.2))
+ '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@10.2.2))
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5(supports-color@10.2.2)
'@eslint/config-helpers': 0.7.0
@@ -5058,7 +5058,7 @@ snapshots:
is-typed-array@1.1.15:
dependencies:
- which-typed-array: 1.1.23
+ which-typed-array: 1.1.24
is-utf8@0.2.1: {}
@@ -5940,13 +5940,13 @@ snapshots:
dependencies:
typescript: 6.0.3
- typescript-eslint@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3):
+ typescript-eslint@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
- '@typescript-eslint/parser': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
'@typescript-eslint/typescript-estree': 8.70.0(supports-color@10.2.2)(typescript@6.0.3)
- '@typescript-eslint/utils': 8.70.0(eslint@10.10.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
- eslint: 10.10.0(supports-color@10.2.2)
+ '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
+ eslint: 10.11.0(supports-color@10.2.2)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -5991,7 +5991,7 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
- vite-plugin-imagemin@0.6.1(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2)):
+ vite-plugin-imagemin@0.6.1(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2)):
dependencies:
'@types/imagemin': 7.0.1
'@types/imagemin-gifsicle': 7.0.4
@@ -6016,11 +6016,11 @@ snapshots:
imagemin-webp: 6.1.0
jpegtran-bin: 6.0.1
pathe: 0.2.0
- vite: 8.3.0(@types/node@26.6.1)(terser@5.51.2)
+ vite: 8.3.0(@types/node@26.6.2)(terser@5.51.2)
transitivePeerDependencies:
- supports-color
- vite-prerender-plugin@0.5.13(vite@8.3.0(@types/node@26.6.1)(terser@5.51.2)):
+ vite-prerender-plugin@0.5.13(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2)):
dependencies:
kolorist: 1.8.0
magic-string: 0.30.21
@@ -6028,9 +6028,9 @@ snapshots:
simple-code-frame: 1.3.0
source-map: 0.7.6
stack-trace: 1.0.0
- vite: 8.3.0(@types/node@26.6.1)(terser@5.51.2)
+ vite: 8.3.0(@types/node@26.6.2)(terser@5.51.2)
- vite@8.3.0(@types/node@26.6.1)(terser@5.51.2):
+ vite@8.3.0(@types/node@26.6.2)(terser@5.51.2):
dependencies:
lightningcss: 1.33.0
picomatch: 4.0.7
@@ -6038,11 +6038,11 @@ snapshots:
rolldown: 1.2.9
tinyglobby: 0.2.17
optionalDependencies:
- '@types/node': 26.6.1
+ '@types/node': 26.6.2
fsevents: 2.3.3
terser: 5.51.2
- which-typed-array@1.1.23:
+ which-typed-array@1.1.24:
dependencies:
available-typed-arrays: 1.0.7
call-bind: 1.0.9
diff --git a/interface/src/app/status/NetworkStatus.tsx b/interface/src/app/status/NetworkStatus.tsx
index 97b9c66e0..95defba48 100644
--- a/interface/src/app/status/NetworkStatus.tsx
+++ b/interface/src/app/status/NetworkStatus.tsx
@@ -89,13 +89,18 @@ const AddressList = ({
));
};
-const isSet = (ip?: string) => !!ip && ip !== '0.0.0.0' && ip !== '::';
+const isSet = (ip?: string): ip is string => !!ip && ip !== '0.0.0.0' && ip !== '::';
const ipAddresses = ({ local_ip, ipv6 }: NetworkStatusType) => [
...(isSet(local_ip) ? [{ value: local_ip }] : []),
...(ipv6 ?? []).map(({ address, scope }) => ({ value: address, note: scope }))
];
+const gateways = ({ gateway_ip, gateway_ipv6 }: NetworkStatusType) => [
+ ...(isSet(gateway_ip) ? [{ value: gateway_ip }] : []),
+ ...(gateway_ipv6 ?? []).filter(isSet).map((value) => ({ value }))
+];
+
const dnsServers = ({ dns }: NetworkStatusType) =>
(dns ?? []).filter(isSet).map((value) => ({ value }));
@@ -230,7 +235,7 @@ const NetworkStatus = () => {
}
/>
diff --git a/interface/src/types/network.ts b/interface/src/types/network.ts
index f76c9479b..e9362f3be 100644
--- a/interface/src/types/network.ts
+++ b/interface/src/types/network.ts
@@ -36,6 +36,7 @@ export interface NetworkStatusType {
channel: number;
subnet_mask: string;
gateway_ip?: string;
+ gateway_ipv6?: string[];
dns?: string[];
hostname: string;
reconnect_count: number;
diff --git a/mock-api/pnpm-lock.yaml b/mock-api/pnpm-lock.yaml
index 27c733e28..8f42cb36a 100644
--- a/mock-api/pnpm-lock.yaml
+++ b/mock-api/pnpm-lock.yaml
@@ -202,8 +202,8 @@ packages:
resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
engines: {node: '>=6.9.0'}
- '@babel/parser@7.29.8':
- resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
+ '@babel/parser@7.29.9':
+ resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -318,7 +318,7 @@ snapshots:
'@babel/generator@7.29.8':
dependencies:
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/types': 7.29.8
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
@@ -330,14 +330,14 @@ snapshots:
'@babel/helper-validator-identifier@7.29.7': {}
- '@babel/parser@7.29.8':
+ '@babel/parser@7.29.9':
dependencies:
'@babel/types': 7.29.8
'@babel/template@7.29.7':
dependencies:
'@babel/code-frame': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/types': 7.29.8
'@babel/traverse@7.29.8':
@@ -345,7 +345,7 @@ snapshots:
'@babel/code-frame': 7.29.7
'@babel/generator': 7.29.8
'@babel/helper-globals': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/template': 7.29.7
'@babel/types': 7.29.8
debug: 4.4.3
@@ -376,7 +376,7 @@ snapshots:
'@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.8)':
dependencies:
'@babel/generator': 7.29.8
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/traverse': 7.29.8
'@babel/types': 7.29.8
javascript-natural-sort: 0.7.1
diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts
index b561e486d..3fb7b8cdd 100644
--- a/mock-api/restServer.ts
+++ b/mock-api/restServer.ts
@@ -624,6 +624,7 @@ const network_status = {
channel: 11,
subnet_mask: '255.255.255.0',
gateway_ip: '10.10.10.1',
+ gateway_ipv6: ['fe80::1', 'fe80::2'],
dns: ['10.10.10.1', 'fd00:1234:5678::1'],
hostname: 'ems-esp',
reconnect_count: 1
diff --git a/src/ESP32React/NetworkStatus.cpp b/src/ESP32React/NetworkStatus.cpp
index 9c945249a..129ce5fa5 100644
--- a/src/ESP32React/NetworkStatus.cpp
+++ b/src/ESP32React/NetworkStatus.cpp
@@ -24,6 +24,20 @@ void add_dns(JsonObject root, const NetworkInterface & netif) {
}
}
+void add_gateways(JsonObject root, NetworkInterface & netif) {
+ if (IPUtils::isSet(netif.gatewayIP())) {
+ root["gateway_ip"] = netif.gatewayIP().toString();
+ }
+ auto found = emsesp::Network::ipv6_gateways(netif);
+ if (found.empty()) {
+ return;
+ }
+ JsonArray gateways = root["gateway_ipv6"].to();
+ for (const auto & ip : found) {
+ gateways.add(ip.toString());
+ }
+}
+
} // namespace
NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) {
@@ -53,9 +67,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
root["local_ip"] = ETH.localIP().toString();
root["mac_address"] = ETH.macAddress();
root["subnet_mask"] = ETH.subnetMask().toString();
- if (IPUtils::isSet(ETH.gatewayIP())) {
- root["gateway_ip"] = ETH.gatewayIP().toString();
- }
+ add_gateways(root, ETH);
add_ipv6(root, ETH);
add_dns(root, ETH);
} else if (wifi_status == WL_CONNECTED) {
@@ -67,9 +79,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
root["channel"] = WiFi.channel();
root["reconnect_count"] = emsesp::EMSESP::network_.getNetworkReconnects();
root["subnet_mask"] = WiFi.subnetMask().toString();
- if (IPUtils::isSet(WiFi.gatewayIP())) {
- root["gateway_ip"] = WiFi.gatewayIP().toString();
- }
+ add_gateways(root, WiFi.STA);
add_ipv6(root, WiFi.STA);
add_dns(root, WiFi.STA);
}
diff --git a/src/core/network.cpp b/src/core/network.cpp
index 4ecda77aa..f2e6e816f 100644
--- a/src/core/network.cpp
+++ b/src/core/network.cpp
@@ -20,6 +20,10 @@
#include "emsesp.h"
+#if CONFIG_LWIP_IPV6 && !defined(EMSESP_STANDALONE)
+#include
+#endif
+
#ifndef NETWORK_FALLBACK_AP_SSID
#define NETWORK_FALLBACK_AP_SSID "ems-esp"
#endif
@@ -214,6 +218,54 @@ std::vector Network::dns_servers(const NetworkInterface & netif) {
}
return servers;
}
+
+#if CONFIG_LWIP_IPV6
+namespace {
+
+struct Ipv6GatewayCtx {
+ int netif_index;
+ std::vector * gateways;
+};
+
+// runs in the TCP/IP task, where neighbour discovery owns default_router_list
+esp_err_t collect_ipv6_gateways(void * ctx) {
+ auto * args = static_cast(ctx);
+ for (uint8_t i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
+ const struct nd6_neighbor_cache_entry * router = default_router_list[i].neighbor_entry;
+ // a null neighbour entry is how nd6_tmr() marks a slot as unused or expired
+ if (router == nullptr || netif_get_index(router->netif) != args->netif_index) {
+ continue;
+ }
+ const ip6_addr_t * addr = &router->next_hop_address;
+ args->gateways->push_back(IPAddress(IPv6, reinterpret_cast(addr->addr), ip6_addr_zone(addr)));
+ }
+ return ESP_OK;
+}
+
+} // namespace
+
+// Every IPv6 default router known on the interface. Routers are learnt from router advertisements
+// rather than configured, so there can be more than one and none of them appear in the IPv4-only
+// esp_netif_ip_info_t.gw that gatewayIP() returns.
+std::vector Network::ipv6_gateways(NetworkInterface & netif) {
+ std::vector gateways;
+
+ const int netif_index = esp_netif_get_netif_impl_index(netif.netif());
+ if (netif_index < 0) {
+ return gateways;
+ }
+
+ gateways.reserve(LWIP_ND6_NUM_ROUTERS); // allocate before we're inside the TCP/IP task
+ Ipv6GatewayCtx args = {netif_index, &gateways};
+ esp_netif_tcpip_exec(collect_ipv6_gateways, &args);
+
+ return gateways;
+}
+#else
+std::vector Network::ipv6_gateways(NetworkInterface &) {
+ return {};
+}
+#endif
#endif
// get the soft-AP's own MAC address, empty when the AP isn't running
diff --git a/src/core/network.h b/src/core/network.h
index e88b8a1a9..a98e77357 100644
--- a/src/core/network.h
+++ b/src/core/network.h
@@ -150,6 +150,7 @@ class Network {
static std::vector ipv6_addresses(NetworkInterface & netif);
static std::vector dns_servers(const NetworkInterface & netif);
+ static std::vector ipv6_gateways(NetworkInterface & netif);
#endif
void reconnect();
diff --git a/src/core/system.cpp b/src/core/system.cpp
index b867e8642..8416c5817 100644
--- a/src/core/system.cpp
+++ b/src/core/system.cpp
@@ -109,6 +109,9 @@ static void show_addresses(uuid::console::Shell & shell, NetworkInterface & neti
for (const auto & ipv6 : Network::ipv6_addresses(netif)) {
shell.printfln(" IPv6 address: %s (%s)", uuid::printable_to_string(ipv6.ip).c_str(), ipv6.scope);
}
+ for (const auto & gateway : Network::ipv6_gateways(netif)) {
+ shell.printfln(" IPv6 gateway: %s", uuid::printable_to_string(gateway).c_str());
+ }
}
// Add the full set of addresses for an interface to a JSON document, both families.
@@ -132,6 +135,14 @@ static void add_addresses(JsonDocument & doc, NetworkInterface & netif) {
addresses.add(uuid::printable_to_string(ipv6.ip));
}
}
+
+ auto gateways = Network::ipv6_gateways(netif);
+ if (!gateways.empty()) {
+ JsonArray routers = doc["IPv6 gateway"].to();
+ for (const auto & ip : gateways) {
+ routers.add(uuid::printable_to_string(ip));
+ }
+ }
}
#endif