From be82f5527736d6121f04357b459da281a595ab04 Mon Sep 17 00:00:00 2001 From: SerinaNya <34389622+SerinaNya@users.noreply.github.com> Date: Fri, 28 Aug 2026 23:14:04 +0800 Subject: [PATCH] feat(srv6)!: add node-info update invalidation Publish node metadata through Tiny and Full queries, synchronize Swing and web topology details, and document the updated protocol workflow. BREAKING CHANGE: RouterInfo no longer carries device names; peers must use Tiny node-info queries. --- AGENTS.md | 118 ++----- dashboard | 2 +- src/klalb_en_US.properties | 1 + src/klalb_zh_CN.properties | 5 +- .../cloud/network/klalb/KLALBController.java | 202 ++++++++++-- .../network/klalb/ui/KLALBStateGUI3.java | 121 ++++--- .../network/klalb/ui/NetworkGraphPanel.java | 195 +++++++++++- .../klalb/ui/NodeInformationPanel.java | 112 ++++--- .../network/klalb/web/KLALBWebServer.java | 301 ++++++++++++++++-- .../cloud/network/srv6/JsonDataPacket.java | 12 +- .../network/srv6/KLALBNodeInformation.java | 18 +- .../network/srv6/KLALBRoutingProtocol.java | 170 +++++++--- .../srv6/KLALBRoutingProtocolAPIClient.java | 16 +- .../srv6/KLALBRoutingProtocolAPIServer.java | 35 +- .../srv6/KLALBRoutingProtocolJsonData.java | 38 ++- .../srv6/KLALBRoutingProtocolPacket.java | 34 +- .../kne/cloud/network/srv6/RouterInfo.java | 51 +-- .../cloud/network/srv6/RouterInfoPacket.java | 72 ++++- 18 files changed, 1130 insertions(+), 373 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 16377ee..7d962ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,101 +1,53 @@ -# AGENTS.md +# KLALB Repository Guide -KLALB ("KLALB Decentralized SRv6 Network") — Java load-balancing/tunnel system that merges multiple WAN links into one virtual IPv6/SRv6 network. Version constant lives in `src/org/kne/cloud/network/klalb/CONST.java`. Protocol specs and manuals are the Chinese `.docx` files in the repo root. +KLALB is a Java SRv6/load-balancing system. The executable entrypoint is `org.kne.cloud.network.klalb.KLALBMain`; runtime configuration is `klalb-config.json` in the repository root. -## Build & run +## Build And Run -No Maven/Gradle. Plain Eclipse/IntelliJ project: dependencies are vendored jars in `lib/`, output goes to `bin/` (gitignored). When adding a jar, update **both** `.classpath` and `KLALB.iml`. - -Compile (`javac` is NOT on PATH — use the full JDK path; `-encoding UTF-8` is mandatory because sources contain Chinese text): +- This is a plain Eclipse/IntelliJ Java project: sources are `src/`, vendored dependencies are `lib/`, and output is `bin/`. There is no Maven or Gradle. +- `.classpath` targets `JavaSE-25`. Sources contain Chinese text, so manual compilation must use UTF-8: ```powershell & "C:\Program Files\Zulu\zulu-25\bin\javac.exe" -encoding UTF-8 -cp "lib/*" -d bin (Get-ChildItem -Recurse src -Filter *.java | ForEach-Object FullName) ``` -Warnings about `ThreadTool` varargs / deprecated `finalize` are pre-existing and expected — success = exit code 0. After recompiling, restart the running app (IDE-debugged JVMs keep old classes). - -Run from the repo root — CWD matters: -- reads `klalb-config.json` from CWD -- loads native libs from CWD: `tuntap4j.dll/.so/.dylib`, `wintun.dll`, `fastcopy.dll` (TUN device support) -- classpath must include `src` as well as `bin`: i18n bundles (`/klalb_*.properties`) and images (`/assets/*`) are classpath resources that Eclipse copies to `bin` but manual `javac` does not +- Run from the repository root. Manual `javac` does not copy resources, so keep `src` on the runtime classpath: ```powershell -java --enable-native-access=ALL-UNNAMED "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED" -cp "bin;src;lib/*" org.kne.cloud.network.klalb.KLALBMain +& "C:\Program Files\Zulu\zulu-25\bin\java.exe" --enable-native-access=ALL-UNNAMED "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED" -cp "bin;src;lib/*" org.kne.cloud.network.klalb.KLALBMain ``` -IDE metadata targets JDK 26 (`jdk-26.0.1`); the tree also compiles cleanly on JDK 25. `.classpath` now references the standard container `JavaSE-25` — an execution-environment spec that any JDK ≥25 satisfies, so it works unchanged on JDK 26 machines too (the original named `jdk-26.0.1` VM broke VS Code import on machines without it). `.vscode/settings.json` maps `JavaSE-25` to the locally installed Adoptium JDK; register every installed JDK there when adding another one. Keep compiler compliance ≤25 (`.settings` pins 19) so both JDKs stay usable. +- The native libraries and `klalb-config.json` are resolved from the current directory. Restart a running JVM after recompiling. +- TUN creation normally needs elevation. For non-admin UI/routing checks, set `"enableTUN": false`. +- Current full compilation emits 11 pre-existing varargs/deprecation warnings; exit code `0` is success. -Runtime gotchas (all verified): -- On JDK 25, `KNEOptimize.jar`'s `FastLib` reflects into `jdk.internal.misc.Unsafe`; without the two JVM flags above it throws `InaccessibleObjectException` at startup (app still runs). -- Creating the SRv6 TUN adapter (`WintunCreateAdapter`) requires an elevated shell; without admin rights it logs "创建虚拟网卡失败" and continues with only the `inLoopBack` interface — links/bridges still work. -- To disable TUN creation completely (e.g. for non-admin UI/routing testing), set `"enableTUN": false` in `klalb-config.json` or toggle off "启用 TUN 虚拟网卡" in GUI/Web settings. -- Routing broadcast (`RouterInfo`) transmits `deviceName`, which topology and node overview panels display. `deviceDescription` is NOT broadcast — it only leaves the node in full node-info query responses (see srv6 API below); `ExtraRoutes` remain local controller configs. +## Dashboard + +- `dashboard/` is a Git submodule. Commit dashboard changes inside it, then update the parent repository's submodule pointer. +- Run frontend commands from `dashboard/` with pnpm: + +```powershell +pnpm install --frozen-lockfile +pnpm lint +pnpm typecheck +pnpm build +pnpm dev +``` + +- `pnpm build` runs `tsc -b` then Vite and writes `dashboard/dist`, which the Java web server hosts. Vite development proxies `/api` to `http://127.0.0.1:4665`. +- Add shadcn components through `pnpm dlx shadcn@latest add `; do not hand-create replacements for installed shadcn primitives. ## Verification -No test suite, no CI. Classes named `*Test*` (`nathole/`, `ntp/`) are manual `main()` harnesses requiring real network peers. Practical check = compile succeeds + app launches. +- There is no CI or automated test suite. `*Test*` classes are manual harnesses that require real network peers. +- For Java changes, compile and launch the app. For dashboard changes, run `pnpm typecheck` and `pnpm build`. -## Architecture +## Important Boundaries -- Entrypoint `org.kne.cloud.network.klalb.KLALBMain`: load config → build `KLALBProxySystem` → open Swing GUI (`KLALBStateGUI3`) unless `"nogui": true` → start `KLALBWebServer` (if `"webUI": true` or web server enabled, default port `4665`) → interactive console (`help`, `links-state`, `route`, `kperf`, ...). -- `org.kne.cloud.network.klalb.web.KLALBWebServer` — built-in HTTP/SSE server (JDK `HttpServer`): - - API endpoints: `/api/status`, `/api/events` (SSE stream, 200ms intervals), `/api/links`, `/api/links/action`, `/api/links/reconnect`, `/api/routing-table`, `/api/nodes` (topology graph), `/api/node-info?address=` (on-demand full node info), `/api/config`. - - Static file hosting / SPA fallback: serves `dashboard/dist/` assets directly. -- `org.kne.cloud.network` — generic socket framework: `VirtualSocket*` hierarchy, `SocketBridge` port-forwarding proxies, `ProtocolDetector` (multi-protocol mux on one port), `MultiProtocolSocketAddress` = URI-style addresses (`tcp://`, `udp://`, `kltp://`, `ntp://`) dispatched through the `SocketType` registry. -- `...network.klalb` — app core: `KLALBController` (the virtual SRv6 network), `KLALBRemoteLink` (WAN lines), `*Packet` wire-format classes, virtual socket implementations. -- `...network.congestion` — pluggable congestion control (BBR, Vegas2, DCTCP...), chosen via `"congestionAlgorithm"` in config. -- `...network.kltp` — custom reliable transport protocol (packets/streams). -- `...network.ipv6`, `...network.srv6` — packet codecs, route table, Dijkstra path computation. -- **Node-info query API** (`...network.srv6`, JSON datagrams on `KLALBRoutingProtocol.DEFAULT_PORT=1001`): `KLALBRoutingProtocolAPIServer/Client` speak two request types — - - `nodeinfotinyreq/resp` → device name ONLY; never gated by any flag (name is public via broadcast anyway). - - `nodeinfofullreq/resp` → externalEndpoints + deviceName + deviceDescription. `denyExternalEndpointQuery=true` hides ONLY the endpoint list (`data=null`); name/description still answer. - - GUI rule: opening `NodeInformationPanel` = Full query; use `requestNodeInfoTiny` for lightweight/background lookups. Legacy `openlines*` message types were removed — mixed-version meshes get silence, so upgrade the whole network together. - - `JsonDataPacket` stores its UTF-8 payload length in a 2-byte header field: keep every JSON message under 64 KiB. -- `...network.frpc` — frp client integration. -- `...klalb.ui` — all Swing UI code. - -## Frontend (Dashboard) - -Located in `dashboard/`: -- **Git layout**: `dashboard/` is a separate git repo wired in as a submodule (own origin on `git.code.cq.cn`). Commit frontend changes inside `dashboard/` first, then bump the submodule pointer in the parent repo — parent-repo commits alone do not capture them. -- **Stack**: Vite + React 19 + TypeScript + Tailwind CSS v4 + `@base-ui/react` (style: `base-nova`, icons: `lucide-react`, toasts: `@base-ui/react/toast`). -- **Routing**: Hash-based routing (`#/overview`, `#/connections`, `#/routing-table`, `#/topology`, `#/settings`) for seamless SPA hosting under Java `KLALBWebServer`. -- **Package Manager**: `pnpm` (run all commands from `dashboard/` directory). -- **Component installation**: **Must** use CLI via `pnpm dlx shadcn@latest add ` (e.g. `pnpm dlx shadcn@latest add alert card badge toast`). Never create or fake shadcn components manually. Non-shadcn libs (`@xyflow/react`, `d3-force`) are installed via plain `pnpm add`. -- **Commands**: - - `pnpm dev` — Start Vite dev server (proxies `/api` to backend `http://127.0.0.1:4665`). - - `pnpm build` — Typecheck and build SPA to `dashboard/dist` (which Java `KLALBWebServer` serves directly). - - `pnpm lint` / `pnpm typecheck` — Verification. -- **Pages & data flow**: - - Overview / Connections read the SSE stream (`use-klalb-sse.ts`, 200ms pushes of status + links). - - Settings loads/saves `/api/config` (`use-klalb-config.ts`); save payload must keep legacy field aliases alongside new names for compatibility. - - Routing table polls `/api/routing-table` every 1s (`use-routing-table.ts`); `cost` is delay-derived and displayed in milliseconds. - - Topology polls `/api/nodes` every 1s (`use-topology.ts`) — SSE does NOT carry topology. - - Selecting a topology node queries `/api/node-info`; remote node descriptions require a full SRv6 node-info request and can time out after 3s. - - Topology layout: `d3-force` headless simulation (recomputed only when node/edge structure changes) rendered by `@xyflow/react` with custom `device-node` / `link-edge` components in `src/components/topology/`. - - React hooks lint rule forbids `setState` synchronously inside effects — initialize form state via component `key` remount + lazy `useState(() => ...)` initializers (see `SettingsForm` pattern). - -## Config - -`klalb-config.json` is an array of items discriminated by their `"Type"` field. Adding a new item type requires a `KLALBConfigItem` subclass **plus** new cases in both `KLALBConfigItem.getDefaultJsonDeserializer()` and `getDefaultJsonSerializer()`; unknown types are preserved as `UnknownKLALBConfigItem`. Any Gson instance handling config must register these adapters via `registerToGsonBuilder` (see `KLALBProxySystem`). - -Key controller config fields: -- `externalEndpoints` / `autoConnections`: published vs auto-connect endpoint lists (renamed from `openConnections`, which itself replaced legacy `LineTable`; the old name was a developer naming mistake — these addresses are this node's externally published endpoints, not "connections"). -- `ntpServers`: time server list (replaces `ntpServerTable`). -- `denyExternalEndpointQuery` / `denyExternalEndpointBroadcast`: safety flags — the query flag hides ONLY the external-endpoint list in full node-info responses (device name/description still answer; Tiny queries are never gated), the broadcast flag disables LAN multicast discovery (renamed from `denyConnectionQuery` / `denyConnectionBroadcast`, which replaced `denyLineTableQuery` / `denyLineTableBroadcast`). -- `enableTUN`: boolean flag for TUN interface creation (`"TUNName"` configures device name). -- `webListen`: Web API listen address, normally `http://0.0.0.0:4665`; legacy `webPort` is accepted on load/API input. - -Legacy JSON keys are still accepted on load: `KLALBConfigItem.getDefaultJsonDeserializer()` normalizes old key names (`openConnections`/`LineTable`, `denyConnectionQuery`, `denyLineTable*`, ...) before reflective deserialization (manual rewrite because gson-2.1 has no `@SerializedName(alternate=...)`), and `handleConfig` in the web server accepts them too. New saves always write canonical names. - -Gson quirks: -- **gson-2.1 (vendored) is ancient**: its `JSON_ELEMENT` adapter factory only matches exact `JsonElement.class`, NOT subclasses. Calling `gson.toJson(Object)` with a runtime `JsonObject`/`JsonArray` reflectively serializes the internal field as `{"members": {...}}`. `KLALBWebServer.sendJsonResponse` guards against this by using `JsonElement.toString()` for JsonElement instances — keep that guard when adding new response paths. SSE avoids the issue entirely via `JsonObject.toString()`. -- `/api/config` GET/POST is parsed field-by-field in `KLALBWebServer.handleConfig` (NOT whole-object Gson reflection) because polymorphic fields (`List`, `List`) break reflective mapping. Keep new config fields in sync there, accepting both legacy and new JSON key names. -- `InetAddress`, `MultiProtocolSocketAddress`, and `KLALBConfigItem` custom adapters are registered on the shared Gson in `KLALBProxySystem`; the web server reuses that instance via `proxySystem.getGson()`. -- Saving via web API persists through `KLALBProxySystem.saveConfigToFile()` (GUI save consumer takes precedence when present). - -## Conventions - -- Sources are UTF-8; comments, log/UI strings, and commit messages are largely Chinese. -- UI strings go through `UIEnv.getRsb().getString(...)`; add keys to **both** `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`. -- `client.cfg`, `server.cfg`, `linetable.txt` at the root are example line-table/port-rule files loaded via the GUI file picker — not hardwired paths. +- `KLALBConfigItem` is a polymorphic JSON array keyed by `Type`. Adding a type requires a subclass and cases in both default config serializer and deserializer; unknown types must remain preserved. +- `/api/config` is field-by-field parsing, not whole-object Gson mapping. Keep legacy key aliases in sync with new fields. +- Vendored Gson is `2.1`: responses that are `JsonElement` instances must be serialized with `JsonElement.toString()`, not reflective `gson.toJson(Object)`. +- UI strings use `UIEnv.getRsb()`; add keys to both `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`. +- `KLALBController.PublishedNodeInfo` is the thread-safe source for Tiny/Full node-info responses. Publish name, description, external endpoints, and Extra Routes through the controller method so snapshots and Tiny/Full update flags stay consistent. +- `RouterInfo` no longer carries a device name. Its wire format retains an empty legacy UTF slot and `RouterInfoPacket` has optional Tiny/Full invalidation flags. Treat codec changes as compatibility work: preserve old-reader behavior and review a whole-mesh rollout. +- Full node-info carries `extraRoutes` separately from the endpoint `data` list. Keep absent fields compatible with older peers. diff --git a/dashboard b/dashboard index 89474b9..0b705f9 160000 --- a/dashboard +++ b/dashboard @@ -1 +1 @@ -Subproject commit 89474b9e91791b8efe098a13171e6ff962ad7ba1 +Subproject commit 0b705f9ba0008540099e307bbb1e3e6944d7aa71 diff --git a/src/klalb_en_US.properties b/src/klalb_en_US.properties index e2bf3d3..02a5e59 100644 --- a/src/klalb_en_US.properties +++ b/src/klalb_en_US.properties @@ -7,6 +7,7 @@ devicename=Device name devicedescription=Device description dnsserver=DNS server extraroutes=Extra routes +noextraroutes=No extra routes asnumber=AS number tcplistening=TCP listening udplistening=UDP listening diff --git a/src/klalb_zh_CN.properties b/src/klalb_zh_CN.properties index 9c46648..fc1b624 100644 --- a/src/klalb_zh_CN.properties +++ b/src/klalb_zh_CN.properties @@ -6,7 +6,8 @@ ipv6addr=IPv6地址 devicename=设备名称 devicedescription=设备描述 dnsserver=DNS服务器 -extraroutes=额外路由 +extraroutes=额外路由 +noextraroutes=暂无额外路由 asnumber=AS号码 tcplistening=TCP监听端口 udplistening=UDP监听端口 @@ -116,4 +117,4 @@ webapisettings=Web API 设置 enablewebapi=启用 Web API weblistenaddr=Web API 监听地址:端口 invaildweblistenaddr=无效的 Web API 监听地址:端口 -enabletun=启用TUN虚拟网卡 \ No newline at end of file +enabletun=启用TUN虚拟网卡 diff --git a/src/org/kne/cloud/network/klalb/KLALBController.java b/src/org/kne/cloud/network/klalb/KLALBController.java index 432fca2..ebbbc61 100644 --- a/src/org/kne/cloud/network/klalb/KLALBController.java +++ b/src/org/kne/cloud/network/klalb/KLALBController.java @@ -10,12 +10,14 @@ import java.net.NetworkInterface; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Enumeration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; +import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.Timer; import java.util.TimerTask; @@ -76,7 +78,50 @@ public class KLALBController { new HashMapTimestampMonitor(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW), new HashMapTimestampMonitor(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW)); - private List externalEndpoints = new ArrayList<>(); + private List externalEndpoints = new ArrayList<>(); + private List configuredExternalEndpoints = new ArrayList<>(); + private List discoveredExternalEndpoints = new ArrayList<>(); + + public static final class PublishedNodeInfo { + private final String deviceName; + private final String deviceDescription; + private final List externalEndpoints; + private final List extraRoutes; + private final long fullRevision; + + private PublishedNodeInfo(String deviceName, String deviceDescription, + List externalEndpoints, List extraRoutes, long fullRevision) { + this.deviceName = deviceName; + this.deviceDescription = deviceDescription; + this.externalEndpoints = Collections.unmodifiableList( + new ArrayList(externalEndpoints)); + this.extraRoutes = Collections.unmodifiableList(new ArrayList(extraRoutes)); + this.fullRevision = fullRevision; + } + + public String getDeviceName() { + return deviceName; + } + + public String getDeviceDescription() { + return deviceDescription; + } + + public List getExternalEndpoints() { + return externalEndpoints; + } + + public List getExtraRoutes() { + return extraRoutes; + } + + public long getFullRevision() { + return fullRevision; + } + } + + private volatile PublishedNodeInfo publishedNodeInfo = new PublishedNodeInfo(null, null, + Collections.emptyList(), Collections.emptyList(), 0L); private List listensSocketAddress = new CopyOnWriteArrayList<>(); @@ -125,7 +170,8 @@ public class KLALBController { @Override public void run() { try { - List localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress(); + List localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress(); + List currentDiscoveredExternalEndpoints = new ArrayList<>(); for (InetAddress inetAddress : localaddress) { for (Iterator iterator = listensSocketAddress.iterator(); iterator.hasNext();) { @@ -137,11 +183,9 @@ public class KLALBController { MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(tcpl.getProtocol(), inetAddress.getHostAddress(), tcpl.getPort()); // System.out.println(bind); - synchronized (externalEndpoints) { - if (!externalEndpoints.contains(bind)) { - externalEndpoints.add(bind); - } - } + if (!currentDiscoveredExternalEndpoints.contains(bind)) { + currentDiscoveredExternalEndpoints.add(bind); + } } } catch (UnknownHostException e) { // TODO 自动生成的 catch 块 @@ -149,9 +193,15 @@ public class KLALBController { } } - } - - lineslock.writeLock().lock(); + } + synchronized (externalEndpoints) { + if (!discoveredExternalEndpoints.equals(currentDiscoveredExternalEndpoints)) { + discoveredExternalEndpoints = currentDiscoveredExternalEndpoints; + publishDiscoveredExternalEndpointLocked(); + } + } + + lineslock.writeLock().lock(); try { @@ -298,8 +348,8 @@ public class KLALBController { private boolean checkIsSelf(MultiProtocolSocketAddress inetAddress) throws UnknownHostException { - return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress() - || externalEndpoints.contains(inetAddress); + return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress() + || publishedNodeInfo.getExternalEndpoints().contains(inetAddress); } private boolean checkIsSelfLocator(InetAddress inetAddress) { @@ -401,9 +451,104 @@ public class KLALBController { } - public List getExternalEndpoints() { - return externalEndpoints; - } + public List getExternalEndpoints() { + return publishedNodeInfo.getExternalEndpoints(); + } + + public List getExternalEndpointsSnapshot() { + return new ArrayList(publishedNodeInfo.getExternalEndpoints()); + } + + public PublishedNodeInfo getPublishedNodeInfo() { + return publishedNodeInfo; + } + + private List createEffectiveExternalEndpointsLocked() { + List effective = new ArrayList(); + for (MultiProtocolSocketAddress endpoint : configuredExternalEndpoints) { + if (!effective.contains(endpoint)) { + effective.add(endpoint); + } + } + for (MultiProtocolSocketAddress endpoint : discoveredExternalEndpoints) { + if (!effective.contains(endpoint)) { + effective.add(endpoint); + } + } + return effective; + } + + private void publishNodeInfoLocked(String deviceName, String deviceDescription, + List configEndpoints, List extraRoutes) { + configuredExternalEndpoints = configEndpoints == null + ? new ArrayList() + : new ArrayList(configEndpoints); + List publishedExtraRoutes = extraRoutes == null + ? new ArrayList() : new ArrayList(extraRoutes); + List effective = createEffectiveExternalEndpointsLocked(); + PublishedNodeInfo previous = publishedNodeInfo; + boolean nameChanged = !Objects.equals(previous.getDeviceName(), deviceName); + boolean fullChanged = !Objects.equals(previous.getDeviceDescription(), deviceDescription) + || !previous.getExternalEndpoints().equals(effective) + || !previous.getExtraRoutes().equals(publishedExtraRoutes); + + externalEndpoints.clear(); + externalEndpoints.addAll(effective); + if (srv6Router != null) { + srv6Router.setDeviceName(deviceName); + } + long fullRevision = previous.getFullRevision() + (fullChanged ? 1L : 0L); + publishedNodeInfo = new PublishedNodeInfo(deviceName, deviceDescription, effective, publishedExtraRoutes, + fullRevision); + if (routingProtocol != null) { + if (nameChanged) { + routingProtocol.announceNodeInfoUpdate( + org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED); + } + if (fullChanged) { + routingProtocol.announceNodeInfoUpdate( + org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED); + } + } + } + + public void publishNodeInfo(String deviceName, String deviceDescription, + List configEndpoints, List extraRoutes) { + synchronized (externalEndpoints) { + if (configItem != null) { + configItem.setDeviceName(deviceName); + configItem.setDeviceDescription(deviceDescription); + configItem.setExternalEndpoints(new ArrayList(configEndpoints == null + ? Collections.emptyList() : configEndpoints)); + configItem.setExtraRoutes(new ArrayList(extraRoutes == null + ? Collections.emptyList() : extraRoutes)); + } + publishNodeInfoLocked(deviceName, deviceDescription, configEndpoints, extraRoutes); + } + } + + private void publishDiscoveredExternalEndpointLocked() { + List effective = createEffectiveExternalEndpointsLocked(); + if (new HashSet(effective) + .equals(new HashSet(externalEndpoints))) { + return; + } + externalEndpoints.clear(); + externalEndpoints.addAll(effective); + PublishedNodeInfo previous = publishedNodeInfo; + publishedNodeInfo = new PublishedNodeInfo(previous.getDeviceName(), previous.getDeviceDescription(), effective, + previous.getExtraRoutes(), previous.getFullRevision() + 1L); + if (routingProtocol != null) { + routingProtocol.announceNodeInfoUpdate( + org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED); + } + } + + public void replaceExternalEndpoints(List endpoints) { + PublishedNodeInfo published = publishedNodeInfo; + publishNodeInfo(published.getDeviceName(), published.getDeviceDescription(), endpoints, + published.getExtraRoutes()); + } public IPv6AddressGroup getSelf() { @@ -602,10 +747,9 @@ public class KLALBController { } } - private String generateExternalEndpointsString() { - StringBuilder sbd = new StringBuilder(); - for (Iterator iterator = externalEndpoints.iterator(); iterator.hasNext();) { - MultiProtocolSocketAddress klalbRemoteLine = (MultiProtocolSocketAddress) iterator.next(); + private String generateExternalEndpointsString() { + StringBuilder sbd = new StringBuilder(); + for (MultiProtocolSocketAddress klalbRemoteLine : publishedNodeInfo.getExternalEndpoints()) { sbd.append(klalbRemoteLine.toString()); sbd.append('\n'); } @@ -764,8 +908,11 @@ public class KLALBController { } } }).start(); - loadSRv6ProtocolStack(selfg, enableVirtualAdapter); - loadController(); + loadSRv6ProtocolStack(selfg, enableVirtualAdapter); + if (configItem == null) { + publishNodeInfo(srv6Router.getDeviceName(), null, null, null); + } + loadController(); } public KLALBController(boolean enableVirtualAdapter, List dnsaddr) { @@ -801,10 +948,9 @@ public class KLALBController { getIpv6Router().setASN(vasn); } - List linele = configItem.getExternalEndpoints(); - if (linele != null) { - getExternalEndpoints().addAll(linele); - } + List linele = configItem.getExternalEndpoints(); + publishNodeInfo(configItem.getDeviceName(), configItem.getDeviceDescription(), linele, + configItem.getExtraRoutes()); List linetoc = configItem.getAutoConnections(); if (linetoc != null) { linetoc.forEach((aline) -> { diff --git a/src/org/kne/cloud/network/klalb/ui/KLALBStateGUI3.java b/src/org/kne/cloud/network/klalb/ui/KLALBStateGUI3.java index 45735cb..b110b94 100644 --- a/src/org/kne/cloud/network/klalb/ui/KLALBStateGUI3.java +++ b/src/org/kne/cloud/network/klalb/ui/KLALBStateGUI3.java @@ -5,9 +5,10 @@ import java.awt.datatransfer.StringSelection; import java.awt.event.*; import java.io.IOException; import java.net.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Timer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Timer; import java.util.TimerTask; import java.util.function.Consumer; import javax.imageio.ImageIO; @@ -26,7 +27,7 @@ import org.kne.cloud.network.ipv6.IPv6NetworkLink; import org.kne.cloud.network.klalb.*; import org.kne.cloud.network.klalb.ui.GraphPanel.GraphNode; import org.kne.cloud.network.monitor.LinkStatus; -import org.kne.cloud.network.srv6.SRv6Router; +import org.kne.cloud.network.srv6.SRv6Router; import org.kne.ui.XFrame; import org.kne.ui.YScrollPane; @@ -1061,8 +1062,19 @@ public class KLALBStateGUI3 extends XFrame { } for (KLALBConfigItem item : config) { - if (item instanceof KLALBControllerConfigItem) { - KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item; + if (item instanceof KLALBControllerConfigItem) { + KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item; + String oldDeviceName = kck.getDeviceName(); + String oldDeviceDescription = kck.getDeviceDescription(); + List oldExternalEndpoints = kck.getExternalEndpoints() == null + ? null + : new ArrayList(kck.getExternalEndpoints()); + List oldExtraRoutes = kck.getExtraRoutes() == null + ? null : new ArrayList(kck.getExtraRoutes()); + String newDeviceName; + String newDeviceDescription; + List newExtraRoutes; + List newExternalEndpoints; // 保存语言设置 kck.setLanguage(((Language) comboLang.getSelectedItem()).name()); @@ -1070,23 +1082,19 @@ public class KLALBStateGUI3 extends XFrame { kck.setNogui(nogui.isSelected()); // 保存设备名称 - String dnametext = deviceNameSet.getText().trim(); - if (dnametext.equals("")) { - kck.setDeviceName(null); - } else { - kck.setDeviceName(dnametext); - } - if (kcontroller != null && kcontroller.getIpv6Router() != null) { - kcontroller.getIpv6Router().setDeviceName(kck.getDeviceName()); - } - - // 保存设备描述 - String ddesctext = deviceDescriptionSet.getText().trim(); - if (ddesctext.equals("")) { - kck.setDeviceDescription(null); - } else { - kck.setDeviceDescription(ddesctext); - } + String dnametext = deviceNameSet.getText().trim(); + if (dnametext.equals("")) { + newDeviceName = null; + } else { + newDeviceName = dnametext; + } + // 保存设备描述 + String ddesctext = deviceDescriptionSet.getText().trim(); + if (ddesctext.equals("")) { + newDeviceDescription = null; + } else { + newDeviceDescription = ddesctext; + } // 保存IPv6地址 @@ -1141,7 +1149,7 @@ public class KLALBStateGUI3 extends XFrame { } } } - kck.setExtraRoutes(eroutes); + newExtraRoutes = eroutes; // 保存ASN String asntext = asnFieldSet.getText(); @@ -1232,7 +1240,7 @@ public class KLALBStateGUI3 extends XFrame { } } } - kck.setExternalEndpoints(iaddr1); + newExternalEndpoints = iaddr1; // 保存自动连接线路表 String[] splt11 = connectLineTabelSet.getText().split("\n"); @@ -1304,7 +1312,23 @@ public class KLALBStateGUI3 extends XFrame { kck.setNagleDelayTime(nagleDelayTime.getSlider().getValue()*100000L); - kck.setLinkNagleDelayTime(linkNagleDelayTime.getSlider().getValue()*100000L); + kck.setLinkNagleDelayTime(linkNagleDelayTime.getSlider().getValue()*100000L); + + boolean deviceNameChanged = !Objects.equals(oldDeviceName, newDeviceName); + boolean deviceDescriptionChanged = !Objects.equals(oldDeviceDescription, newDeviceDescription); + boolean externalEndpointsChanged = !Objects.equals(oldExternalEndpoints, newExternalEndpoints); + boolean extraRoutesChanged = !Objects.equals(oldExtraRoutes, newExtraRoutes); + if (deviceNameChanged || deviceDescriptionChanged || externalEndpointsChanged || extraRoutesChanged) { + if (kcontroller != null) { + kcontroller.publishNodeInfo(newDeviceName, newDeviceDescription, newExternalEndpoints, + newExtraRoutes); + } else { + kck.setDeviceName(newDeviceName); + kck.setDeviceDescription(newDeviceDescription); + kck.setExternalEndpoints(newExternalEndpoints); + kck.setExtraRoutes(newExtraRoutes); + } + } } } @@ -1403,20 +1427,22 @@ public class KLALBStateGUI3 extends XFrame { if (tsk5 != null) { tsk5.cancel(); } - tsk5 = new TimerTask() { - @Override - public void run() { - if (isVisible()) { - graph.loadNodes(); - for (int i = 0; i < 100; i++) { - graph.runPhy(); - } - graph.repaint(); - graph.revalidate(); - } - } - }; - t.scheduleAtFixedRate(tsk5, 1000, 1000); + tsk5 = new TimerTask() { + @Override + public void run() { + SwingUtilities.invokeLater(() -> { + if (isVisible()) { + graph.loadNodes(); + for (int i = 0; i < 100; i++) { + graph.runPhy(); + } + graph.repaint(); + graph.revalidate(); + } + }); + } + }; + t.scheduleAtFixedRate(tsk5, 0, 1000); } /** @@ -1761,9 +1787,18 @@ public class KLALBStateGUI3 extends XFrame { /** * 关闭窗口并清理资源 */ - public void close() { - setVisible(false); - if (tsk != null) { + public void close() { + for (int i = 0; i < tabbedPane.getTabCount(); i++) { + Component component = tabbedPane.getComponentAt(i); + if (component instanceof NodeInformationPanel) { + ((NodeInformationPanel) component).close(); + } + } + if (graph != null) { + graph.close(); + } + setVisible(false); + if (tsk != null) { tsk.cancel(); } if (st != null) { diff --git a/src/org/kne/cloud/network/klalb/ui/NetworkGraphPanel.java b/src/org/kne/cloud/network/klalb/ui/NetworkGraphPanel.java index cd8e656..eb832cb 100644 --- a/src/org/kne/cloud/network/klalb/ui/NetworkGraphPanel.java +++ b/src/org/kne/cloud/network/klalb/ui/NetworkGraphPanel.java @@ -8,28 +8,58 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.BiConsumer; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; import org.kne.cloud.network.MultiProtocolSocketAddress; import org.kne.cloud.network.ipv6.IPv6Address; import org.kne.cloud.network.klalb.KLALBController; -import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection; +import org.kne.cloud.network.srv6.KLALBNodeInformation; +import org.kne.cloud.network.srv6.KLALBRoutingProtocol; +import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient; +import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection; +import org.kne.cloud.network.srv6.RouterInfoPacket; public class NetworkGraphPanel extends GraphPanel { - private KLALBController controller; - private KLALBStateGUI3 klbgui; - + private KLALBController controller; + private KLALBStateGUI3 klbgui; + private final KLALBRoutingProtocolAPIClient nodeInfoClient; + private final KLALBRoutingProtocol nodeInfoRoutingProtocol; + private final BiConsumer nodeInfoUpdateListener; + private final Object nodeInfoLock = new Object(); + private final Map nodeNameCache = new HashMap(); + private final Map nodeNameCacheTimes = new HashMap(); + private final Map nodeInfoRequestTimes = new HashMap(); + private final Map nodeInfoRequestGenerations = new HashMap(); + private static final long NODE_INFO_REQUEST_TIMEOUT = 3000L; + private static final long NODE_INFO_CACHE_TTL = 60000L; + private long nextNodeInfoRequestGeneration; + private volatile boolean nodeInfoClosed; + public NetworkGraphPanel(KLALBController controller,KLALBStateGUI3 klbgui) { super(); this.controller = controller; this.klbgui=klbgui; + this.nodeInfoRoutingProtocol = controller.getIpv6Router().getKlalbRouteProtol(); + this.nodeInfoClient = new KLALBRoutingProtocolAPIClient(nodeInfoRoutingProtocol); + this.nodeInfoUpdateListener = (address, flags) -> { + if ((flags & RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED) != 0) { + SwingUtilities.invokeLater(() -> invalidateTinyNodeInfo(address)); + } + }; + nodeInfoRoutingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener); + cacheLocalNodeName(); } public NetworkGraphPanel(KLALBController kc) { @@ -153,17 +183,145 @@ public class NetworkGraphPanel extends GraphPanel { } /** - * 节点标签:显示广播得知的设备名称(若有)+IP地址 + * 节点标签:显示 Tiny API 查询的设备名称(若有)+IP地址 */ - private String getNodeText(IPv6Address address) { - StringBuilder sb=new StringBuilder(); - String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address); - if(dname!=null) - sb.append(dname).append('\n'); - sb.append(InetGraphNode.getText(address)); - return sb.toString(); - } + private String getNodeText(IPv6Address address) { + StringBuilder sb=new StringBuilder(); + String dname; + synchronized (nodeInfoLock) { + dname = nodeNameCache.get(address); + } + if(dname!=null&&!dname.isEmpty()) + sb.append(dname).append('\n'); + sb.append(InetGraphNode.getText(address)); + return sb.toString(); + } + private void cacheLocalNodeName() { + IPv6Address localAddress = controller.getIpv6Router().getLocator().getAddress(); + synchronized (nodeInfoLock) { + nodeNameCache.put(localAddress, controller.getIpv6Router().getDeviceName()); + nodeNameCacheTimes.put(localAddress, System.currentTimeMillis()); + } + } + + private void requestNodeInfoTiny(final IPv6Address address) { + final long requestTime = System.currentTimeMillis(); + final long requestGeneration; + synchronized (nodeInfoLock) { + if (nodeInfoClosed) + return; + Long cachedAt = nodeNameCacheTimes.get(address); + if (nodeNameCache.containsKey(address) && cachedAt != null + && requestTime - cachedAt < NODE_INFO_CACHE_TTL) + return; + nodeNameCache.remove(address); + nodeNameCacheTimes.remove(address); + Long previousRequestTime = nodeInfoRequestTimes.get(address); + if (previousRequestTime != null && requestTime - previousRequestTime < NODE_INFO_REQUEST_TIMEOUT) + return; + nodeInfoRequestTimes.put(address, requestTime); + requestGeneration = ++nextNodeInfoRequestGeneration; + nodeInfoRequestGenerations.put(address, requestGeneration); + } + try { + nodeInfoClient.requestNodeInfoTiny( + new InetSocketAddress(address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), + (info) -> SwingUtilities.invokeLater(() -> handleNodeInfo(address, requestGeneration, info))); + } catch (IOException e) { + synchronized (nodeInfoLock) { + if (nodeInfoRequestGenerations.get(address) != null + && nodeInfoRequestGenerations.get(address).longValue() == requestGeneration) { + nodeInfoRequestTimes.remove(address); + nodeInfoRequestGenerations.remove(address); + } + } + } + } + + private void handleNodeInfo(IPv6Address address, long requestGeneration, KLALBNodeInformation info) { + if (nodeInfoClosed) + return; + boolean currentRequest; + boolean nodeExists = getNodes().containsKey(address); + String deviceName = info == null ? "" : info.getDeviceName(); + if (deviceName == null || deviceName.isEmpty()) + deviceName = ""; + synchronized (nodeInfoLock) { + if (nodeInfoClosed) + return; + Long activeRequestGeneration = nodeInfoRequestGenerations.get(address); + currentRequest = activeRequestGeneration != null + && activeRequestGeneration.longValue() == requestGeneration; + if (currentRequest) { + nodeInfoRequestTimes.remove(address); + nodeInfoRequestGenerations.remove(address); + if (nodeExists) + nodeNameCache.put(address, deviceName); + else + nodeNameCache.remove(address); + if (nodeExists) + nodeNameCacheTimes.put(address, System.currentTimeMillis()); + else + nodeNameCacheTimes.remove(address); + } + } + if (currentRequest && nodeExists) { + ((InetGraphNode) getNodes().get(address)).updateLabel(); + repaint(); + } + } + + private void removeNodeInfoState(IPv6Address address) { + synchronized (nodeInfoLock) { + nodeNameCache.remove(address); + nodeNameCacheTimes.remove(address); + nodeInfoRequestTimes.remove(address); + nodeInfoRequestGenerations.remove(address); + } + } + + private void invalidateTinyNodeInfo(IPv6Address address) { + synchronized (nodeInfoLock) { + if (nodeInfoClosed) + return; + nodeNameCache.remove(address); + nodeNameCacheTimes.remove(address); + nodeInfoRequestTimes.remove(address); + nodeInfoRequestGenerations.remove(address); + } + InetGraphNode node = (InetGraphNode) getNodes().get(address); + if (node != null) { + node.updateLabel(); + } + repaint(); + requestNodeInfoTiny(address); + } + + public void close() { + synchronized (nodeInfoLock) { + if (nodeInfoClosed) + return; + nodeInfoClosed = true; + nodeNameCache.clear(); + nodeNameCacheTimes.clear(); + nodeInfoRequestTimes.clear(); + nodeInfoRequestGenerations.clear(); + } + nodeInfoRoutingProtocol.removeNodeInfoUpdateListener(nodeInfoUpdateListener); + nodeInfoClient.close(); + } + + @Override + public void removeNotify() { + super.removeNotify(); + SwingUtilities.invokeLater(() -> { + if (!isDisplayable() && getParent() == null) { + close(); + } + }); + } + private double nsPerPixel=5000L; private class InetGraphEdgeGroup extends GraphEdgeGroup{ public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) { @@ -181,6 +339,8 @@ public class NetworkGraphPanel extends GraphPanel { } protected void loadNodes() { Map addr= controller.getIpv6Router().getKlalbRouteProtol().getAddresses(); + IPv6Address localAddress = controller.getIpv6Router().getLocator().getAddress(); + cacheLocalNodeName(); Set ks=addr.keySet(); for (Iterator iterator = ks.iterator(); iterator.hasNext();) { IPv6Address inet6Address = (IPv6Address) iterator.next(); @@ -188,12 +348,15 @@ public class NetworkGraphPanel extends GraphPanel { Vector2 v2pos=super.getRandomPos(); getNodes().put(inet6Address,new InetGraphNode(inet6Address,Color.BLACK,v2pos.x,v2pos.y,inet6Address.equals(controller.getIpv6Router().getLocator().getAddress()))); } + if(!inet6Address.equals(localAddress)) + requestNodeInfoTiny(inet6Address); } Set kns=getNodes().keySet(); for (Iterator iterator = kns.iterator(); iterator.hasNext();) { IPv6Address inet6Address = (IPv6Address) iterator.next(); if(!addr.containsKey(inet6Address)) { iterator.remove(); + removeNodeInfoState(inet6Address); }else { ((InetGraphNode)getNodes().get(inet6Address)).updateLabel(); } diff --git a/src/org/kne/cloud/network/klalb/ui/NodeInformationPanel.java b/src/org/kne/cloud/network/klalb/ui/NodeInformationPanel.java index 248b63e..079315d 100644 --- a/src/org/kne/cloud/network/klalb/ui/NodeInformationPanel.java +++ b/src/org/kne/cloud/network/klalb/ui/NodeInformationPanel.java @@ -15,7 +15,7 @@ import javax.swing.JButton; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; -import javax.swing.JTextArea; +import javax.swing.JTextArea; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; @@ -32,8 +32,9 @@ import javax.swing.JMenuItem; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.ActionEvent; +import java.awt.event.MouseListener; +import java.awt.event.ActionEvent; +import java.util.concurrent.atomic.AtomicLong; public class NodeInformationPanel extends JPanel { private IPv6Address address; @@ -41,7 +42,11 @@ public class NodeInformationPanel extends JPanel { private KLALBController controller; private Image image; - private XDefaultListModel listModel=new XDefaultListModel<>(); + private XDefaultListModel listModel=new XDefaultListModel<>(); + private XDefaultListModel extraRoutesModel=new XDefaultListModel<>(); + private volatile boolean active=true; + private final AtomicLong fullInfoGeneration=new AtomicLong(); + private final java.util.function.BiConsumer nodeInfoUpdateListener; public KLALBController getController() { return controller; } @@ -73,16 +78,20 @@ public class NodeInformationPanel extends JPanel { overviewArea.setWrapStyleWord(true); overviewArea.setFont(UIEnv.getFont().deriveFont(14.0f)); overviewArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); - String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address); - String ddesc=null; - if(address.equals(controller.getIpv6Router().getLocator().getAddress())&&controller.getConfigItem()!=null) { - ddesc=controller.getConfigItem().getDeviceDescription(); - if(ddesc!=null&&ddesc.isEmpty()) { - ddesc=null; - } - } - overviewArea.setText(buildOverviewText(dname, ddesc)); - panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER); + overviewArea.setText(buildOverviewText(null, null)); + panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER); + + JPanel extraRoutesPanel = new JPanel(new BorderLayout()); + JList extraRoutesList = new JList(extraRoutesModel); + extraRoutesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + extraRoutesList.setFont(UIEnv.getFont().deriveFont(14.0f)); + extraRoutesPanel.add(new JScrollPane(extraRoutesList), BorderLayout.CENTER); + javax.swing.JLabel extraRoutesEmptyLabel=new javax.swing.JLabel(UIEnv.getRsb().getString("noextraroutes")); + extraRoutesEmptyLabel.setText("..."); + extraRoutesEmptyLabel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + extraRoutesEmptyLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + extraRoutesPanel.add(extraRoutesEmptyLabel, BorderLayout.SOUTH); + tabbedPane.addTab(UIEnv.getRsb().getString("extraroutes"), null, extraRoutesPanel, null); JPanel panel_1 = new JPanel(); panel_1.setLayout(new BorderLayout(0, 0)); @@ -163,28 +172,59 @@ public class NodeInformationPanel extends JPanel { panel_1.add(btnNewButton, BorderLayout.SOUTH); - client=new KLALBRoutingProtocolAPIClient(controller.getIpv6Router().getKlalbRouteProtol()); - try { - client.requestNodeInfoFull(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (info)->{ - listModel.clear(); - for (MultiProtocolSocketAddress multiProtocolSocketAddress : info.getOpenLines()) { - listModel.addElement(multiProtocolSocketAddress); - } - // 用对端返回的设备名称/描述更新概览(旧版本节点无该字段时保留原显示) - String dn=info.getDeviceName(); - if(dn==null||dn.isEmpty()) { - dn=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address); - } - String dd=info.getDeviceDescription(); - if(dd!=null&&dd.isEmpty()) { - dd=null; - } - overviewArea.setText(buildOverviewText(dn, dd)); - }); - } catch (IOException e) { - e.printStackTrace(); - } - } + KLALBRoutingProtocol routingProtocol=controller.getIpv6Router().getKlalbRouteProtol(); + client=new KLALBRoutingProtocolAPIClient(routingProtocol); + nodeInfoUpdateListener=(updatedAddress, flags) -> { + if(active && address.equals(updatedAddress) + && (flags & org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED) != 0) { + requestFullInfo(overviewArea, extraRoutesEmptyLabel); + } + }; + routingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener); + requestFullInfo(overviewArea, extraRoutesEmptyLabel); + } + + private void requestFullInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel) { + final long generation=fullInfoGeneration.incrementAndGet(); + try { + client.requestNodeInfoFull(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (info)->{ + if(!active || generation!=fullInfoGeneration.get()) return; + javax.swing.SwingUtilities.invokeLater(() -> { + if(!active || generation!=fullInfoGeneration.get()) return; + listModel.clear(); + if(info.getOpenLines()!=null) for (MultiProtocolSocketAddress item : info.getOpenLines()) listModel.addElement(item); + extraRoutesModel.clear(); + List routes=info.getExtraRoutes(); + if(routes!=null) for(String route : routes) extraRoutesModel.addElement(route); + extraRoutesEmptyLabel.setText(UIEnv.getRsb().getString("noextraroutes")); + extraRoutesEmptyLabel.setVisible(extraRoutesModel.isEmpty()); + String dd=info.getDeviceDescription(); + overviewArea.setText(buildOverviewText(info.getDeviceName(), dd==null||dd.isEmpty()?null:dd)); + }); + }); + } catch (IOException e) { + if(active) e.printStackTrace(); + } + } + + public synchronized void close() { + if (!active) + return; + active=false; + fullInfoGeneration.incrementAndGet(); + KLALBRoutingProtocol routingProtocol=controller.getIpv6Router().getKlalbRouteProtol(); + routingProtocol.removeNodeInfoUpdateListener(nodeInfoUpdateListener); + if(client!=null) { + client.close(); + client=null; + } + } + + @Override + public void removeNotify() { + close(); + super.removeNotify(); + } private String buildOverviewText(String dname,String ddesc) { StringBuilder sb=new StringBuilder(); diff --git a/src/org/kne/cloud/network/klalb/web/KLALBWebServer.java b/src/org/kne/cloud/network/klalb/web/KLALBWebServer.java index d967389..2a5dd59 100644 --- a/src/org/kne/cloud/network/klalb/web/KLALBWebServer.java +++ b/src/org/kne/cloud/network/klalb/web/KLALBWebServer.java @@ -7,6 +7,7 @@ import java.nio.file.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; import com.sun.net.httpserver.*; import com.google.gson.*; @@ -25,6 +26,7 @@ import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection; import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient; import org.kne.cloud.network.srv6.NeighborInfo; import org.kne.cloud.network.srv6.RouterInfo; +import org.kne.cloud.network.srv6.RouterInfoPacket; import org.kne.cloud.network.srv6.SRv6Router; /** @@ -35,14 +37,12 @@ public class KLALBWebServer { private final KLALBProxySystem proxySystem; private final MultiProtocolSocketAddress listen; private HttpServer server; + private ExecutorService httpExecutor; private final Gson gson; - private final ScheduledExecutorService sseExecutor = Executors.newSingleThreadScheduledExecutor(r -> { - Thread t = new Thread(r, "KLALB-Web-SSE"); - t.setDaemon(true); - return t; - }); + private ScheduledExecutorService sseExecutor = createSseExecutor(); private final Set sseClients = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final AtomicBoolean running = new AtomicBoolean(false); + private final Object configUpdateLock = new Object(); public KLALBWebServer(KLALBProxySystem proxySystem, int port) { this(proxySystem, new MultiProtocolSocketAddress("http", "0.0.0.0", port)); @@ -52,15 +52,37 @@ public class KLALBWebServer { this.proxySystem = proxySystem; this.listen = listen; this.gson = proxySystem.getGson(); + this.nodeInfoUpdateListener = (address, flags) -> { + if ((flags & RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED) != 0) { + nodeInfoFullRevisions.merge(address, 1L, Long::sum); + } + if ((flags & RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED) != 0) { + long lifecycleGeneration; + synchronized (this) { + lifecycleGeneration = nodeInfoLifecycleGeneration; + } + invalidateTinyDeviceName(address); + KLALBController kc = this.proxySystem.getKlalbController(); + if (kc != null && kc.getIpv6Router() != null + && kc.getIpv6Router().getKlalbRouteProtol() != null) { + refreshTinyDeviceNameAsync(kc, address, lifecycleGeneration); + } + } + }; } public synchronized void start() throws IOException { if (running.get()) return; + nodeInfoFullRevisionEpoch = UUID.randomUUID().toString(); + if (sseExecutor == null || sseExecutor.isShutdown()) { + sseExecutor = createSseExecutor(); + } InetSocketAddress socketAddress = "0.0.0.0".equals(listen.getHost()) ? new InetSocketAddress(listen.getPort()) : listen.getSocketAddress(); server = HttpServer.create(socketAddress, 0); - server.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); + ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); + server.setExecutor(executor); // API Contexts server.createContext("/api/status", this::handleStatus); @@ -76,7 +98,13 @@ public class KLALBWebServer { // Static Files / SPA Fallback Handler server.createContext("/", this::handleStatic); - server.start(); + try { + server.start(); + httpExecutor = executor; + } catch (RuntimeException e) { + executor.shutdownNow(); + throw e; + } running.set(true); startSseBroadcaster(); @@ -84,7 +112,11 @@ public class KLALBWebServer { } public synchronized void stop() { - if (!running.get()) return; + if (!running.get()) { + closeHttpExecutor(); + closeNodeInfoClient(); + return; + } running.set(false); sseExecutor.shutdownNow(); for (HttpExchange client : sseClients) { @@ -97,9 +129,18 @@ public class KLALBWebServer { server.stop(1); server = null; } + closeHttpExecutor(); + closeNodeInfoClient(); System.out.println("KLALB Web Dashboard stopped."); } + private void closeHttpExecutor() { + if (httpExecutor != null) { + httpExecutor.shutdownNow(); + httpExecutor = null; + } + } + public boolean isRunning() { return running.get(); } @@ -456,14 +497,160 @@ public class KLALBWebServer { } private KLALBRoutingProtocolAPIClient nodeInfoClient; + private static final long TINY_NAME_REQUEST_TIMEOUT_MS = 3000L; + private final ConcurrentMap tinyDeviceNameCache = new ConcurrentHashMap<>(); + private final ConcurrentMap tinyDeviceNameCacheTimes = new ConcurrentHashMap<>(); + private final Set tinyNameRequestsInFlight = ConcurrentHashMap.newKeySet(); + private final ConcurrentMap tinyNameRequestTimes = new ConcurrentHashMap<>(); + private final ConcurrentMap tinyNameRequestGenerations = new ConcurrentHashMap<>(); + private final ConcurrentMap nodeInfoFullRevisions = new ConcurrentHashMap<>(); + private final Object tinyNameStateLock = new Object(); + private static final long TINY_NAME_CACHE_TTL_MS = 60000L; + private long nextTinyNameRequestGeneration; + private KLALBRoutingProtocol nodeInfoRoutingProtocol; + private final BiConsumer nodeInfoUpdateListener; + private long nodeInfoLifecycleGeneration; + private volatile String nodeInfoFullRevisionEpoch = UUID.randomUUID().toString(); + + private static ScheduledExecutorService createSseExecutor() { + return Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "KLALB-Web-SSE"); + t.setDaemon(true); + return t; + }); + } private synchronized KLALBRoutingProtocolAPIClient getNodeInfoClient(KLALBController kc) { + return getNodeInfoClient(kc, -1L); + } + + private synchronized KLALBRoutingProtocolAPIClient getNodeInfoClient(KLALBController kc, + long expectedLifecycleGeneration) { + if (!running.get() || (expectedLifecycleGeneration >= 0 + && expectedLifecycleGeneration != nodeInfoLifecycleGeneration) + || kc == null || kc.getIpv6Router() == null + || kc.getIpv6Router().getKlalbRouteProtol() == null) { + return null; + } if (nodeInfoClient == null) { - nodeInfoClient = new KLALBRoutingProtocolAPIClient(kc.getIpv6Router().getKlalbRouteProtol()); + nodeInfoRoutingProtocol = kc.getIpv6Router().getKlalbRouteProtol(); + nodeInfoClient = new KLALBRoutingProtocolAPIClient(nodeInfoRoutingProtocol); + nodeInfoRoutingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener); } return nodeInfoClient; } + private synchronized void closeNodeInfoClient() { + nodeInfoLifecycleGeneration++; + if (nodeInfoRoutingProtocol != null) { + nodeInfoRoutingProtocol.removeNodeInfoUpdateListener(nodeInfoUpdateListener); + nodeInfoRoutingProtocol = null; + } + if (nodeInfoClient != null) { + nodeInfoClient.close(); + nodeInfoClient = null; + } + synchronized (tinyNameStateLock) { + tinyDeviceNameCache.clear(); + tinyDeviceNameCacheTimes.clear(); + tinyNameRequestsInFlight.clear(); + tinyNameRequestTimes.clear(); + tinyNameRequestGenerations.clear(); + } + } + + private String getTinyDeviceName(KLALBController kc, IPv6Address addr) { + return getTinyDeviceName(kc, addr, -1L); + } + + private String getTinyDeviceName(KLALBController kc, IPv6Address addr, long lifecycleGeneration) { + String cachedName; + long requestStartedAt = 0L; + long requestGeneration; + synchronized (tinyNameStateLock) { + cachedName = tinyDeviceNameCache.get(addr); + Long cachedAt = tinyDeviceNameCacheTimes.get(addr); + long now = System.currentTimeMillis(); + if (cachedName != null && cachedAt != null && now - cachedAt < TINY_NAME_CACHE_TTL_MS) return cachedName; + tinyDeviceNameCache.remove(addr); + tinyDeviceNameCacheTimes.remove(addr); + + if (tinyNameRequestsInFlight.contains(addr)) { + Long requestedAt = tinyNameRequestTimes.get(addr); + if (requestedAt != null && now - requestedAt < TINY_NAME_REQUEST_TIMEOUT_MS) { + return ""; + } + tinyNameRequestsInFlight.remove(addr); + tinyNameRequestTimes.remove(addr); + } + + if (!tinyNameRequestsInFlight.add(addr)) return ""; + requestStartedAt = now; + tinyNameRequestTimes.put(addr, requestStartedAt); + requestGeneration = ++nextTinyNameRequestGeneration; + tinyNameRequestGenerations.put(addr, requestGeneration); + } + + final long requestGenerationToken = requestGeneration; + try { + KLALBRoutingProtocolAPIClient client = lifecycleGeneration < 0 + ? getNodeInfoClient(kc) : getNodeInfoClient(kc, lifecycleGeneration); + if (client == null) { + synchronized (tinyNameStateLock) { + if (Long.valueOf(requestGenerationToken).equals(tinyNameRequestGenerations.get(addr))) { + tinyNameRequestsInFlight.remove(addr); + tinyNameRequestTimes.remove(addr); + tinyNameRequestGenerations.remove(addr); + } + } + return ""; + } + client.requestNodeInfoTiny( + new InetSocketAddress(addr.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), + info -> { + synchronized (tinyNameStateLock) { + Long activeRequestGeneration = tinyNameRequestGenerations.get(addr); + if (!Long.valueOf(requestGenerationToken).equals(activeRequestGeneration)) return; + tinyDeviceNameCache.put(addr, + info != null && info.getDeviceName() != null ? info.getDeviceName() : ""); + tinyDeviceNameCacheTimes.put(addr, System.currentTimeMillis()); + tinyNameRequestsInFlight.remove(addr); + tinyNameRequestTimes.remove(addr); + tinyNameRequestGenerations.remove(addr); + } + }); + } catch (IOException e) { + synchronized (tinyNameStateLock) { + Long activeRequestGeneration = tinyNameRequestGenerations.get(addr); + if (Long.valueOf(requestGenerationToken).equals(activeRequestGeneration)) { + tinyNameRequestsInFlight.remove(addr); + tinyNameRequestTimes.remove(addr); + tinyNameRequestGenerations.remove(addr); + } + } + } + return ""; + } + + private void refreshTinyDeviceNameAsync(KLALBController kc, IPv6Address address, long lifecycleGeneration) { + CompletableFuture.runAsync(() -> { + synchronized (this) { + if (!running.get() || nodeInfoLifecycleGeneration != lifecycleGeneration) return; + getTinyDeviceName(kc, address, lifecycleGeneration); + } + }); + } + + private void invalidateTinyDeviceName(IPv6Address address) { + synchronized (tinyNameStateLock) { + tinyDeviceNameCache.remove(address); + tinyDeviceNameCacheTimes.remove(address); + tinyNameRequestsInFlight.remove(address); + tinyNameRequestTimes.remove(address); + tinyNameRequestGenerations.remove(address); + } + } + private void handleNodeInfo(HttpExchange exchange) throws IOException { if (handleCorsPreflight(exchange)) return; if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) { @@ -500,18 +687,23 @@ public class KLALBWebServer { if (isSelf) { // 本机:描述直接取本地控制器配置 - KLALBControllerConfigItem cfg = proxySystem.getControllerConfig(); + KLALBController.PublishedNodeInfo published = kc.getPublishedNodeInfo(); resp.addProperty("isSelf", true); - String dname = router.getDeviceName() != null ? router.getDeviceName() - : (cfg != null && cfg.getDeviceName() != null ? cfg.getDeviceName() : ""); - resp.addProperty("deviceName", dname); + resp.addProperty("deviceName", published.getDeviceName() != null ? published.getDeviceName() : ""); resp.addProperty("deviceDescription", - cfg != null && cfg.getDeviceDescription() != null ? cfg.getDeviceDescription() : ""); + published.getDeviceDescription() != null ? published.getDeviceDescription() : ""); JsonArray lines = new JsonArray(); - for (MultiProtocolSocketAddress mpsa : kc.getExternalEndpoints()) { + for (MultiProtocolSocketAddress mpsa : published.getExternalEndpoints()) { lines.add(new JsonPrimitive(mpsa.toString())); } resp.add("openLines", lines); + JsonArray extraRoutes = new JsonArray(); + if (published.getExtraRoutes() != null) { + for (String route : published.getExtraRoutes()) { + extraRoutes.add(new JsonPrimitive(route != null ? route : "")); + } + } + resp.add("extraRoutes", extraRoutes); sendJsonResponse(exchange, 200, resp); return; } @@ -520,7 +712,12 @@ public class KLALBWebServer { resp.addProperty("isSelf", false); CompletableFuture future = new CompletableFuture<>(); try { - getNodeInfoClient(kc).requestNodeInfoFull( + KLALBRoutingProtocolAPIClient client = getNodeInfoClient(kc); + if (client == null) { + sendError(exchange, 503, "Node info service stopped"); + return; + } + client.requestNodeInfoFull( new InetSocketAddress(target.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), info -> future.complete(info)); @@ -533,8 +730,8 @@ public class KLALBWebServer { String dname = info != null && info.getDeviceName() != null && !info.getDeviceName().isEmpty() ? info.getDeviceName() - : router.getKlalbRouteProtol().getDeviceName(target); - resp.addProperty("deviceName", dname != null ? dname : ""); + : ""; + resp.addProperty("deviceName", dname); resp.addProperty("deviceDescription", info != null && info.getDeviceDescription() != null ? info.getDeviceDescription() : ""); resp.addProperty("reachable", info != null); @@ -545,11 +742,19 @@ public class KLALBWebServer { } } resp.add("openLines", lines); + JsonArray extraRoutes = new JsonArray(); + if (info != null && info.getExtraRoutes() != null) { + for (String route : info.getExtraRoutes()) { + extraRoutes.add(new JsonPrimitive(route != null ? route : "")); + } + } + resp.add("extraRoutes", extraRoutes); } catch (Exception e) { resp.addProperty("deviceName", ""); resp.addProperty("deviceDescription", ""); resp.addProperty("reachable", false); resp.add("openLines", new JsonArray()); + resp.add("extraRoutes", new JsonArray()); } sendJsonResponse(exchange, 200, resp); } @@ -584,6 +789,17 @@ public class KLALBWebServer { KLALBRoutingProtocol rproto = kc.getIpv6Router().getKlalbRouteProtol(); Map addrs = rproto.getAddresses(); IPv6Address selfAddr = kc.getIpv6Router().getLocator().getAddress(); + KLALBController.PublishedNodeInfo published = kc.getPublishedNodeInfo(); + Set activeAddresses = addrs == null + ? Collections.emptySet() : new HashSet(addrs.keySet()); + synchronized (tinyNameStateLock) { + tinyDeviceNameCache.keySet().removeIf(address -> !activeAddresses.contains(address)); + tinyDeviceNameCacheTimes.keySet().removeIf(address -> !activeAddresses.contains(address)); + tinyNameRequestsInFlight.removeIf(address -> !activeAddresses.contains(address)); + tinyNameRequestTimes.keySet().removeIf(address -> !activeAddresses.contains(address)); + tinyNameRequestGenerations.keySet().removeIf(address -> !activeAddresses.contains(address)); + } + nodeInfoFullRevisions.keySet().removeIf(address -> !activeAddresses.contains(address)); if (addrs != null) { for (IPv6Address addr : addrs.keySet()) { @@ -592,8 +808,13 @@ public class KLALBWebServer { nodeObj.addProperty("address", addr.toString()); nodeObj.addProperty("compressedAddress", addr.toCompressedString()); nodeObj.addProperty("isSelf", addr.equals(selfAddr)); - String dname = rproto.getDeviceName(addr); + String dname = addr.equals(selfAddr) ? kc.getIpv6Router().getDeviceName() + : getTinyDeviceName(kc, addr); nodeObj.addProperty("deviceName", dname != null ? dname : ""); + long fullRevision = addr.equals(selfAddr) ? published.getFullRevision() + : nodeInfoFullRevisions.getOrDefault(addr, 0L); + nodeObj.addProperty("nodeInfoFullRevision", fullRevision); + nodeObj.addProperty("nodeInfoFullRevisionEpoch", nodeInfoFullRevisionEpoch); nodesArray.add(nodeObj); } } @@ -644,21 +865,33 @@ public class KLALBWebServer { sendError(exchange, 404, "Configuration not found"); } } else if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) { + synchronized (configUpdateLock) { String body = readRequestBody(exchange); try { JsonObject json = new JsonParser().parse(body).getAsJsonObject(); KLALBControllerConfigItem current = proxySystem.getControllerConfig(); if (current != null) { + String oldDeviceName = current.getDeviceName(); + String oldDeviceDescription = current.getDeviceDescription(); + List oldExternalEndpoints = current.getExternalEndpoints() == null + ? null + : new ArrayList(current.getExternalEndpoints()); + List oldExtraRoutes = current.getExtraRoutes() == null + ? null : new ArrayList(current.getExtraRoutes()); + String newDeviceName = oldDeviceName; + String newDeviceDescription = oldDeviceDescription; + List newExternalEndpoints = current.getExternalEndpoints(); + List newExtraRoutes = current.getExtraRoutes(); if (json.has("deviceName") && !json.get("deviceName").isJsonNull()) { - current.setDeviceName(json.get("deviceName").getAsString()); + newDeviceName = json.get("deviceName").getAsString(); } else if (json.has("DeviceName") && !json.get("DeviceName").isJsonNull()) { - current.setDeviceName(json.get("DeviceName").getAsString()); + newDeviceName = json.get("DeviceName").getAsString(); } if (json.has("deviceDescription") && !json.get("deviceDescription").isJsonNull()) { - current.setDeviceDescription(json.get("deviceDescription").getAsString()); + newDeviceDescription = json.get("deviceDescription").getAsString(); } else if (json.has("DeviceDescription") && !json.get("DeviceDescription").isJsonNull()) { - current.setDeviceDescription(json.get("DeviceDescription").getAsString()); + newDeviceDescription = json.get("DeviceDescription").getAsString(); } if (json.has("language") && !json.get("language").isJsonNull()) { @@ -733,7 +966,7 @@ public class KLALBWebServer { list.add(new MultiProtocolSocketAddress(el.getAsString())); } } - current.setExternalEndpoints(list); + newExternalEndpoints = list; } if (json.has("autoConnections") || json.has("AutoConnections") || json.has("connectLineTable") || json.has("ConnectLineTable")) { @@ -783,7 +1016,7 @@ public class KLALBWebServer { list.add(el.getAsString()); } } - current.setExtraRoutes(list); + newExtraRoutes = list; } if (json.has("networkInterfaceExcepts") || json.has("NetworkInterfaceExcepts")) { @@ -845,6 +1078,23 @@ public class KLALBWebServer { current.setDenyExternalEndpointBroadcast(json.get("denyLineTableBroadcast").getAsBoolean()); } + boolean deviceNameChanged = !Objects.equals(oldDeviceName, newDeviceName); + boolean deviceDescriptionChanged = !Objects.equals(oldDeviceDescription, newDeviceDescription); + boolean externalEndpointsChanged = !Objects.equals(oldExternalEndpoints, newExternalEndpoints); + boolean extraRoutesChanged = !Objects.equals(oldExtraRoutes, newExtraRoutes); + if (deviceNameChanged || deviceDescriptionChanged || externalEndpointsChanged || extraRoutesChanged) { + KLALBController kc = proxySystem.getKlalbController(); + if (kc != null) { + kc.publishNodeInfo(newDeviceName, newDeviceDescription, newExternalEndpoints, + newExtraRoutes); + } else { + current.setDeviceName(newDeviceName); + current.setDeviceDescription(newDeviceDescription); + current.setExternalEndpoints(newExternalEndpoints); + current.setExtraRoutes(newExtraRoutes); + } + } + // Trigger GUI save consumer or save directly if (proxySystem.getKLALBGUI() != null && proxySystem.getKLALBGUI().getSaveComsumer() != null) { proxySystem.getKLALBGUI().getSaveComsumer().accept(proxySystem.getConfig()); @@ -862,6 +1112,7 @@ public class KLALBWebServer { } catch (Exception e) { sendError(exchange, 400, "Failed to update configuration: " + e.getMessage()); } + } } else { sendError(exchange, 405, "Method not allowed"); } diff --git a/src/org/kne/cloud/network/srv6/JsonDataPacket.java b/src/org/kne/cloud/network/srv6/JsonDataPacket.java index fef11b4..d32bfb5 100644 --- a/src/org/kne/cloud/network/srv6/JsonDataPacket.java +++ b/src/org/kne/cloud/network/srv6/JsonDataPacket.java @@ -33,7 +33,8 @@ public class JsonDataPacket extends KLALBRoutingProtocolPacket implements Serial private String data; - private static final int HEADER_LENGTH=3; + private static final int HEADER_LENGTH=3; + private static final int MAX_JSON_PAYLOAD_LENGTH=65535-HEADER_LENGTH; private static Gson gson; static{ @@ -73,9 +74,12 @@ public class JsonDataPacket extends KLALBRoutingProtocolPacket implements Serial @Override - public void writeToChannel(WritableByteChannel dto) throws IOException { - byte[]bta=data.getBytes(Charset.forName("UTF-8")); - getHeader().putChar(1,(char) bta.length); + public void writeToChannel(WritableByteChannel dto) throws IOException { + byte[]bta=data.getBytes(Charset.forName("UTF-8")); + if(bta.length>MAX_JSON_PAYLOAD_LENGTH) { + throw new IOException("JSON payload exceeds " + MAX_JSON_PAYLOAD_LENGTH + " bytes"); + } + getHeader().putChar(1,(char) bta.length); super.writeToChannel(dto); dto.write(ByteBuffer.wrap(bta)); } diff --git a/src/org/kne/cloud/network/srv6/KLALBNodeInformation.java b/src/org/kne/cloud/network/srv6/KLALBNodeInformation.java index d38f5d6..6a94ba6 100644 --- a/src/org/kne/cloud/network/srv6/KLALBNodeInformation.java +++ b/src/org/kne/cloud/network/srv6/KLALBNodeInformation.java @@ -1,6 +1,7 @@ package org.kne.cloud.network.srv6; import java.util.List; +import java.util.ArrayList; import org.kne.cloud.network.MultiProtocolSocketAddress; @@ -11,13 +12,20 @@ public class KLALBNodeInformation { private List openLines; private String deviceName; private String deviceDescription; + private List extraRoutes; public KLALBNodeInformation(List openLines, String deviceName, String deviceDescription) { + this(openLines, deviceName, deviceDescription, new ArrayList()); + } + + public KLALBNodeInformation(List openLines, String deviceName, + String deviceDescription, List extraRoutes) { super(); this.openLines = openLines; this.deviceName = deviceName; this.deviceDescription = deviceDescription; + this.extraRoutes = extraRoutes == null ? new ArrayList() : extraRoutes; } public List getOpenLines() { @@ -44,9 +52,17 @@ public class KLALBNodeInformation { this.deviceDescription = deviceDescription; } + public List getExtraRoutes() { + return extraRoutes; + } + + public void setExtraRoutes(List extraRoutes) { + this.extraRoutes = extraRoutes == null ? new ArrayList() : extraRoutes; + } + @Override public String toString() { return "KLALBNodeInformation [openLines=" + openLines + ", deviceName=" + deviceName - + ", deviceDescription=" + deviceDescription + "]"; + + ", deviceDescription=" + deviceDescription + ", extraRoutes=" + extraRoutes + "]"; } } diff --git a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocol.java b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocol.java index 1a9980f..d3c2e52 100644 --- a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocol.java +++ b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocol.java @@ -13,15 +13,18 @@ import java.nio.channels.Channels; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; -import java.util.List; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; import org.kne.cloud.network.ipv6.IPv6Address; @@ -41,8 +44,12 @@ public class KLALBRoutingProtocol extends Thread{ private static final boolean debug = false; - private RouterInfo selfRouterInfo; - private Map netmap=new ConcurrentHashMap<>(); + private RouterInfo selfRouterInfo; + private Map netmap=new ConcurrentHashMap<>(); + private final AtomicLong selfCreateTime = new AtomicLong(); + private final AtomicInteger pendingNodeInfoUpdateFlags = new AtomicInteger(); + private static final int NODE_INFO_UPDATE_REPLAY_WINDOW_SIZE = 64; + private final Map> seenNodeInfoUpdates = new HashMap<>(); public Map getNetmap() { return netmap; @@ -84,15 +91,67 @@ public class KLALBRoutingProtocol extends Thread{ private KLALBVirtualRawSocket ds = null; private ReentrantLock sendLock=new ReentrantLock(); - private Set>receivers=new CopyOnWriteArraySet>(); + private Set>receivers=new CopyOnWriteArraySet>(); + private Set> nodeInfoUpdateListeners = new CopyOnWriteArraySet>(); public void addReceiver(BiConsumer rec) { receivers.add(rec); } - public void removeReceiver(BiConsumer rec) { - receivers.remove(rec); - } + public void removeReceiver(BiConsumer rec) { + receivers.remove(rec); + } + + public void addNodeInfoUpdateListener(BiConsumer listener) { + nodeInfoUpdateListeners.add(listener); + } + + public void removeNodeInfoUpdateListener(BiConsumer listener) { + nodeInfoUpdateListeners.remove(listener); + } + + public void announceNodeInfoUpdate(int flags) { + int validFlags = flags & (RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED + | RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED); + if(validFlags != 0) { + pendingNodeInfoUpdateFlags.getAndUpdate(old -> old | validFlags); + } + } + + private boolean markNodeInfoUpdateSeen(IPv6Address origin, long createTime) { + synchronized(seenNodeInfoUpdates) { + LinkedHashSet createTimes = seenNodeInfoUpdates.get(origin); + if(createTimes == null) { + createTimes = new LinkedHashSet<>(); + seenNodeInfoUpdates.put(origin, createTimes); + } + if(!createTimes.add(createTime)) { + return false; + } + while(createTimes.size() > NODE_INFO_UPDATE_REPLAY_WINDOW_SIZE) { + createTimes.remove(createTimes.iterator().next()); + } + return true; + } + } + + private void removeNodeInfoUpdateWindow(IPv6Address origin) { + synchronized(seenNodeInfoUpdates) { + seenNodeInfoUpdates.remove(origin); + } + } + + private void notifyNodeInfoUpdate(IPv6Address origin, int flags) { + for(BiConsumer listener : nodeInfoUpdateListeners) { + try { + listener.accept(origin, flags); + } catch(Throwable e) { + if(debug) { + e.printStackTrace(); + } + } + } + } @Override public void run() { @@ -103,11 +162,22 @@ public class KLALBRoutingProtocol extends Thread{ new Thread(()->{ Thread.currentThread().setName("KLALB路由协议发送线程"); - while(true) { - try { - selfRouterInfo = getSelfRouterInfo(); - RouterInfo oslf=netmap.put(selfRouterInfo.getLocator().getAddress(), selfRouterInfo); - noticeUpdate(); + while(true) { + try { + selfRouterInfo = getSelfRouterInfo(); + RouterInfo oslf=netmap.put(selfRouterInfo.getLocator().getAddress(), selfRouterInfo); + noticeUpdate(); + int nodeInfoUpdateFlags = pendingNodeInfoUpdateFlags.getAndSet(0); + if(nodeInfoUpdateFlags != 0) { + RouterInfoPacket updatePacket = new RouterInfoPacket(getSelfRouterInfo(), true, -1); + updatePacket.setNodeInfoUpdateFlags(nodeInfoUpdateFlags); + try { + floodPacket(null, updatePacket); + } catch(IOException e) { + pendingNodeInfoUpdateFlags.getAndUpdate(old -> old | nodeInfoUpdateFlags); + e.printStackTrace(); + } + } /*if(oslf==null||(!selfRouterInfo.equals(oslf))) { long cur=System.nanoTime(); if(cur-floodTimer2>5000000000L) { @@ -147,10 +217,12 @@ public class KLALBRoutingProtocol extends Thread{ if(val.getAsn()==router.getASN()) { timeout=2000000000L; } - if(val.checkTimeOut(timeout)) { - iterator.remove(); - noticeUpdate(); - } + if(val.checkTimeOut(timeout)) { + if(netmap.remove(type.getKey(), val)) { + removeNodeInfoUpdateWindow(type.getKey()); + noticeUpdate(); + } + } /*List ads=val.getNeighborAddresses(); for (Iterator iterator2 = ads.iterator(); iterator2.hasNext();) { Inet6Address address=iterator2.next().getLocator().getAddress(); @@ -216,7 +288,7 @@ public class KLALBRoutingProtocol extends Thread{ ds.receive(dgp); //System.out.println(Arrays.toString( Arrays.copyOf( dgp.getData(),dgp.getLength()))); ByteArrayInputStream bi=new ByteArrayInputStream(dgp.getData(),0,dgp.getLength()); - KLALBRoutingProtocolPacket kp=KLALBRoutingProtocolPacket.readKLALBPacketFromChannel(Channels.newChannel(bi)); + KLALBRoutingProtocolPacket kp=KLALBRoutingProtocolPacket.readKLALBPacketFromChannel(Channels.newChannel(bi), dgp.getLength()); switch(kp.getType()) { case KLALBRoutingProtocolPacket.RINFO_REQ: @@ -234,28 +306,34 @@ public class KLALBRoutingProtocol extends Thread{ break; case KLALBRoutingProtocolPacket.RINFO: - RouterInfoPacket rifp=(RouterInfoPacket) kp; - RouterInfo rif=rifp.getRinfo(); - RouterInfo oldrif=netmap.get(rif.getLocator().getAddress()); + RouterInfoPacket rifp=(RouterInfoPacket) kp; + RouterInfo rif=rifp.getRinfo(); + RouterInfo oldrif=netmap.get(rif.getLocator().getAddress()); + IPv6Address origin = rif.getLocator().getAddress(); + int nodeInfoUpdateFlags = rifp.getNodeInfoUpdateFlags(); + boolean newNodeInfoUpdate = nodeInfoUpdateFlags != 0 + && markNodeInfoUpdateSeen(origin, rif.getCreateTime()); if(debug) System.out.println(rif); - if(oldrif==null||oldrif.getCreateTime()links=router.getLinkTabel(); Object[] nls=links.toArray(); - RouterInfo ri=new RouterInfo(System.currentTimeMillis(),router.getLocator(),router.getASN()); - ri.setDeviceName(router.getDeviceName()); + long currentTime = System.currentTimeMillis(); + long createTime = selfCreateTime.updateAndGet(previous -> Math.max(currentTime, previous + 1)); + RouterInfo ri=new RouterInfo(createTime,router.getLocator(),router.getASN()); for(int i=0;i> getPaths() { return paths; diff --git a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIClient.java b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIClient.java index 1a201e7..f0915da 100644 --- a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIClient.java +++ b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIClient.java @@ -36,7 +36,7 @@ public class KLALBRoutingProtocolAPIClient { List connects = (List) dataobj.getData(); List connectsm = new ArrayList( connects == null ? 0 : connects.size()); - if (connects != null) { + if (connects != null) { for (Object open : connects) { if (open instanceof MultiProtocolSocketAddress) { connectsm.add((MultiProtocolSocketAddress) open); @@ -45,10 +45,14 @@ public class KLALBRoutingProtocolAPIClient { } } - } - // 组装节点信息(线路 + 设备名称 + 设备描述,精简模式下线路与描述为 null) - KLALBNodeInformation info = new KLALBNodeInformation(connectsm, dataobj.getDeviceName(), - dataobj.getDeviceDescription()); + } + List extraRoutes = dataobj.getExtraRoutes(); + if(extraRoutes == null) { + extraRoutes = new ArrayList(); + } + // 组装节点信息(线路 + 设备名称 + 设备描述,精简模式下线路与描述为 null) + KLALBNodeInformation info = new KLALBNodeInformation(connectsm, dataobj.getDeviceName(), + dataobj.getDeviceDescription(), extraRoutes); ((Consumer) relate.getUserCallback()).accept(info); } break; @@ -122,4 +126,4 @@ class KLALBRoutingProtocolAPIClientReleaser extends Releaser(fullPublished.getExternalEndpoints())); + json.setDeviceName(fullPublished.getDeviceName());// 附带本机设备名称 + json.setDeviceDescription(fullPublished.getDeviceDescription());// 附带本机设备描述 + json.setExtraRoutes(new ArrayList(fullPublished.getExtraRoutes())); + routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(json),addr); break; } } catch (IOException e) { @@ -85,4 +88,4 @@ class KLALBRoutingProtocolAPIServerReleaser extends Releaser resource) { routingProtocol.removeReceiver(resource); } -} \ No newline at end of file +} diff --git a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolJsonData.java b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolJsonData.java index ca8f1b5..0e8e80c 100644 --- a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolJsonData.java +++ b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolJsonData.java @@ -1,6 +1,7 @@ package org.kne.cloud.network.srv6; -import java.util.UUID; +import java.util.UUID; +import java.util.List; public class KLALBRoutingProtocolJsonData { public static final String NODE_INFO_TINY_REQ="nodeinfotinyreq";// 精简查询:仅设备名称 @@ -10,8 +11,9 @@ public class KLALBRoutingProtocolJsonData { private String type; private UUID uuid; private Object data; - private String deviceName;// 对端设备名称 - private String deviceDescription;// 对端设备描述 + private String deviceName;// 对端设备名称 + private String deviceDescription;// 对端设备描述 + private List extraRoutes; public String getDeviceName() { return deviceName; } @@ -21,9 +23,15 @@ public class KLALBRoutingProtocolJsonData { public String getDeviceDescription() { return deviceDescription; } - public void setDeviceDescription(String deviceDescription) { - this.deviceDescription = deviceDescription; - } + public void setDeviceDescription(String deviceDescription) { + this.deviceDescription = deviceDescription; + } + public List getExtraRoutes() { + return extraRoutes; + } + public void setExtraRoutes(List extraRoutes) { + this.extraRoutes = extraRoutes; + } public String getType() { return type; } @@ -41,7 +49,8 @@ public class KLALBRoutingProtocolJsonData { result = prime * result + ((type == null) ? 0 : type.hashCode()); result = prime * result + ((uuid == null) ? 0 : uuid.hashCode()); result = prime * result + ((deviceName == null) ? 0 : deviceName.hashCode()); - result = prime * result + ((deviceDescription == null) ? 0 : deviceDescription.hashCode()); + result = prime * result + ((deviceDescription == null) ? 0 : deviceDescription.hashCode()); + result = prime * result + ((extraRoutes == null) ? 0 : extraRoutes.hashCode()); return result; } @Override @@ -76,9 +85,14 @@ public class KLALBRoutingProtocolJsonData { if (deviceDescription == null) { if (other.deviceDescription != null) return false; - } else if (!deviceDescription.equals(other.deviceDescription)) - return false; - return true; + } else if (!deviceDescription.equals(other.deviceDescription)) + return false; + if (extraRoutes == null) { + if (other.extraRoutes != null) + return false; + } else if (!extraRoutes.equals(other.extraRoutes)) + return false; + return true; } public KLALBRoutingProtocolJsonData(String type, UUID uuid, Object data) { super(); @@ -88,8 +102,8 @@ public class KLALBRoutingProtocolJsonData { } @Override public String toString() { - return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data + ", deviceName=" - + deviceName + ", deviceDescription=" + deviceDescription + "]"; + return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data + ", deviceName=" + + deviceName + ", deviceDescription=" + deviceDescription + ", extraRoutes=" + extraRoutes + "]"; } } diff --git a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolPacket.java b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolPacket.java index 6986878..459ed7f 100644 --- a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolPacket.java +++ b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolPacket.java @@ -95,8 +95,12 @@ public abstract class KLALBRoutingProtocolPacket extends NetworkPacket { return readKLALBPacketFromChannel(Channels.newChannel(in)); } - public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in) throws IOException { - while(true) { + public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in) throws IOException { + return readKLALBPacketFromChannel(in, -1); + } + + public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in, long packetLength) throws IOException { + while(true) { ByteBuffer bb=NetworkPacket.bufferAllocator.allocate(40); bb.limit(1); while(bb.hasRemaining()){ @@ -109,18 +113,20 @@ public abstract class KLALBRoutingProtocolPacket extends NetworkPacket { KLALBRoutingProtocolPacket klp; switch(type) { - case RINFO_REQ: - klp=new RouterInfoRequestPacket(bb); - klp.readFromChannel(in); - return klp; - case RINFO: - klp=new RouterInfoPacket(bb); - klp.readFromChannel(in); - return klp; - case JSON_DATA: - klp=new JsonDataPacket(bb); - klp.readFromChannel(in); - return klp; + case RINFO_REQ: + klp=new RouterInfoRequestPacket(bb); + if(packetLength < 0) klp.readFromChannel(in); + else klp.readFromChannel(in, packetLength); + return klp; + case RINFO: + klp=new RouterInfoPacket(bb); + if(packetLength < 0) klp.readFromChannel(in); + else klp.readFromChannel(in, packetLength); + return klp; + case JSON_DATA: + klp=new JsonDataPacket(bb); + klp.readFromChannel(in); + return klp; } //throw new StreamCorruptedException("unknown package type:"+type); System.err.println("ignore unknown KLALBRoutingProtocolPacket type:"+type); diff --git a/src/org/kne/cloud/network/srv6/RouterInfo.java b/src/org/kne/cloud/network/srv6/RouterInfo.java index ae46e1f..6e2a149 100644 --- a/src/org/kne/cloud/network/srv6/RouterInfo.java +++ b/src/org/kne/cloud/network/srv6/RouterInfo.java @@ -60,15 +60,15 @@ public class RouterInfo implements Serializable{ return Objects.equals(locator, other.locator) && Objects.equals(neighborAddresses, other.neighborAddresses); } private List neighborAddresses=new ArrayList<>(); - private String deviceName=""; // 设备名称(广播时携带,描述不广播) - private long createTime; + private long createTime; public long getCreateTime() { return createTime; } - private static final long INFO_UPDATETIME=10000000000L; - private static final long INFO_TIMEOUT=60000000000L; + private static final long INFO_UPDATETIME=10000000000L; + private static final long INFO_TIMEOUT=60000000000L; + private static final int MAX_NEIGHBOR_COUNT = 512; private long putTime=System.nanoTime(); public boolean checkUpdateTime() { return System.nanoTime()-putTime>INFO_UPDATETIME; @@ -91,31 +91,28 @@ public class RouterInfo implements Serializable{ return neighborAddresses; } - public String getDeviceName() { - return deviceName; - } - - public void setDeviceName(String deviceName) { - this.deviceName = deviceName; - } - - public RouterInfo( long createTime,IPv6AddressGroup locator,long asn) { + public RouterInfo( long createTime,IPv6AddressGroup locator,long asn) { this.createTime = createTime; this.locator=locator; this.asn=asn; } - public RouterInfo() { - } - public void writeToStream(DataOutputStream out) throws IOException { - locator.writeToStream(out); - out.writeLong(asn); - out.writeLong(createTime); - out.writeInt(neighborAddresses.size()); + public RouterInfo() { + } + public void writeToStream(DataOutputStream out) throws IOException { + int neighborCount = neighborAddresses == null ? -1 : neighborAddresses.size(); + if(neighborCount<0 || neighborCount>MAX_NEIGHBOR_COUNT) { + throw new IOException("Invalid neighbor count: " + neighborCount); + } + locator.writeToStream(out); + out.writeLong(asn); + out.writeLong(createTime); + out.writeInt(neighborCount); for( NeighborInfo neighborInfo :neighborAddresses) { neighborInfo.writeToStream(out); } - out.writeUTF(deviceName==null?"":deviceName); + // Keep the legacy UTF slot so older RINFO decoders stay aligned. + out.writeUTF(""); } protected void writeToChannel(WritableByteChannel dto) throws IOException { @@ -127,16 +124,20 @@ public class RouterInfo implements Serializable{ public void readFromStream(DataInputStream in) throws IOException { locator=new IPv6AddressGroup(in); asn=in.readLong(); - createTime=in.readLong(); - int size=in.readInt(); - neighborAddresses=new ArrayList<>(size); + createTime=in.readLong(); + int size=in.readInt(); + if(size<0 || size>MAX_NEIGHBOR_COUNT) { + throw new IOException("Invalid neighbor count: " + size); + } + neighborAddresses=new ArrayList<>(size); for (int i = 0; i < size; i++) { NeighborInfo nif=new NeighborInfo(); nif.readFromStream(in); neighborAddresses.add(nif); } - deviceName=in.readUTF(); + // Consume the legacy device-name slot; names are no longer broadcast here. + in.readUTF(); } } diff --git a/src/org/kne/cloud/network/srv6/RouterInfoPacket.java b/src/org/kne/cloud/network/srv6/RouterInfoPacket.java index 31ef280..04a2c60 100644 --- a/src/org/kne/cloud/network/srv6/RouterInfoPacket.java +++ b/src/org/kne/cloud/network/srv6/RouterInfoPacket.java @@ -1,7 +1,8 @@ package org.kne.cloud.network.srv6; import java.io.DataInputStream; -import java.io.DataOutputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayInputStream; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; @@ -22,7 +23,12 @@ import java.util.Set; import org.kne.cloud.network.NetworkPacket; import org.kne.cloud.network.ipv6.IPv6AddressGroup; -public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Serializable{ +public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Serializable{ + public static final int NODE_INFO_TINY_UPDATE_REQUIRED = 1; + public static final int NODE_INFO_FULL_UPDATE_REQUIRED = 2; + private static final int NODE_INFO_UPDATE_FLAGS_MASK = NODE_INFO_TINY_UPDATE_REQUIRED + | NODE_INFO_FULL_UPDATE_REQUIRED; + private static final int NODE_INFO_UPDATE_MAGIC = 0xA5; public RouterInfoPacket(RouterInfo routerInfo, boolean flood,long asn) { super(RINFO); @@ -34,7 +40,8 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri super(bb); } - private RouterInfo rinfo; + private RouterInfo rinfo; + private transient int nodeInfoUpdateFlags; public boolean isFlood() { return (getHeader().get(1)&1)==1; @@ -44,9 +51,17 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri return getHeader().getLong(2); } - public RouterInfo getRinfo() { - return rinfo; - } + public RouterInfo getRinfo() { + return rinfo; + } + + public int getNodeInfoUpdateFlags() { + return nodeInfoUpdateFlags; + } + + public void setNodeInfoUpdateFlags(int flags) { + nodeInfoUpdateFlags = flags & NODE_INFO_UPDATE_FLAGS_MASK; + } @Override protected long getHeaderSize() { return super.getHeaderSize()+9; @@ -65,15 +80,46 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri @Override - public void writeToChannel(WritableByteChannel dto) throws IOException { - super.writeToChannel(dto); - rinfo.writeToChannel(dto); - } + public void writeToChannel(WritableByteChannel dto) throws IOException { + super.writeToChannel(dto); + rinfo.writeToChannel(dto); + int flags = nodeInfoUpdateFlags & NODE_INFO_UPDATE_FLAGS_MASK; + if(flags != 0) { + ByteBuffer extension = ByteBuffer.wrap(new byte[] {(byte) NODE_INFO_UPDATE_MAGIC, (byte) flags}); + while(extension.hasRemaining()) { + dto.write(extension); + } + } + } @Override public void readFromChannel(ReadableByteChannel din, long length) throws IOException { - super.readFromChannel(din, length); - rinfo=new RouterInfo(); - rinfo.readFromChannel(din, length); + super.readFromChannel(din, length); + rinfo=new RouterInfo(); + if(length < 0) { + rinfo.readFromChannel(din, length); + nodeInfoUpdateFlags = 0; + return; + } + long payloadLength = length - getHeaderSize(); + if(payloadLength < 0 || payloadLength > Integer.MAX_VALUE) { + throw new IOException("Invalid RINFO packet length: " + length); + } + byte[] payload = new byte[(int) payloadLength]; + ByteBuffer payloadBuffer = ByteBuffer.wrap(payload); + while(payloadBuffer.hasRemaining()) { + if(din.read(payloadBuffer) == -1) { + throw new java.io.EOFException(); + } + } + ByteArrayInputStream payloadInput = new ByteArrayInputStream(payload); + rinfo.readFromStream(new DataInputStream(payloadInput)); + nodeInfoUpdateFlags = 0; + int marker = payloadInput.read(); + int flags = payloadInput.read(); + int trailing = payloadInput.read(); + if(marker == NODE_INFO_UPDATE_MAGIC && flags >= 0 && trailing == -1) { + nodeInfoUpdateFlags = flags & NODE_INFO_UPDATE_FLAGS_MASK; + } } }