WIP: Patch 4 #5

Closed
SerinaNya wants to merge 4 commits from SerinaNya/KLALB:patch-4 into master
23 changed files with 1884 additions and 734 deletions
+21 -83
View File
@@ -1,101 +1,39 @@
# 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. Start at `org.kne.cloud.network.klalb.KLALBMain`; runtime configuration is root-relative `klalb-config.json`.
## 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`. - This is an Eclipse Java project, not Maven or Gradle: `src/` contains sources, `lib/` vendored dependencies, and `bin/` compiled output. `.classpath` targets Java 25.
- Source files contain Chinese text, so compile with UTF-8 from the repository root:
Compile (`javac` is NOT on PATH — use the full JDK path; `-encoding UTF-8` is mandatory because sources contain Chinese text):
```powershell ```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) & "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 repository root. `src` must stay on the classpath because manual compilation does not copy resource bundles:
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
```powershell ```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. - Native libraries and `klalb-config.json` are resolved from the current directory; restart the JVM after recompiling. TUN creation requires elevation, so use `"enableTUN": false` for non-admin checks.
Runtime gotchas (all verified): ## Dashboard
- 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. - `dashboard/` is a Git submodule. Commit dashboard changes in that repository, then update the parent submodule pointer.
- 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. - Run frontend commands from `dashboard/`: `pnpm install --frozen-lockfile`, `pnpm lint`, `pnpm typecheck`, and `pnpm build`. The build is `tsc -b && vite build` and produces `dashboard/dist`, which the Java server hosts.
- 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. - Vite proxies `/api` to `http://127.0.0.1:4665`; use `pnpm dev` only with the Java API running there.
## Verification ## 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. - No repository test runner or CI workflow is configured. 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`, ...). - `KLALBConfigItem` is a polymorphic JSON array keyed by case-sensitive `Type`. New types need serializer and deserializer support; preserve unknown items' raw JSON.
- `org.kne.cloud.network.klalb.web.KLALBWebServer` — built-in HTTP/SSE server (JDK `HttpServer`): - `/api/config` is field-by-field parsing, not whole-object Gson mapping. Keep legacy key aliases in sync with new fields.
- 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=<ipv6>` (on-demand full node info), `/api/config`. - Vendored Gson is `2.1`: responses that are `JsonElement` instances must be serialized with `JsonElement.toString()`, not reflective `gson.toJson(Object)`.
- Static file hosting / SPA fallback: serves `dashboard/dist/` assets directly. - UI strings use `UIEnv.getRsb()`; add keys to both `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`.
- `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. - `KLALBController.PublishedNodeInfo` is the thread-safe node-info source. Publish profile, effective external endpoints, and extra routes through the controller so snapshots and update flags remain coherent. Peer queries are separate profile, endpoint, and extra-route requests; use `NodeInfoQueryCoordinator` when a consumer needs an aggregate detail result.
- `...network.klalb` — app core: `KLALBController` (the virtual SRv6 network), `KLALBRemoteLink` (WAN lines), `*Packet` wire-format classes, virtual socket implementations. - `RouterInfo` retains an empty legacy UTF slot. `RouterInfoPacket` appends optional update flags behind a marker; treat binary codec changes as mesh-compatibility work and preserve old-reader behavior.
- `...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 <component>` (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<InetAddress>`, `List<MultiProtocolSocketAddress>`) 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.
+1
View File
@@ -7,6 +7,7 @@ devicename=Device name
devicedescription=Device description devicedescription=Device description
dnsserver=DNS server dnsserver=DNS server
extraroutes=Extra routes extraroutes=Extra routes
noextraroutes=No extra routes
asnumber=AS number asnumber=AS number
tcplistening=TCP listening tcplistening=TCP listening
udplistening=UDP listening udplistening=UDP listening
+3 -2
View File
@@ -6,7 +6,8 @@ ipv6addr=IPv6地址
devicename=设备名称 devicename=设备名称
devicedescription=设备描述 devicedescription=设备描述
dnsserver=DNS服务器 dnsserver=DNS服务器
extraroutes=额外路由 extraroutes=额外路由
noextraroutes=暂无额外路由
asnumber=AS号码 asnumber=AS号码
tcplistening=TCP监听端口 tcplistening=TCP监听端口
udplistening=UDP监听端口 udplistening=UDP监听端口
@@ -116,4 +117,4 @@ webapisettings=Web API 设置
enablewebapi=启用 Web API enablewebapi=启用 Web API
weblistenaddr=Web API 监听地址:端口 weblistenaddr=Web API 监听地址:端口
invaildweblistenaddr=无效的 Web API 监听地址:端口 invaildweblistenaddr=无效的 Web API 监听地址:端口
enabletun=启用TUN虚拟网卡 enabletun=启用TUN虚拟网卡
@@ -10,12 +10,14 @@ import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration; import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
@@ -51,10 +53,11 @@ import org.kne.cloud.network.ntp.NTPContext;
import org.kne.cloud.network.ntp.NTPv4Packet; import org.kne.cloud.network.ntp.NTPv4Packet;
import org.kne.cloud.network.ntp.NTPv4Protocol; import org.kne.cloud.network.ntp.NTPv4Protocol;
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer; import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol; import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient; import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIServer; import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIServer;
import org.kne.cloud.network.srv6.SRv6Router; import org.kne.cloud.network.srv6.NodeInfoQueryStatus;
import org.kne.cloud.network.srv6.SRv6Router;
import org.kne.cloud.network.srv6.SRv6RouterListener; import org.kne.cloud.network.srv6.SRv6RouterListener;
import org.kne.cloud.network.tcp.UDPPacket; import org.kne.cloud.network.tcp.UDPPacket;
import org.kne.cloud.network.tcp.UDPProtocolRegister; import org.kne.cloud.network.tcp.UDPProtocolRegister;
@@ -76,7 +79,50 @@ public class KLALBController {
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW), new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW),
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW)); new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>(); private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
private List<MultiProtocolSocketAddress> configuredExternalEndpoints = new ArrayList<>();
private List<MultiProtocolSocketAddress> discoveredExternalEndpoints = new ArrayList<>();
public static final class PublishedNodeInfo {
private final String deviceName;
private final String deviceDescription;
private final List<MultiProtocolSocketAddress> externalEndpoints;
private final List<String> extraRoutes;
private final long fullRevision;
private PublishedNodeInfo(String deviceName, String deviceDescription,
List<MultiProtocolSocketAddress> externalEndpoints, List<String> extraRoutes, long fullRevision) {
this.deviceName = deviceName;
this.deviceDescription = deviceDescription;
this.externalEndpoints = Collections.unmodifiableList(
new ArrayList<MultiProtocolSocketAddress>(externalEndpoints));
this.extraRoutes = Collections.unmodifiableList(new ArrayList<String>(extraRoutes));
this.fullRevision = fullRevision;
}
public String getDeviceName() {
return deviceName;
}
public String getDeviceDescription() {
return deviceDescription;
}
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return externalEndpoints;
}
public List<String> getExtraRoutes() {
return extraRoutes;
}
public long getFullRevision() {
return fullRevision;
}
}
private volatile PublishedNodeInfo publishedNodeInfo = new PublishedNodeInfo(null, null,
Collections.<MultiProtocolSocketAddress>emptyList(), Collections.<String>emptyList(), 0L);
private List<MultiProtocolSocketAddress> listensSocketAddress = new CopyOnWriteArrayList<>(); private List<MultiProtocolSocketAddress> listensSocketAddress = new CopyOnWriteArrayList<>();
@@ -125,7 +171,8 @@ public class KLALBController {
@Override @Override
public void run() { public void run() {
try { try {
List<InetAddress> localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress(); List<InetAddress> localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress();
List<MultiProtocolSocketAddress> currentDiscoveredExternalEndpoints = new ArrayList<>();
for (InetAddress inetAddress : localaddress) { for (InetAddress inetAddress : localaddress) {
for (Iterator<MultiProtocolSocketAddress> iterator = listensSocketAddress.iterator(); iterator.hasNext();) { for (Iterator<MultiProtocolSocketAddress> iterator = listensSocketAddress.iterator(); iterator.hasNext();) {
@@ -137,11 +184,9 @@ public class KLALBController {
MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(tcpl.getProtocol(), MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(tcpl.getProtocol(),
inetAddress.getHostAddress(), tcpl.getPort()); inetAddress.getHostAddress(), tcpl.getPort());
// System.out.println(bind); // System.out.println(bind);
synchronized (externalEndpoints) { if (!currentDiscoveredExternalEndpoints.contains(bind)) {
if (!externalEndpoints.contains(bind)) { currentDiscoveredExternalEndpoints.add(bind);
externalEndpoints.add(bind); }
}
}
} }
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
// TODO 自动生成的 catch 块 // TODO 自动生成的 catch 块
@@ -149,9 +194,15 @@ public class KLALBController {
} }
} }
} }
synchronized (externalEndpoints) {
lineslock.writeLock().lock(); if (!discoveredExternalEndpoints.equals(currentDiscoveredExternalEndpoints)) {
discoveredExternalEndpoints = currentDiscoveredExternalEndpoints;
publishDiscoveredExternalEndpointLocked();
}
}
lineslock.writeLock().lock();
try { try {
@@ -298,8 +349,8 @@ public class KLALBController {
private boolean checkIsSelf(MultiProtocolSocketAddress inetAddress) throws UnknownHostException { private boolean checkIsSelf(MultiProtocolSocketAddress inetAddress) throws UnknownHostException {
return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress() return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress()
|| externalEndpoints.contains(inetAddress); || publishedNodeInfo.getExternalEndpoints().contains(inetAddress);
} }
private boolean checkIsSelfLocator(InetAddress inetAddress) { private boolean checkIsSelfLocator(InetAddress inetAddress) {
@@ -339,9 +390,11 @@ public class KLALBController {
try { try {
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(), InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(),
KLALBRoutingProtocol.DEFAULT_PORT); KLALBRoutingProtocol.DEFAULT_PORT);
apiClient.requestNodeInfoFull(iaddr, (v) -> { apiClient.requestExternalEndpoints(iaddr, (v) -> {
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> { if (v == null || v.getStatus() != NodeInfoQueryStatus.OK) return;
for (MultiProtocolSocketAddress msa : v.getOpenLines()) { ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
if (v.getExternalEndpoints() == null) return;
for (MultiProtocolSocketAddress msa : v.getExternalEndpoints()) {
// System.out.print(msa); // System.out.print(msa);
addRemoteLines(msa); addRemoteLines(msa);
} }
@@ -401,9 +454,104 @@ public class KLALBController {
} }
public List<MultiProtocolSocketAddress> getExternalEndpoints() { public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return externalEndpoints; return publishedNodeInfo.getExternalEndpoints();
} }
public List<MultiProtocolSocketAddress> getExternalEndpointsSnapshot() {
return new ArrayList<MultiProtocolSocketAddress>(publishedNodeInfo.getExternalEndpoints());
}
public PublishedNodeInfo getPublishedNodeInfo() {
return publishedNodeInfo;
}
private List<MultiProtocolSocketAddress> createEffectiveExternalEndpointsLocked() {
List<MultiProtocolSocketAddress> effective = new ArrayList<MultiProtocolSocketAddress>();
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<MultiProtocolSocketAddress> configEndpoints, List<String> extraRoutes) {
configuredExternalEndpoints = configEndpoints == null
? new ArrayList<MultiProtocolSocketAddress>()
: new ArrayList<MultiProtocolSocketAddress>(configEndpoints);
List<String> publishedExtraRoutes = extraRoutes == null
? new ArrayList<String>() : new ArrayList<String>(extraRoutes);
List<MultiProtocolSocketAddress> 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<MultiProtocolSocketAddress> configEndpoints, List<String> extraRoutes) {
synchronized (externalEndpoints) {
if (configItem != null) {
configItem.setDeviceName(deviceName);
configItem.setDeviceDescription(deviceDescription);
configItem.setExternalEndpoints(new ArrayList<MultiProtocolSocketAddress>(configEndpoints == null
? Collections.<MultiProtocolSocketAddress>emptyList() : configEndpoints));
configItem.setExtraRoutes(new ArrayList<String>(extraRoutes == null
? Collections.<String>emptyList() : extraRoutes));
}
publishNodeInfoLocked(deviceName, deviceDescription, configEndpoints, extraRoutes);
}
}
private void publishDiscoveredExternalEndpointLocked() {
List<MultiProtocolSocketAddress> effective = createEffectiveExternalEndpointsLocked();
if (new HashSet<MultiProtocolSocketAddress>(effective)
.equals(new HashSet<MultiProtocolSocketAddress>(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<MultiProtocolSocketAddress> endpoints) {
PublishedNodeInfo published = publishedNodeInfo;
publishNodeInfo(published.getDeviceName(), published.getDeviceDescription(), endpoints,
published.getExtraRoutes());
}
public IPv6AddressGroup getSelf() { public IPv6AddressGroup getSelf() {
@@ -602,10 +750,9 @@ public class KLALBController {
} }
} }
private String generateExternalEndpointsString() { private String generateExternalEndpointsString() {
StringBuilder sbd = new StringBuilder(); StringBuilder sbd = new StringBuilder();
for (Iterator<MultiProtocolSocketAddress> iterator = externalEndpoints.iterator(); iterator.hasNext();) { for (MultiProtocolSocketAddress klalbRemoteLine : publishedNodeInfo.getExternalEndpoints()) {
MultiProtocolSocketAddress klalbRemoteLine = (MultiProtocolSocketAddress) iterator.next();
sbd.append(klalbRemoteLine.toString()); sbd.append(klalbRemoteLine.toString());
sbd.append('\n'); sbd.append('\n');
} }
@@ -764,8 +911,11 @@ public class KLALBController {
} }
} }
}).start(); }).start();
loadSRv6ProtocolStack(selfg, enableVirtualAdapter); loadSRv6ProtocolStack(selfg, enableVirtualAdapter);
loadController(); if (configItem == null) {
publishNodeInfo(srv6Router.getDeviceName(), null, null, null);
}
loadController();
} }
public KLALBController(boolean enableVirtualAdapter, List<InetAddress> dnsaddr) { public KLALBController(boolean enableVirtualAdapter, List<InetAddress> dnsaddr) {
@@ -801,10 +951,9 @@ public class KLALBController {
getIpv6Router().setASN(vasn); getIpv6Router().setASN(vasn);
} }
List<MultiProtocolSocketAddress> linele = configItem.getExternalEndpoints(); List<MultiProtocolSocketAddress> linele = configItem.getExternalEndpoints();
if (linele != null) { publishNodeInfo(configItem.getDeviceName(), configItem.getDeviceDescription(), linele,
getExternalEndpoints().addAll(linele); configItem.getExtraRoutes());
}
List<MultiProtocolSocketAddress> linetoc = configItem.getAutoConnections(); List<MultiProtocolSocketAddress> linetoc = configItem.getAutoConnections();
if (linetoc != null) { if (linetoc != null) {
linetoc.forEach((aline) -> { linetoc.forEach((aline) -> {
@@ -0,0 +1,211 @@
package org.kne.cloud.network.klalb;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.srv6.ExternalEndpointsResult;
import org.kne.cloud.network.srv6.ExtraRoutesResult;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.NodeInfoQueryStatus;
import org.kne.cloud.network.srv6.NodeProfile;
/** Coordinates the three independent sections of a remote node-info query. */
public final class NodeInfoQueryCoordinator {
private static final long DEADLINE_MILLIS = 3000L;
private static final ExecutorService REQUEST_EXECUTOR = Executors.newVirtualThreadPerTaskExecutor();
private static final ScheduledExecutorService DEADLINE_EXECUTOR = Executors
.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r, "KLALB-node-info-deadline");
thread.setDaemon(true);
return thread;
});
private final KLALBRoutingProtocolAPIClient client;
public NodeInfoQueryCoordinator(KLALBRoutingProtocolAPIClient client) {
this.client = Objects.requireNonNull(client, "client");
}
/** Starts all section requests and completes at the first all-sections response or the shared deadline. */
public CompletableFuture<Result> query(SocketAddress address) {
Objects.requireNonNull(address, "address");
Pending pending = new Pending();
pending.result.whenComplete((ignored, error) -> {
if (pending.result.isCancelled()) pending.cancel();
});
pending.deadline = DEADLINE_EXECUTOR.schedule(pending::timeout, DEADLINE_MILLIS, TimeUnit.MILLISECONDS);
issue(pending, () -> client.requestNodeProfile(address, pending::profile), pending::profileFailed);
issue(pending, () -> client.requestExternalEndpoints(address, pending::externalEndpoints),
pending::externalEndpointsFailed);
issue(pending, () -> client.requestExtraRoutes(address, pending::extraRoutes), pending::extraRoutesFailed);
return pending.result;
}
private void issue(Pending pending, ThrowingRequest request, Runnable failure) {
REQUEST_EXECUTOR.execute(() -> pending.issue(request, failure));
}
@FunctionalInterface
private interface ThrowingRequest {
void run() throws Exception;
}
public static final class Section<T> {
private final boolean received;
private final Optional<T> value;
private final Optional<NodeInfoQueryStatus> status;
private Section(boolean received, T value, NodeInfoQueryStatus status) {
this.received = received;
this.value = Optional.ofNullable(value);
this.status = Optional.ofNullable(status);
}
public boolean isReceived() {
return received;
}
public Optional<T> getValue() {
return value;
}
public Optional<NodeInfoQueryStatus> getStatus() {
return status;
}
private static <T> Section<T> missing() {
return new Section<T>(false, null, null);
}
private static <T> Section<T> received(T value, NodeInfoQueryStatus status) {
return new Section<T>(true, value, status);
}
}
public static final class Result {
private final Section<NodeProfile> profile;
private final Section<List<MultiProtocolSocketAddress>> externalEndpoints;
private final Section<List<String>> extraRoutes;
private Result(Section<NodeProfile> profile,
Section<List<MultiProtocolSocketAddress>> externalEndpoints,
Section<List<String>> extraRoutes) {
this.profile = profile;
this.externalEndpoints = externalEndpoints;
this.extraRoutes = extraRoutes;
}
public Section<NodeProfile> getProfile() {
return profile;
}
public Section<List<MultiProtocolSocketAddress>> getExternalEndpoints() {
return externalEndpoints;
}
public Section<List<String>> getExtraRoutes() {
return extraRoutes;
}
}
private final class Pending {
private final CompletableFuture<Result> result = new CompletableFuture<>();
private final AtomicBoolean finished = new AtomicBoolean();
private int remaining = 3;
private Section<NodeProfile> profile = Section.missing();
private Section<List<MultiProtocolSocketAddress>> externalEndpoints = Section.missing();
private Section<List<String>> extraRoutes = Section.missing();
private java.util.concurrent.ScheduledFuture<?> deadline;
private synchronized void profile(NodeProfile value) {
if (profile.isReceived()) return;
profile = Section.received(value, NodeInfoQueryStatus.OK);
completeSection();
}
private synchronized void profileFailed() {
if (profile.isReceived()) return;
profile = Section.missing();
completeSection();
}
private synchronized void externalEndpoints(ExternalEndpointsResult value) {
if (externalEndpoints.isReceived()) return;
if (value == null) {
externalEndpoints = Section.missing();
} else {
List<MultiProtocolSocketAddress> endpoints = value.getExternalEndpoints();
externalEndpoints = Section.received(endpoints == null
? Collections.<MultiProtocolSocketAddress>emptyList()
: Collections.unmodifiableList(new ArrayList<MultiProtocolSocketAddress>(endpoints)),
value.getStatus());
}
completeSection();
}
private synchronized void externalEndpointsFailed() {
if (externalEndpoints.isReceived()) return;
externalEndpoints = Section.missing();
completeSection();
}
private synchronized void extraRoutes(ExtraRoutesResult value) {
if (extraRoutes.isReceived()) return;
if (value == null) {
extraRoutes = Section.missing();
} else {
List<String> routes = value.getExtraRoutes();
extraRoutes = Section.received(routes == null
? Collections.<String>emptyList()
: Collections.unmodifiableList(new ArrayList<String>(routes)), value.getStatus());
}
completeSection();
}
private synchronized void extraRoutesFailed() {
if (extraRoutes.isReceived()) return;
extraRoutes = Section.missing();
completeSection();
}
private synchronized void issue(ThrowingRequest request, Runnable failure) {
if (finished.get() || result.isDone() || result.isCancelled()) return;
try {
request.run();
} catch (Exception e) {
failure.run();
}
}
private synchronized void cancel() {
finish();
}
private void completeSection() {
remaining--;
if (remaining == 0) finish();
}
private synchronized void timeout() {
finish();
}
private void finish() {
if (!finished.compareAndSet(false, true)) return;
if (deadline != null) deadline.cancel(false);
result.complete(new Result(profile, externalEndpoints, extraRoutes));
}
}
}
@@ -5,9 +5,10 @@ import java.awt.datatransfer.StringSelection;
import java.awt.event.*; import java.awt.event.*;
import java.io.IOException; import java.io.IOException;
import java.net.*; import java.net.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Timer; import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.imageio.ImageIO; 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.*;
import org.kne.cloud.network.klalb.ui.GraphPanel.GraphNode; import org.kne.cloud.network.klalb.ui.GraphPanel.GraphNode;
import org.kne.cloud.network.monitor.LinkStatus; 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.XFrame;
import org.kne.ui.YScrollPane; import org.kne.ui.YScrollPane;
@@ -1061,8 +1062,19 @@ public class KLALBStateGUI3 extends XFrame {
} }
for (KLALBConfigItem item : config) { for (KLALBConfigItem item : config) {
if (item instanceof KLALBControllerConfigItem) { if (item instanceof KLALBControllerConfigItem) {
KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item; KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item;
String oldDeviceName = kck.getDeviceName();
String oldDeviceDescription = kck.getDeviceDescription();
List<MultiProtocolSocketAddress> oldExternalEndpoints = kck.getExternalEndpoints() == null
? null
: new ArrayList<MultiProtocolSocketAddress>(kck.getExternalEndpoints());
List<String> oldExtraRoutes = kck.getExtraRoutes() == null
? null : new ArrayList<String>(kck.getExtraRoutes());
String newDeviceName;
String newDeviceDescription;
List<String> newExtraRoutes;
List<MultiProtocolSocketAddress> newExternalEndpoints;
// 保存语言设置 // 保存语言设置
kck.setLanguage(((Language) comboLang.getSelectedItem()).name()); kck.setLanguage(((Language) comboLang.getSelectedItem()).name());
@@ -1070,23 +1082,19 @@ public class KLALBStateGUI3 extends XFrame {
kck.setNogui(nogui.isSelected()); kck.setNogui(nogui.isSelected());
// 保存设备名称 // 保存设备名称
String dnametext = deviceNameSet.getText().trim(); String dnametext = deviceNameSet.getText().trim();
if (dnametext.equals("")) { if (dnametext.equals("")) {
kck.setDeviceName(null); newDeviceName = null;
} else { } else {
kck.setDeviceName(dnametext); newDeviceName = dnametext;
} }
if (kcontroller != null && kcontroller.getIpv6Router() != null) { // 保存设备描述
kcontroller.getIpv6Router().setDeviceName(kck.getDeviceName()); String ddesctext = deviceDescriptionSet.getText().trim();
} if (ddesctext.equals("")) {
newDeviceDescription = null;
// 保存设备描述 } else {
String ddesctext = deviceDescriptionSet.getText().trim(); newDeviceDescription = ddesctext;
if (ddesctext.equals("")) { }
kck.setDeviceDescription(null);
} else {
kck.setDeviceDescription(ddesctext);
}
// 保存IPv6地址 // 保存IPv6地址
@@ -1141,7 +1149,7 @@ public class KLALBStateGUI3 extends XFrame {
} }
} }
} }
kck.setExtraRoutes(eroutes); newExtraRoutes = eroutes;
// 保存ASN // 保存ASN
String asntext = asnFieldSet.getText(); String asntext = asnFieldSet.getText();
@@ -1232,7 +1240,7 @@ public class KLALBStateGUI3 extends XFrame {
} }
} }
} }
kck.setExternalEndpoints(iaddr1); newExternalEndpoints = iaddr1;
// 保存自动连接线路表 // 保存自动连接线路表
String[] splt11 = connectLineTabelSet.getText().split("\n"); String[] splt11 = connectLineTabelSet.getText().split("\n");
@@ -1304,7 +1312,23 @@ public class KLALBStateGUI3 extends XFrame {
kck.setNagleDelayTime(nagleDelayTime.getSlider().getValue()*100000L); 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) { if (tsk5 != null) {
tsk5.cancel(); tsk5.cancel();
} }
tsk5 = new TimerTask() { tsk5 = new TimerTask() {
@Override @Override
public void run() { public void run() {
if (isVisible()) { SwingUtilities.invokeLater(() -> {
graph.loadNodes(); if (isVisible()) {
for (int i = 0; i < 100; i++) { graph.loadNodes();
graph.runPhy(); for (int i = 0; i < 100; i++) {
} graph.runPhy();
graph.repaint(); }
graph.revalidate(); graph.repaint();
} graph.revalidate();
} }
}; });
t.scheduleAtFixedRate(tsk5, 1000, 1000); }
};
t.scheduleAtFixedRate(tsk5, 0, 1000);
} }
/** /**
@@ -1761,9 +1787,18 @@ public class KLALBStateGUI3 extends XFrame {
/** /**
* 关闭窗口并清理资源 * 关闭窗口并清理资源
*/ */
public void close() { public void close() {
setVisible(false); for (int i = 0; i < tabbedPane.getTabCount(); i++) {
if (tsk != null) { Component component = tabbedPane.getComponentAt(i);
if (component instanceof NodeInformationPanel) {
((NodeInformationPanel) component).close();
}
}
if (graph != null) {
graph.close();
}
setVisible(false);
if (tsk != null) {
tsk.cancel(); tsk.cancel();
} }
if (st != null) { if (st != null) {
@@ -8,28 +8,58 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import org.kne.cloud.network.MultiProtocolSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address; import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.klalb.KLALBController; import org.kne.cloud.network.klalb.KLALBController;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection; 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.NodeProfile;
import org.kne.cloud.network.srv6.RouterInfoPacket;
public class NetworkGraphPanel extends GraphPanel { public class NetworkGraphPanel extends GraphPanel {
private KLALBController controller; private KLALBController controller;
private KLALBStateGUI3 klbgui; private KLALBStateGUI3 klbgui;
private final KLALBRoutingProtocolAPIClient nodeInfoClient;
private final KLALBRoutingProtocol nodeInfoRoutingProtocol;
private final BiConsumer<IPv6Address, Integer> nodeInfoUpdateListener;
private final Object nodeInfoLock = new Object();
private final Map<IPv6Address, String> nodeNameCache = new HashMap<IPv6Address, String>();
private final Map<IPv6Address, Long> nodeNameCacheTimes = new HashMap<IPv6Address, Long>();
private final Map<IPv6Address, Long> nodeInfoRequestTimes = new HashMap<IPv6Address, Long>();
private final Map<IPv6Address, Long> nodeInfoRequestGenerations = new HashMap<IPv6Address, Long>();
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) { public NetworkGraphPanel(KLALBController controller,KLALBStateGUI3 klbgui) {
super(); super();
this.controller = controller; this.controller = controller;
this.klbgui=klbgui; 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) { public NetworkGraphPanel(KLALBController kc) {
@@ -153,17 +183,145 @@ public class NetworkGraphPanel extends GraphPanel {
} }
/** /**
* 节点标签:显示广播得知的设备名称(若有)+IP地址 * 节点标签:显示 Tiny API 查询的设备名称(若有)+IP地址
*/ */
private String getNodeText(IPv6Address address) { private String getNodeText(IPv6Address address) {
StringBuilder sb=new StringBuilder(); StringBuilder sb=new StringBuilder();
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address); String dname;
if(dname!=null) synchronized (nodeInfoLock) {
sb.append(dname).append('\n'); dname = nodeNameCache.get(address);
sb.append(InetGraphNode.getText(address)); }
return sb.toString(); 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 requestNodeProfile(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.requestNodeProfile(
new InetSocketAddress(address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
(profile) -> SwingUtilities.invokeLater(() -> handleNodeProfile(address, requestGeneration, profile)));
} catch (IOException e) {
synchronized (nodeInfoLock) {
if (nodeInfoRequestGenerations.get(address) != null
&& nodeInfoRequestGenerations.get(address).longValue() == requestGeneration) {
nodeInfoRequestTimes.remove(address);
nodeInfoRequestGenerations.remove(address);
}
}
}
}
private void handleNodeProfile(IPv6Address address, long requestGeneration, NodeProfile profile) {
if (nodeInfoClosed)
return;
boolean currentRequest;
boolean nodeExists = getNodes().containsKey(address);
String deviceName = profile == null ? "" : profile.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();
requestNodeProfile(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 double nsPerPixel=5000L;
private class InetGraphEdgeGroup extends GraphEdgeGroup{ private class InetGraphEdgeGroup extends GraphEdgeGroup{
public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) { public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) {
@@ -181,6 +339,8 @@ public class NetworkGraphPanel extends GraphPanel {
} }
protected void loadNodes() { protected void loadNodes() {
Map<IPv6Address, Long> addr= controller.getIpv6Router().getKlalbRouteProtol().getAddresses(); Map<IPv6Address, Long> addr= controller.getIpv6Router().getKlalbRouteProtol().getAddresses();
IPv6Address localAddress = controller.getIpv6Router().getLocator().getAddress();
cacheLocalNodeName();
Set<IPv6Address> ks=addr.keySet(); Set<IPv6Address> ks=addr.keySet();
for (Iterator<IPv6Address> iterator = ks.iterator(); iterator.hasNext();) { for (Iterator<IPv6Address> iterator = ks.iterator(); iterator.hasNext();) {
IPv6Address inet6Address = (IPv6Address) iterator.next(); IPv6Address inet6Address = (IPv6Address) iterator.next();
@@ -188,12 +348,15 @@ public class NetworkGraphPanel extends GraphPanel {
Vector2 v2pos=super.getRandomPos(); Vector2 v2pos=super.getRandomPos();
getNodes().put(inet6Address,new InetGraphNode(inet6Address,Color.BLACK,v2pos.x,v2pos.y,inet6Address.equals(controller.getIpv6Router().getLocator().getAddress()))); getNodes().put(inet6Address,new InetGraphNode(inet6Address,Color.BLACK,v2pos.x,v2pos.y,inet6Address.equals(controller.getIpv6Router().getLocator().getAddress())));
} }
if(!inet6Address.equals(localAddress))
requestNodeProfile(inet6Address);
} }
Set<IPv6Address> kns=getNodes().keySet(); Set<IPv6Address> kns=getNodes().keySet();
for (Iterator<IPv6Address> iterator = kns.iterator(); iterator.hasNext();) { for (Iterator<IPv6Address> iterator = kns.iterator(); iterator.hasNext();) {
IPv6Address inet6Address = (IPv6Address) iterator.next(); IPv6Address inet6Address = (IPv6Address) iterator.next();
if(!addr.containsKey(inet6Address)) { if(!addr.containsKey(inet6Address)) {
iterator.remove(); iterator.remove();
removeNodeInfoState(inet6Address);
}else { }else {
((InetGraphNode)getNodes().get(inet6Address)).updateLabel(); ((InetGraphNode)getNodes().get(inet6Address)).updateLabel();
} }
@@ -4,9 +4,9 @@ import java.awt.BorderLayout;
import java.awt.Image; import java.awt.Image;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
import java.io.IOException; import java.net.InetSocketAddress;
import java.net.InetSocketAddress; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
@@ -15,33 +15,42 @@ import javax.swing.JButton;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JTextArea; import javax.swing.JTextArea;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import org.kne.cloud.klalb.uitool.XDefaultListModel; import org.kne.cloud.klalb.uitool.XDefaultListModel;
import org.kne.cloud.network.MultiProtocolSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address; import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.klalb.KLALBController; import org.kne.cloud.network.klalb.KLALBController;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol; import org.kne.cloud.network.klalb.NodeInfoQueryCoordinator;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient; import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.NodeInfoQueryStatus;
import org.kne.cloud.network.srv6.NodeProfile;
import javax.swing.JTabbedPane; import javax.swing.JTabbedPane;
import javax.swing.JList; import javax.swing.JList;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.util.concurrent.atomic.AtomicLong;
public class NodeInformationPanel extends JPanel { public class NodeInformationPanel extends JPanel {
private IPv6Address address; private IPv6Address address;
private KLALBRoutingProtocolAPIClient client; private KLALBRoutingProtocolAPIClient client;
private NodeInfoQueryCoordinator queryCoordinator;
private KLALBController controller; private KLALBController controller;
private Image image; private Image image;
private XDefaultListModel<MultiProtocolSocketAddress> listModel=new XDefaultListModel<>(); private XDefaultListModel<MultiProtocolSocketAddress> listModel=new XDefaultListModel<>();
private XDefaultListModel<String> extraRoutesModel=new XDefaultListModel<>();
private volatile boolean active=true;
private final AtomicLong fullInfoGeneration=new AtomicLong();
private final java.util.function.BiConsumer<IPv6Address,Integer> nodeInfoUpdateListener;
public KLALBController getController() { public KLALBController getController() {
return controller; return controller;
} }
@@ -73,16 +82,20 @@ public class NodeInformationPanel extends JPanel {
overviewArea.setWrapStyleWord(true); overviewArea.setWrapStyleWord(true);
overviewArea.setFont(UIEnv.getFont().deriveFont(14.0f)); overviewArea.setFont(UIEnv.getFont().deriveFont(14.0f));
overviewArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); overviewArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address); overviewArea.setText(buildOverviewText(null, null));
String ddesc=null; panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER);
if(address.equals(controller.getIpv6Router().getLocator().getAddress())&&controller.getConfigItem()!=null) {
ddesc=controller.getConfigItem().getDeviceDescription(); JPanel extraRoutesPanel = new JPanel(new BorderLayout());
if(ddesc!=null&&ddesc.isEmpty()) { JList<String> extraRoutesList = new JList<String>(extraRoutesModel);
ddesc=null; extraRoutesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
} extraRoutesList.setFont(UIEnv.getFont().deriveFont(14.0f));
} extraRoutesPanel.add(new JScrollPane(extraRoutesList), BorderLayout.CENTER);
overviewArea.setText(buildOverviewText(dname, ddesc)); javax.swing.JLabel extraRoutesEmptyLabel=new javax.swing.JLabel(UIEnv.getRsb().getString("noextraroutes"));
panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER); 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(); JPanel panel_1 = new JPanel();
panel_1.setLayout(new BorderLayout(0, 0)); panel_1.setLayout(new BorderLayout(0, 0));
@@ -163,28 +176,110 @@ public class NodeInformationPanel extends JPanel {
panel_1.add(btnNewButton, BorderLayout.SOUTH); panel_1.add(btnNewButton, BorderLayout.SOUTH);
client=new KLALBRoutingProtocolAPIClient(controller.getIpv6Router().getKlalbRouteProtol()); KLALBRoutingProtocol routingProtocol=controller.getIpv6Router().getKlalbRouteProtol();
try { client=new KLALBRoutingProtocolAPIClient(routingProtocol);
client.requestNodeInfoFull(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (info)->{ queryCoordinator=new NodeInfoQueryCoordinator(client);
listModel.clear(); nodeInfoUpdateListener=(updatedAddress, flags) -> {
for (MultiProtocolSocketAddress multiProtocolSocketAddress : info.getOpenLines()) { if(active && address.equals(updatedAddress)
listModel.addElement(multiProtocolSocketAddress); && (flags & org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED) != 0) {
} requestFullInfo(overviewArea, extraRoutesEmptyLabel);
// 用对端返回的设备名称/描述更新概览(旧版本节点无该字段时保留原显示) }
String dn=info.getDeviceName(); };
if(dn==null||dn.isEmpty()) { routingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener);
dn=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address); requestFullInfo(overviewArea, extraRoutesEmptyLabel);
} }
String dd=info.getDeviceDescription();
if(dd!=null&&dd.isEmpty()) { private void requestFullInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel) {
dd=null; final long generation=fullInfoGeneration.incrementAndGet();
} if (controller.getIpv6Router().getLocator().getAddress().equals(address)) {
overviewArea.setText(buildOverviewText(dn, dd)); KLALBController.PublishedNodeInfo published=controller.getPublishedNodeInfo();
}); applyNodeInfo(overviewArea, extraRoutesEmptyLabel, published.getDeviceName(),
} catch (IOException e) { published.getDeviceDescription(), published.getExternalEndpoints(), published.getExtraRoutes());
e.printStackTrace(); return;
} }
} try {
queryCoordinator.query(new InetSocketAddress(address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT)).whenComplete((info, error)->{
if(!active || generation!=fullInfoGeneration.get()) return;
javax.swing.SwingUtilities.invokeLater(() -> {
if(!active || generation!=fullInfoGeneration.get()) return;
if (error != null || info == null) {
return;
}
applyRemoteNodeInfo(overviewArea, extraRoutesEmptyLabel, info);
});
});
} catch (RuntimeException e) {
if(active) e.printStackTrace();
}
}
private void applyNodeInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel,
String deviceName, String deviceDescription, List<MultiProtocolSocketAddress> endpoints,
List<String> routes) {
applyProfile(overviewArea, deviceName, deviceDescription);
applyEndpoints(endpoints == null ? Collections.<MultiProtocolSocketAddress>emptyList() : endpoints);
applyRoutes(extraRoutesEmptyLabel, routes == null ? Collections.<String>emptyList() : routes);
}
private void applyRemoteNodeInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel,
NodeInfoQueryCoordinator.Result info) {
if (info.getProfile().isReceived()) {
NodeProfile profile=info.getProfile().getValue().orElse(null);
applyProfile(overviewArea, profile == null ? null : profile.getDeviceName(),
profile == null ? null : profile.getDeviceDescription());
}
if (info.getExternalEndpoints().isReceived()) {
List<MultiProtocolSocketAddress> endpoints=info.getExternalEndpoints().getStatus()
.filter(NodeInfoQueryStatus.OK::equals).isPresent()
? info.getExternalEndpoints().getValue().orElse(Collections.<MultiProtocolSocketAddress>emptyList())
: Collections.<MultiProtocolSocketAddress>emptyList();
applyEndpoints(endpoints);
}
if (info.getExtraRoutes().isReceived()) {
List<String> routes=info.getExtraRoutes().getStatus().filter(NodeInfoQueryStatus.OK::equals).isPresent()
? info.getExtraRoutes().getValue().orElse(Collections.<String>emptyList())
: Collections.<String>emptyList();
applyRoutes(extraRoutesEmptyLabel, routes);
}
}
private void applyProfile(JTextArea overviewArea, String deviceName, String deviceDescription) {
overviewArea.setText(buildOverviewText(deviceName,
deviceDescription == null || deviceDescription.isEmpty() ? null : deviceDescription));
}
private void applyEndpoints(List<MultiProtocolSocketAddress> endpoints) {
listModel.clear();
if (endpoints != null) for (MultiProtocolSocketAddress item : endpoints) listModel.addElement(item);
}
private void applyRoutes(javax.swing.JLabel extraRoutesEmptyLabel, List<String> routes) {
extraRoutesModel.clear();
if (routes != null) for (String route : routes) extraRoutesModel.addElement(route);
extraRoutesEmptyLabel.setText(UIEnv.getRsb().getString("noextraroutes"));
extraRoutesEmptyLabel.setVisible(extraRoutesModel.isEmpty());
}
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) { private String buildOverviewText(String dname,String ddesc) {
StringBuilder sb=new StringBuilder(); StringBuilder sb=new StringBuilder();
@@ -7,6 +7,7 @@ import java.nio.file.*;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import com.sun.net.httpserver.*; import com.sun.net.httpserver.*;
import com.google.gson.*; import com.google.gson.*;
@@ -19,12 +20,14 @@ import org.kne.cloud.network.ipv6.IPv6NetworkLink;
import org.kne.cloud.network.ipv6.RouteItem; import org.kne.cloud.network.ipv6.RouteItem;
import org.kne.cloud.network.klalb.*; import org.kne.cloud.network.klalb.*;
import org.kne.cloud.network.monitor.LinkStatus; import org.kne.cloud.network.monitor.LinkStatus;
import org.kne.cloud.network.srv6.KLALBNodeInformation;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol; import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection; import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient; import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.NodeInfoQueryStatus;
import org.kne.cloud.network.srv6.NodeProfile;
import org.kne.cloud.network.srv6.NeighborInfo; import org.kne.cloud.network.srv6.NeighborInfo;
import org.kne.cloud.network.srv6.RouterInfo; import org.kne.cloud.network.srv6.RouterInfo;
import org.kne.cloud.network.srv6.RouterInfoPacket;
import org.kne.cloud.network.srv6.SRv6Router; import org.kne.cloud.network.srv6.SRv6Router;
/** /**
@@ -35,14 +38,12 @@ public class KLALBWebServer {
private final KLALBProxySystem proxySystem; private final KLALBProxySystem proxySystem;
private final MultiProtocolSocketAddress listen; private final MultiProtocolSocketAddress listen;
private HttpServer server; private HttpServer server;
private ExecutorService httpExecutor;
private final Gson gson; private final Gson gson;
private final ScheduledExecutorService sseExecutor = Executors.newSingleThreadScheduledExecutor(r -> { private ScheduledExecutorService sseExecutor = createSseExecutor();
Thread t = new Thread(r, "KLALB-Web-SSE");
t.setDaemon(true);
return t;
});
private final Set<HttpExchange> sseClients = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set<HttpExchange> sseClients = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final AtomicBoolean running = new AtomicBoolean(false); private final AtomicBoolean running = new AtomicBoolean(false);
private final Object configUpdateLock = new Object();
public KLALBWebServer(KLALBProxySystem proxySystem, int port) { public KLALBWebServer(KLALBProxySystem proxySystem, int port) {
this(proxySystem, new MultiProtocolSocketAddress("http", "0.0.0.0", port)); this(proxySystem, new MultiProtocolSocketAddress("http", "0.0.0.0", port));
@@ -52,15 +53,36 @@ public class KLALBWebServer {
this.proxySystem = proxySystem; this.proxySystem = proxySystem;
this.listen = listen; this.listen = listen;
this.gson = proxySystem.getGson(); this.gson = proxySystem.getGson();
this.nodeInfoUpdateListener = (address, flags) -> {
if ((flags & RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED) != 0) {
nodeInfoRevisions.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 { public synchronized void start() throws IOException {
if (running.get()) return; if (running.get()) return;
if (sseExecutor == null || sseExecutor.isShutdown()) {
sseExecutor = createSseExecutor();
}
InetSocketAddress socketAddress = "0.0.0.0".equals(listen.getHost()) InetSocketAddress socketAddress = "0.0.0.0".equals(listen.getHost())
? new InetSocketAddress(listen.getPort()) : listen.getSocketAddress(); ? new InetSocketAddress(listen.getPort()) : listen.getSocketAddress();
server = HttpServer.create(socketAddress, 0); server = HttpServer.create(socketAddress, 0);
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
server.setExecutor(executor);
// API Contexts // API Contexts
server.createContext("/api/status", this::handleStatus); server.createContext("/api/status", this::handleStatus);
@@ -76,7 +98,13 @@ public class KLALBWebServer {
// Static Files / SPA Fallback Handler // Static Files / SPA Fallback Handler
server.createContext("/", this::handleStatic); server.createContext("/", this::handleStatic);
server.start(); try {
server.start();
httpExecutor = executor;
} catch (RuntimeException e) {
executor.shutdownNow();
throw e;
}
running.set(true); running.set(true);
startSseBroadcaster(); startSseBroadcaster();
@@ -84,7 +112,11 @@ public class KLALBWebServer {
} }
public synchronized void stop() { public synchronized void stop() {
if (!running.get()) return; if (!running.get()) {
closeHttpExecutor();
closeNodeInfoClient();
return;
}
running.set(false); running.set(false);
sseExecutor.shutdownNow(); sseExecutor.shutdownNow();
for (HttpExchange client : sseClients) { for (HttpExchange client : sseClients) {
@@ -97,9 +129,18 @@ public class KLALBWebServer {
server.stop(1); server.stop(1);
server = null; server = null;
} }
closeHttpExecutor();
closeNodeInfoClient();
System.out.println("KLALB Web Dashboard stopped."); System.out.println("KLALB Web Dashboard stopped.");
} }
private void closeHttpExecutor() {
if (httpExecutor != null) {
httpExecutor.shutdownNow();
httpExecutor = null;
}
}
public boolean isRunning() { public boolean isRunning() {
return running.get(); return running.get();
} }
@@ -456,14 +497,159 @@ public class KLALBWebServer {
} }
private KLALBRoutingProtocolAPIClient nodeInfoClient; private KLALBRoutingProtocolAPIClient nodeInfoClient;
private static final long TINY_NAME_REQUEST_TIMEOUT_MS = 3000L;
private final ConcurrentMap<IPv6Address, String> tinyDeviceNameCache = new ConcurrentHashMap<>();
private final ConcurrentMap<IPv6Address, Long> tinyDeviceNameCacheTimes = new ConcurrentHashMap<>();
private final Set<IPv6Address> tinyNameRequestsInFlight = ConcurrentHashMap.newKeySet();
private final ConcurrentMap<IPv6Address, Long> tinyNameRequestTimes = new ConcurrentHashMap<>();
private final ConcurrentMap<IPv6Address, Long> tinyNameRequestGenerations = new ConcurrentHashMap<>();
private final ConcurrentMap<IPv6Address, Long> nodeInfoRevisions = 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<IPv6Address, Integer> nodeInfoUpdateListener;
private long nodeInfoLifecycleGeneration;
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) { 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) { if (nodeInfoClient == null) {
nodeInfoClient = new KLALBRoutingProtocolAPIClient(kc.getIpv6Router().getKlalbRouteProtol()); nodeInfoRoutingProtocol = kc.getIpv6Router().getKlalbRouteProtol();
nodeInfoClient = new KLALBRoutingProtocolAPIClient(nodeInfoRoutingProtocol);
nodeInfoRoutingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener);
} }
return nodeInfoClient; 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.requestNodeProfile(
new InetSocketAddress(addr.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
profile -> {
synchronized (tinyNameStateLock) {
Long activeRequestGeneration = tinyNameRequestGenerations.get(addr);
if (!Long.valueOf(requestGenerationToken).equals(activeRequestGeneration)) return;
tinyDeviceNameCache.put(addr,
profile != null && profile.getDeviceName() != null ? profile.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 { private void handleNodeInfo(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return; if (handleCorsPreflight(exchange)) return;
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) { if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
@@ -500,56 +686,70 @@ public class KLALBWebServer {
if (isSelf) { if (isSelf) {
// 本机:描述直接取本地控制器配置 // 本机:描述直接取本地控制器配置
KLALBControllerConfigItem cfg = proxySystem.getControllerConfig(); KLALBController.PublishedNodeInfo published = kc.getPublishedNodeInfo();
resp.addProperty("isSelf", true); resp.addProperty("isSelf", true);
String dname = router.getDeviceName() != null ? router.getDeviceName() resp.addProperty("reachable", true);
: (cfg != null && cfg.getDeviceName() != null ? cfg.getDeviceName() : ""); resp.addProperty("deviceName", published.getDeviceName() != null ? published.getDeviceName() : "");
resp.addProperty("deviceName", dname);
resp.addProperty("deviceDescription", resp.addProperty("deviceDescription",
cfg != null && cfg.getDeviceDescription() != null ? cfg.getDeviceDescription() : ""); published.getDeviceDescription() != null ? published.getDeviceDescription() : "");
JsonArray lines = new JsonArray(); JsonArray lines = new JsonArray();
for (MultiProtocolSocketAddress mpsa : kc.getExternalEndpoints()) { for (MultiProtocolSocketAddress mpsa : published.getExternalEndpoints()) {
lines.add(new JsonPrimitive(mpsa.toString())); lines.add(new JsonPrimitive(mpsa.toString()));
} }
resp.add("openLines", lines); 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); sendJsonResponse(exchange, 200, resp);
return; return;
} }
// 远端节点:经 SRv6 虚拟网络发送完整节点信息查询(异步回调,限时等待) // 远端节点:经 SRv6 虚拟网络发送完整节点信息查询(异步回调,限时等待)
resp.addProperty("isSelf", false); resp.addProperty("isSelf", false);
CompletableFuture<KLALBNodeInformation> future = new CompletableFuture<>();
try { try {
getNodeInfoClient(kc).requestNodeInfoFull( KLALBRoutingProtocolAPIClient client = getNodeInfoClient(kc);
new InetSocketAddress(target.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), if (client == null) {
info -> future.complete(info)); sendError(exchange, 503, "Node info service stopped");
return;
KLALBNodeInformation info;
try {
info = future.get(3, TimeUnit.SECONDS);
} catch (TimeoutException te) {
info = null;
} }
NodeInfoQueryCoordinator.Result info = new NodeInfoQueryCoordinator(client)
String dname = info != null && info.getDeviceName() != null && !info.getDeviceName().isEmpty() .query(new InetSocketAddress(target.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT)).get();
? info.getDeviceName() NodeInfoQueryCoordinator.Section<NodeProfile> profile = info.getProfile();
: router.getKlalbRouteProtol().getDeviceName(target); NodeProfile nodeProfile = profile.getValue().orElse(null);
resp.addProperty("deviceName", dname != null ? dname : ""); String dname = nodeProfile != null && nodeProfile.getDeviceName() != null
&& !nodeProfile.getDeviceName().isEmpty() ? nodeProfile.getDeviceName() : "";
resp.addProperty("deviceName", dname);
resp.addProperty("deviceDescription", resp.addProperty("deviceDescription",
info != null && info.getDeviceDescription() != null ? info.getDeviceDescription() : ""); nodeProfile != null && nodeProfile.getDeviceDescription() != null
resp.addProperty("reachable", info != null); ? nodeProfile.getDeviceDescription() : "");
resp.addProperty("reachable", profile.isReceived()
|| info.getExternalEndpoints().isReceived() || info.getExtraRoutes().isReceived());
JsonArray lines = new JsonArray(); JsonArray lines = new JsonArray();
if (info != null && info.getOpenLines() != null) { if (info.getExternalEndpoints().getStatus().filter(NodeInfoQueryStatus.OK::equals).isPresent()
for (MultiProtocolSocketAddress mpsa : info.getOpenLines()) { && info.getExternalEndpoints().getValue().isPresent()) {
for (MultiProtocolSocketAddress mpsa : info.getExternalEndpoints().getValue().get()) {
lines.add(new JsonPrimitive(mpsa.toString())); lines.add(new JsonPrimitive(mpsa.toString()));
} }
} }
resp.add("openLines", lines); resp.add("openLines", lines);
JsonArray extraRoutes = new JsonArray();
if (info.getExtraRoutes().getStatus().filter(NodeInfoQueryStatus.OK::equals).isPresent()
&& info.getExtraRoutes().getValue().isPresent()) {
for (String route : info.getExtraRoutes().getValue().get()) {
extraRoutes.add(new JsonPrimitive(route != null ? route : ""));
}
}
resp.add("extraRoutes", extraRoutes);
} catch (Exception e) { } catch (Exception e) {
resp.addProperty("deviceName", ""); resp.addProperty("deviceName", "");
resp.addProperty("deviceDescription", ""); resp.addProperty("deviceDescription", "");
resp.addProperty("reachable", false); resp.addProperty("reachable", false);
resp.add("openLines", new JsonArray()); resp.add("openLines", new JsonArray());
resp.add("extraRoutes", new JsonArray());
} }
sendJsonResponse(exchange, 200, resp); sendJsonResponse(exchange, 200, resp);
} }
@@ -584,6 +784,17 @@ public class KLALBWebServer {
KLALBRoutingProtocol rproto = kc.getIpv6Router().getKlalbRouteProtol(); KLALBRoutingProtocol rproto = kc.getIpv6Router().getKlalbRouteProtol();
Map<IPv6Address, Long> addrs = rproto.getAddresses(); Map<IPv6Address, Long> addrs = rproto.getAddresses();
IPv6Address selfAddr = kc.getIpv6Router().getLocator().getAddress(); IPv6Address selfAddr = kc.getIpv6Router().getLocator().getAddress();
KLALBController.PublishedNodeInfo published = kc.getPublishedNodeInfo();
Set<IPv6Address> activeAddresses = addrs == null
? Collections.<IPv6Address>emptySet() : new HashSet<IPv6Address>(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));
}
nodeInfoRevisions.keySet().removeIf(address -> !activeAddresses.contains(address));
if (addrs != null) { if (addrs != null) {
for (IPv6Address addr : addrs.keySet()) { for (IPv6Address addr : addrs.keySet()) {
@@ -592,8 +803,12 @@ public class KLALBWebServer {
nodeObj.addProperty("address", addr.toString()); nodeObj.addProperty("address", addr.toString());
nodeObj.addProperty("compressedAddress", addr.toCompressedString()); nodeObj.addProperty("compressedAddress", addr.toCompressedString());
nodeObj.addProperty("isSelf", addr.equals(selfAddr)); 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 : ""); nodeObj.addProperty("deviceName", dname != null ? dname : "");
long nodeInfoRevision = addr.equals(selfAddr) ? published.getFullRevision()
: nodeInfoRevisions.getOrDefault(addr, 0L);
nodeObj.addProperty("nodeInfoRevision", nodeInfoRevision);
nodesArray.add(nodeObj); nodesArray.add(nodeObj);
} }
} }
@@ -644,21 +859,33 @@ public class KLALBWebServer {
sendError(exchange, 404, "Configuration not found"); sendError(exchange, 404, "Configuration not found");
} }
} else if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) { } else if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) {
synchronized (configUpdateLock) {
String body = readRequestBody(exchange); String body = readRequestBody(exchange);
try { try {
JsonObject json = new JsonParser().parse(body).getAsJsonObject(); JsonObject json = new JsonParser().parse(body).getAsJsonObject();
KLALBControllerConfigItem current = proxySystem.getControllerConfig(); KLALBControllerConfigItem current = proxySystem.getControllerConfig();
if (current != null) { if (current != null) {
String oldDeviceName = current.getDeviceName();
String oldDeviceDescription = current.getDeviceDescription();
List<MultiProtocolSocketAddress> oldExternalEndpoints = current.getExternalEndpoints() == null
? null
: new ArrayList<MultiProtocolSocketAddress>(current.getExternalEndpoints());
List<String> oldExtraRoutes = current.getExtraRoutes() == null
? null : new ArrayList<String>(current.getExtraRoutes());
String newDeviceName = oldDeviceName;
String newDeviceDescription = oldDeviceDescription;
List<MultiProtocolSocketAddress> newExternalEndpoints = current.getExternalEndpoints();
List<String> newExtraRoutes = current.getExtraRoutes();
if (json.has("deviceName") && !json.get("deviceName").isJsonNull()) { 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()) { } 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()) { 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()) { } 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()) { if (json.has("language") && !json.get("language").isJsonNull()) {
@@ -733,7 +960,7 @@ public class KLALBWebServer {
list.add(new MultiProtocolSocketAddress(el.getAsString())); list.add(new MultiProtocolSocketAddress(el.getAsString()));
} }
} }
current.setExternalEndpoints(list); newExternalEndpoints = list;
} }
if (json.has("autoConnections") || json.has("AutoConnections") || json.has("connectLineTable") || json.has("ConnectLineTable")) { if (json.has("autoConnections") || json.has("AutoConnections") || json.has("connectLineTable") || json.has("ConnectLineTable")) {
@@ -783,7 +1010,7 @@ public class KLALBWebServer {
list.add(el.getAsString()); list.add(el.getAsString());
} }
} }
current.setExtraRoutes(list); newExtraRoutes = list;
} }
if (json.has("networkInterfaceExcepts") || json.has("NetworkInterfaceExcepts")) { if (json.has("networkInterfaceExcepts") || json.has("NetworkInterfaceExcepts")) {
@@ -845,6 +1072,23 @@ public class KLALBWebServer {
current.setDenyExternalEndpointBroadcast(json.get("denyLineTableBroadcast").getAsBoolean()); 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 // Trigger GUI save consumer or save directly
if (proxySystem.getKLALBGUI() != null && proxySystem.getKLALBGUI().getSaveComsumer() != null) { if (proxySystem.getKLALBGUI() != null && proxySystem.getKLALBGUI().getSaveComsumer() != null) {
proxySystem.getKLALBGUI().getSaveComsumer().accept(proxySystem.getConfig()); proxySystem.getKLALBGUI().getSaveComsumer().accept(proxySystem.getConfig());
@@ -862,6 +1106,7 @@ public class KLALBWebServer {
} catch (Exception e) { } catch (Exception e) {
sendError(exchange, 400, "Failed to update configuration: " + e.getMessage()); sendError(exchange, 400, "Failed to update configuration: " + e.getMessage());
} }
}
} else { } else {
sendError(exchange, 405, "Method not allowed"); sendError(exchange, 405, "Method not allowed");
} }
@@ -0,0 +1,49 @@
package org.kne.cloud.network.srv6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.kne.cloud.network.MultiProtocolSocketAddress;
public final class ExternalEndpointsResult {
private final List<MultiProtocolSocketAddress> externalEndpoints;
private final NodeInfoQueryStatus status;
public ExternalEndpointsResult(List<MultiProtocolSocketAddress> externalEndpoints, NodeInfoQueryStatus status) {
this.externalEndpoints = Collections.unmodifiableList(new ArrayList<MultiProtocolSocketAddress>(
externalEndpoints == null ? Collections.<MultiProtocolSocketAddress>emptyList() : externalEndpoints));
this.status = status;
}
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return externalEndpoints;
}
public NodeInfoQueryStatus getStatus() {
return status;
}
@Override
public int hashCode() {
return Objects.hash(externalEndpoints, status);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ExternalEndpointsResult)) {
return false;
}
ExternalEndpointsResult other = (ExternalEndpointsResult) obj;
return Objects.equals(externalEndpoints, other.externalEndpoints) && status == other.status;
}
@Override
public String toString() {
return "ExternalEndpointsResult [externalEndpoints=" + externalEndpoints + ", status=" + status + "]";
}
}
@@ -0,0 +1,47 @@
package org.kne.cloud.network.srv6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public final class ExtraRoutesResult {
private final List<String> extraRoutes;
private final NodeInfoQueryStatus status;
public ExtraRoutesResult(List<String> extraRoutes, NodeInfoQueryStatus status) {
this.extraRoutes = Collections.unmodifiableList(new ArrayList<String>(
extraRoutes == null ? Collections.<String>emptyList() : extraRoutes));
this.status = status;
}
public List<String> getExtraRoutes() {
return extraRoutes;
}
public NodeInfoQueryStatus getStatus() {
return status;
}
@Override
public int hashCode() {
return Objects.hash(extraRoutes, status);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ExtraRoutesResult)) {
return false;
}
ExtraRoutesResult other = (ExtraRoutesResult) obj;
return Objects.equals(extraRoutes, other.extraRoutes) && status == other.status;
}
@Override
public String toString() {
return "ExtraRoutesResult [extraRoutes=" + extraRoutes + ", status=" + status + "]";
}
}
@@ -33,7 +33,8 @@ public class JsonDataPacket extends KLALBRoutingProtocolPacket implements Serial
private String data; 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; private static Gson gson;
static{ static{
@@ -73,9 +74,12 @@ public class JsonDataPacket extends KLALBRoutingProtocolPacket implements Serial
@Override @Override
public void writeToChannel(WritableByteChannel dto) throws IOException { public void writeToChannel(WritableByteChannel dto) throws IOException {
byte[]bta=data.getBytes(Charset.forName("UTF-8")); byte[]bta=data.getBytes(Charset.forName("UTF-8"));
getHeader().putChar(1,(char) bta.length); 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); super.writeToChannel(dto);
dto.write(ByteBuffer.wrap(bta)); dto.write(ByteBuffer.wrap(bta));
} }
@@ -1,52 +0,0 @@
package org.kne.cloud.network.srv6;
import java.util.List;
import org.kne.cloud.network.MultiProtocolSocketAddress;
/**
* 节点信息开放线路 + 设备名称 + 设备描述由路由协议 JSON API 查询获得
*/
public class KLALBNodeInformation {
private List<MultiProtocolSocketAddress> openLines;
private String deviceName;
private String deviceDescription;
public KLALBNodeInformation(List<MultiProtocolSocketAddress> openLines, String deviceName,
String deviceDescription) {
super();
this.openLines = openLines;
this.deviceName = deviceName;
this.deviceDescription = deviceDescription;
}
public List<MultiProtocolSocketAddress> getOpenLines() {
return openLines;
}
public void setOpenLines(List<MultiProtocolSocketAddress> openLines) {
this.openLines = openLines;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getDeviceDescription() {
return deviceDescription;
}
public void setDeviceDescription(String deviceDescription) {
this.deviceDescription = deviceDescription;
}
@Override
public String toString() {
return "KLALBNodeInformation [openLines=" + openLines + ", deviceName=" + deviceName
+ ", deviceDescription=" + deviceDescription + "]";
}
}
@@ -13,15 +13,18 @@ import java.nio.channels.Channels;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.kne.cloud.network.ipv6.IPv6Address; import org.kne.cloud.network.ipv6.IPv6Address;
@@ -41,8 +44,12 @@ public class KLALBRoutingProtocol extends Thread{
private static final boolean debug = false; private static final boolean debug = false;
private RouterInfo selfRouterInfo; private RouterInfo selfRouterInfo;
private Map<IPv6Address, RouterInfo> netmap=new ConcurrentHashMap<>(); private Map<IPv6Address, RouterInfo> 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<IPv6Address, LinkedHashSet<Long>> seenNodeInfoUpdates = new HashMap<>();
public Map<IPv6Address, RouterInfo> getNetmap() { public Map<IPv6Address, RouterInfo> getNetmap() {
return netmap; return netmap;
@@ -84,15 +91,67 @@ public class KLALBRoutingProtocol extends Thread{
private KLALBVirtualRawSocket ds = null; private KLALBVirtualRawSocket ds = null;
private ReentrantLock sendLock=new ReentrantLock(); private ReentrantLock sendLock=new ReentrantLock();
private Set<BiConsumer<SocketAddress,JsonDataPacket>>receivers=new CopyOnWriteArraySet<BiConsumer<SocketAddress,JsonDataPacket>>(); private Set<BiConsumer<SocketAddress,JsonDataPacket>>receivers=new CopyOnWriteArraySet<BiConsumer<SocketAddress,JsonDataPacket>>();
private Set<BiConsumer<IPv6Address,Integer>> nodeInfoUpdateListeners = new CopyOnWriteArraySet<BiConsumer<IPv6Address,Integer>>();
public void addReceiver(BiConsumer<SocketAddress,JsonDataPacket> rec) { public void addReceiver(BiConsumer<SocketAddress,JsonDataPacket> rec) {
receivers.add(rec); receivers.add(rec);
} }
public void removeReceiver(BiConsumer<SocketAddress,JsonDataPacket> rec) { public void removeReceiver(BiConsumer<SocketAddress,JsonDataPacket> rec) {
receivers.remove(rec); receivers.remove(rec);
} }
public void addNodeInfoUpdateListener(BiConsumer<IPv6Address,Integer> listener) {
nodeInfoUpdateListeners.add(listener);
}
public void removeNodeInfoUpdateListener(BiConsumer<IPv6Address,Integer> 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<Long> 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<IPv6Address,Integer> listener : nodeInfoUpdateListeners) {
try {
listener.accept(origin, flags);
} catch(Throwable e) {
if(debug) {
e.printStackTrace();
}
}
}
}
@Override @Override
public void run() { public void run() {
@@ -103,11 +162,22 @@ public class KLALBRoutingProtocol extends Thread{
new Thread(()->{ new Thread(()->{
Thread.currentThread().setName("KLALB路由协议发送线程"); Thread.currentThread().setName("KLALB路由协议发送线程");
while(true) { while(true) {
try { try {
selfRouterInfo = getSelfRouterInfo(); selfRouterInfo = getSelfRouterInfo();
RouterInfo oslf=netmap.put(selfRouterInfo.getLocator().getAddress(), selfRouterInfo); RouterInfo oslf=netmap.put(selfRouterInfo.getLocator().getAddress(), selfRouterInfo);
noticeUpdate(); 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))) { /*if(oslf==null||(!selfRouterInfo.equals(oslf))) {
long cur=System.nanoTime(); long cur=System.nanoTime();
if(cur-floodTimer2>5000000000L) { if(cur-floodTimer2>5000000000L) {
@@ -147,10 +217,12 @@ public class KLALBRoutingProtocol extends Thread{
if(val.getAsn()==router.getASN()) { if(val.getAsn()==router.getASN()) {
timeout=2000000000L; timeout=2000000000L;
} }
if(val.checkTimeOut(timeout)) { if(val.checkTimeOut(timeout)) {
iterator.remove(); if(netmap.remove(type.getKey(), val)) {
noticeUpdate(); removeNodeInfoUpdateWindow(type.getKey());
} noticeUpdate();
}
}
/*List<NeighborInfo> ads=val.getNeighborAddresses(); /*List<NeighborInfo> ads=val.getNeighborAddresses();
for (Iterator<NeighborInfo> iterator2 = ads.iterator(); iterator2.hasNext();) { for (Iterator<NeighborInfo> iterator2 = ads.iterator(); iterator2.hasNext();) {
Inet6Address address=iterator2.next().getLocator().getAddress(); Inet6Address address=iterator2.next().getLocator().getAddress();
@@ -216,7 +288,7 @@ public class KLALBRoutingProtocol extends Thread{
ds.receive(dgp); ds.receive(dgp);
//System.out.println(Arrays.toString( Arrays.copyOf( dgp.getData(),dgp.getLength()))); //System.out.println(Arrays.toString( Arrays.copyOf( dgp.getData(),dgp.getLength())));
ByteArrayInputStream bi=new ByteArrayInputStream(dgp.getData(),0,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()) { switch(kp.getType()) {
case KLALBRoutingProtocolPacket.RINFO_REQ: case KLALBRoutingProtocolPacket.RINFO_REQ:
@@ -234,28 +306,34 @@ public class KLALBRoutingProtocol extends Thread{
break; break;
case KLALBRoutingProtocolPacket.RINFO: case KLALBRoutingProtocolPacket.RINFO:
RouterInfoPacket rifp=(RouterInfoPacket) kp; RouterInfoPacket rifp=(RouterInfoPacket) kp;
RouterInfo rif=rifp.getRinfo(); RouterInfo rif=rifp.getRinfo();
RouterInfo oldrif=netmap.get(rif.getLocator().getAddress()); 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) if(debug)
System.out.println(rif); System.out.println(rif);
if(oldrif==null||oldrif.getCreateTime()<rif.getCreateTime()) { boolean topologyUpdated = oldrif==null||oldrif.getCreateTime()<rif.getCreateTime();
if(topologyUpdated) {
if(debug) if(debug)
System.out.println("update RouterInfo"); System.out.println("update RouterInfo");
netmap.put(rif.getLocator().getAddress(), rif); netmap.put(rif.getLocator().getAddress(), rif);
noticeUpdate(); noticeUpdate();
if(rifp.isFlood()) { }else {
floodPacket( dgp.getSocketAddress(), rifp);
}
}else {
if(debug) if(debug)
System.out.println("dispose RouterInfo"); System.out.println("dispose RouterInfo");
} }
computeShortestPathIfUpdated(); if(newNodeInfoUpdate) {
notifyNodeInfoUpdate(origin, nodeInfoUpdateFlags);
}
if(rifp.isFlood() && (topologyUpdated || newNodeInfoUpdate)) {
floodPacket(dgp.getSocketAddress(), rifp);
}
computeShortestPathIfUpdated();
break; break;
case KLALBRoutingProtocolPacket.JSON_DATA: case KLALBRoutingProtocolPacket.JSON_DATA:
JsonDataPacket data=(JsonDataPacket) kp; JsonDataPacket data=(JsonDataPacket) kp;
@@ -314,10 +392,11 @@ public class KLALBRoutingProtocol extends Thread{
sendLock.lock(); sendLock.lock();
try { try {
ds.send(dgpx); ds.send(dgpx);
}catch(BindException e) { }catch(BindException e) {
e.printStackTrace(); e.printStackTrace();
System.err.println("Cannot assign:"+dest); System.err.println("Cannot assign:"+dest);
}finally { throw e;
}finally {
sendLock.unlock(); sendLock.unlock();
} }
} }
@@ -548,8 +627,9 @@ public class KLALBRoutingProtocol extends Thread{
private RouterInfo getSelfRouterInfo() { private RouterInfo getSelfRouterInfo() {
List<IPv6NetworkLink>links=router.getLinkTabel(); List<IPv6NetworkLink>links=router.getLinkTabel();
Object[] nls=links.toArray(); Object[] nls=links.toArray();
RouterInfo ri=new RouterInfo(System.currentTimeMillis(),router.getLocator(),router.getASN()); long currentTime = System.currentTimeMillis();
ri.setDeviceName(router.getDeviceName()); long createTime = selfCreateTime.updateAndGet(previous -> Math.max(currentTime, previous + 1));
RouterInfo ri=new RouterInfo(createTime,router.getLocator(),router.getASN());
for(int i=0;i<nls.length;i++) { for(int i=0;i<nls.length;i++) {
IPv6NetworkLink nl=(IPv6NetworkLink) nls[i]; IPv6NetworkLink nl=(IPv6NetworkLink) nls[i];
if((!nl.isLoopBack())&&nl.isUp()) { if((!nl.isLoopBack())&&nl.isUp()) {
@@ -604,14 +684,8 @@ public class KLALBRoutingProtocol extends Thread{
/** /**
* 查询某地址广播的设备名称未知返回null * 查询某地址广播的设备名称未知返回null
*/ */
public String getDeviceName(IPv6Address address) { public String getDeviceName(IPv6Address address) {
RouterInfo ri=netmap.get(address); return null;
if(ri!=null) {
String dn=ri.getDeviceName();
if(dn!=null&&!dn.isEmpty())
return dn;
}
return null;
} }
public Map<IPv6Address, List<LinkDirection>> getPaths() { public Map<IPv6Address, List<LinkDirection>> getPaths() {
return paths; return paths;
@@ -1,125 +1,208 @@
package org.kne.cloud.network.srv6; package org.kne.cloud.network.srv6;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.Cleaner; import java.lang.ref.Cleaner;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.InetAddress;
import java.util.ArrayList; import java.net.SocketAddress;
import java.util.List; import java.util.ArrayList;
import java.util.UUID; import java.util.List;
import java.util.function.BiConsumer; import java.util.UUID;
import java.util.function.Consumer; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import org.kne.cloud.network.MultiProtocolSocketAddress; import java.util.function.Consumer;
import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
import org.kne.cloud.network.congestion.SendPacketSlidingWindow; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.opencl64.Releaser; import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
public class KLALBRoutingProtocolAPIClient { import org.kne.cloud.network.klalb.SendItem;
private KLALBRoutingProtocol routingProtocol; import org.kne.opencl64.Releaser;
private SendPacketSlidingWindow<UUID, JsonDataPacket> window = new SendPacketSlidingWindow<UUID, JsonDataPacket>(
new NOCongestionAlgorithm(3000000000L), 1024 * 1024); public class KLALBRoutingProtocolAPIClient {
private KLALBRoutingProtocol routingProtocol;
private static final Cleaner clr = Cleaner.create(); private SendPacketSlidingWindow<UUID, JsonDataPacket> window = new SendPacketSlidingWindow<UUID, JsonDataPacket>(
new NOCongestionAlgorithm(3000000000L), 1024 * 1024);
private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
KLALBRoutingProtocolJsonData dataobj = data.getDecodedData(); private static final Cleaner clr = Cleaner.create();
InetSocketAddress addrs = (InetSocketAddress) addr;
UUID ruid = dataobj.getUuid(); private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
JsonDataPacket relate = null; KLALBRoutingProtocolJsonData dataobj = data.getDecodedData();
//System.out.println(ruid + " " + window.getSendmap()); UUID ruid = dataobj.getUuid();
switch (dataobj.getType()) { switch (dataobj.getType()) {
case KLALBRoutingProtocolJsonData.NODE_INFO_FULL_RESP: case KLALBRoutingProtocolJsonData.NODE_PROFILE_RESP:
case KLALBRoutingProtocolJsonData.NODE_INFO_TINY_RESP: PendingRequest profileRequest = acknowledgeResponse(ruid,
KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ, addr);
if ((relate = window.ack(ruid)) != null) { if (profileRequest != null) {
List<?> connects = (List<?>) dataobj.getData(); ((Consumer<NodeProfile>) profileRequest.callback)
List<MultiProtocolSocketAddress> connectsm = new ArrayList<MultiProtocolSocketAddress>( .accept(new NodeProfile(dataobj.getDeviceName(), dataobj.getDeviceDescription()));
connects == null ? 0 : connects.size()); }
if (connects != null) { break;
for (Object open : connects) { case KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_RESP:
if (open instanceof MultiProtocolSocketAddress) { PendingRequest endpointsRequest = acknowledgeResponse(ruid,
connectsm.add((MultiProtocolSocketAddress) open); KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ, addr);
} else { if (endpointsRequest != null) {
connectsm.add(new MultiProtocolSocketAddress((String) open)); ((Consumer<ExternalEndpointsResult>) endpointsRequest.callback).accept(
new ExternalEndpointsResult(decodeExternalEndpoints(dataobj.getData()), dataobj.getStatus()));
} }
} break;
} case KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_RESP:
// 组装节点信息线路 + 设备名称 + 设备描述精简模式下线路与描述为 null PendingRequest routesRequest = acknowledgeResponse(ruid,
KLALBNodeInformation info = new KLALBNodeInformation(connectsm, dataobj.getDeviceName(), KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ, addr);
dataobj.getDeviceDescription()); if (routesRequest != null) {
((Consumer<KLALBNodeInformation>) relate.getUserCallback()).accept(info); ((Consumer<ExtraRoutesResult>) routesRequest.callback).accept(
} new ExtraRoutesResult(decodeExtraRoutes(dataobj.getData()), dataobj.getStatus()));
break; }
} break;
}
}; };
public KLALBRoutingProtocolAPIClient(KLALBRoutingProtocol routingProtocol) { private PendingRequest acknowledgeResponse(UUID uuid, String expectedRequestType, SocketAddress sourceAddress) {
this.routingProtocol = routingProtocol; SendItem<JsonDataPacket> pending = window.getSendmap().get(uuid);
routingProtocol.addReceiver(rec); if (pending == null) {
this.releaser = new KLALBRoutingProtocolAPIClientReleaser(this.routingProtocol, rec, window); return null;
clr.register(this, releaser); }
} KLALBRoutingProtocolJsonData request = pending.getPacket().getDecodedData();
if (request == null || !expectedRequestType.equals(request.getType())) {
/** return null;
* 精简查询仅获取对端设备名称开销最小适用于未查看节点详情的场景 }
*/ Object callback = pending.getPacket().getUserCallback();
public void requestNodeInfoTiny(SocketAddress addr, Consumer<KLALBNodeInformation> callback) if (!(callback instanceof PendingRequest)) {
throws IOException { return null;
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_INFO_TINY_REQ, addr, callback); }
} PendingRequest pendingRequest = (PendingRequest) callback;
if (!expectedRequestType.equals(pendingRequest.expectedRequestType)
/** || !sameDestination(pendingRequest.expectedDestination, sourceAddress)) {
* 完整查询获取对端开放线路 + 设备名称 + 设备描述查看节点信息时使用 return null;
*/ }
public void requestNodeInfoFull(SocketAddress addr, Consumer<KLALBNodeInformation> callback) return window.ack(uuid) == null ? null : pendingRequest;
throws IOException { }
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_INFO_FULL_REQ, addr, callback);
} private static boolean sameDestination(SocketAddress expected, SocketAddress actual) {
if (expected instanceof InetSocketAddress && actual instanceof InetSocketAddress) {
private void sendNodeInfoRequest(String type, SocketAddress addr, Consumer<KLALBNodeInformation> callback) InetSocketAddress expectedInet = (InetSocketAddress) expected;
throws IOException { InetSocketAddress actualInet = (InetSocketAddress) actual;
UUID suid = UUID.randomUUID(); if (expectedInet.getPort() != actualInet.getPort()) return false;
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(type, suid, null); InetAddress expectedAddress = expectedInet.getAddress();
JsonDataPacket packet = new JsonDataPacket(json); InetAddress actualAddress = actualInet.getAddress();
packet.setUserCallback(callback); if (expectedAddress != null && actualAddress != null) return expectedAddress.equals(actualAddress);
window.put(suid, packet); if (expectedAddress == null && actualAddress == null) {
routingProtocol.sendJsonPacketToAddress(packet, addr); return expectedInet.getHostString().equalsIgnoreCase(actualInet.getHostString());
}
} return false;
}
public KLALBRoutingProtocol getRoutingProtocol() { return expected != null && expected.equals(actual);
return routingProtocol; }
}
private List<MultiProtocolSocketAddress> decodeExternalEndpoints(Object data) {
private KLALBRoutingProtocolAPIClientReleaser releaser; if (!(data instanceof List<?>)) {
return new ArrayList<MultiProtocolSocketAddress>();
public void close() { }
releaser.run(); List<?> encoded = (List<?>) data;
} List<MultiProtocolSocketAddress> endpoints = new ArrayList<MultiProtocolSocketAddress>(encoded.size());
for (Object value : encoded) {
public boolean isClosed() { if (value instanceof MultiProtocolSocketAddress) {
return releaser.isReleased(); endpoints.add((MultiProtocolSocketAddress) value);
} } else if (value instanceof String) {
} try {
endpoints.add(new MultiProtocolSocketAddress((String) value));
class KLALBRoutingProtocolAPIClientReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>> { } catch (RuntimeException ignored) {
// Ignore malformed entries while preserving the rest of the response.
private KLALBRoutingProtocol routingProtocol; }
private SendPacketSlidingWindow<UUID, JsonDataPacket> window; }
}
public KLALBRoutingProtocolAPIClientReleaser(KLALBRoutingProtocol routingProtocol, return endpoints;
BiConsumer<SocketAddress, JsonDataPacket> resource, SendPacketSlidingWindow<UUID, JsonDataPacket> window) { }
super(resource);
this.routingProtocol = routingProtocol; private List<String> decodeExtraRoutes(Object data) {
this.window = window; if (!(data instanceof List<?>)) {
} return new ArrayList<String>();
}
@Override List<?> encoded = (List<?>) data;
protected void release(BiConsumer<SocketAddress, JsonDataPacket> resource) { List<String> routes = new ArrayList<String>(encoded.size());
window.close(); for (Object value : encoded) {
routingProtocol.removeReceiver(resource); if (value instanceof String) {
} routes.add((String) value);
} }
}
return routes;
}
public KLALBRoutingProtocolAPIClient(KLALBRoutingProtocol routingProtocol) {
this.routingProtocol = routingProtocol;
routingProtocol.addReceiver(rec);
this.releaser = new KLALBRoutingProtocolAPIClientReleaser(this.routingProtocol, rec, window);
clr.register(this, releaser);
}
public void requestNodeProfile(SocketAddress addr, Consumer<NodeProfile> callback) throws IOException {
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ, addr, callback);
}
public void requestExternalEndpoints(SocketAddress addr, Consumer<ExternalEndpointsResult> callback)
throws IOException {
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ, addr, callback);
}
public void requestExtraRoutes(SocketAddress addr, Consumer<ExtraRoutesResult> callback) throws IOException {
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ, addr, callback);
}
private void sendNodeInfoRequest(String type, SocketAddress addr, Object callback) throws IOException {
UUID suid = UUID.randomUUID();
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(type, suid, null);
JsonDataPacket packet = new JsonDataPacket(json);
packet.setUserCallback(new PendingRequest(addr, type, callback));
synchronized (this) {
if (closed.get() || releaser.isReleased()) throw new IOException("Node info client is closed");
window.put(suid, packet);
routingProtocol.sendJsonPacketToAddress(packet, addr);
}
}
public KLALBRoutingProtocol getRoutingProtocol() {
return routingProtocol;
}
private KLALBRoutingProtocolAPIClientReleaser releaser;
public synchronized void close() {
if (closed.compareAndSet(false, true)) releaser.run();
}
public boolean isClosed() {
return closed.get() || releaser.isReleased();
}
private static final class PendingRequest {
private final SocketAddress expectedDestination;
private final String expectedRequestType;
private final Object callback;
private PendingRequest(SocketAddress expectedDestination, String expectedRequestType, Object callback) {
this.expectedDestination = expectedDestination;
this.expectedRequestType = expectedRequestType;
this.callback = callback;
}
}
private final AtomicBoolean closed = new AtomicBoolean();
}
class KLALBRoutingProtocolAPIClientReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>> {
private KLALBRoutingProtocol routingProtocol;
private SendPacketSlidingWindow<UUID, JsonDataPacket> window;
public KLALBRoutingProtocolAPIClientReleaser(KLALBRoutingProtocol routingProtocol,
BiConsumer<SocketAddress, JsonDataPacket> resource, SendPacketSlidingWindow<UUID, JsonDataPacket> window) {
super(resource);
this.routingProtocol = routingProtocol;
this.window = window;
}
@Override
protected void release(BiConsumer<SocketAddress, JsonDataPacket> resource) {
window.close();
routingProtocol.removeReceiver(resource);
}
}
@@ -1,88 +1,98 @@
package org.kne.cloud.network.srv6; package org.kne.cloud.network.srv6;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.Cleaner; import java.lang.ref.Cleaner;
import java.net.InetSocketAddress; import java.net.SocketAddress;
import java.net.SocketAddress; import java.util.ArrayList;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.kne.cloud.network.klalb.KLALBController; import org.kne.cloud.network.klalb.KLALBController;
import org.kne.opencl64.Releaser; import org.kne.opencl64.Releaser;
public class KLALBRoutingProtocolAPIServer { public class KLALBRoutingProtocolAPIServer {
private KLALBController controller; private KLALBController controller;
private KLALBRoutingProtocol routingProtocol; private KLALBRoutingProtocol routingProtocol;
private static final Cleaner clr = Cleaner.create();
private static final Cleaner clr=Cleaner.create();
private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
private BiConsumer<SocketAddress, JsonDataPacket> rec=(addr,data)->{ try {
try { KLALBRoutingProtocolJsonData dataobj = data.getDecodedData();
KLALBRoutingProtocolJsonData dataobj= data.getDecodedData(); switch (dataobj.getType()) {
InetSocketAddress addrs=(InetSocketAddress) addr; case KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ:
switch(dataobj.getType()){ KLALBController.PublishedNodeInfo profilePublished = controller.getPublishedNodeInfo();
case KLALBRoutingProtocolJsonData.NODE_INFO_TINY_REQ: KLALBRoutingProtocolJsonData profile = new KLALBRoutingProtocolJsonData(
// 精简查询仅返回设备名称设备名称随路由信息广播公开不受 denyExternalEndpointQuery 限制 KLALBRoutingProtocolJsonData.NODE_PROFILE_RESP, dataobj.getUuid(), null);
KLALBRoutingProtocolJsonData tinyjson=new KLALBRoutingProtocolJsonData(KLALBRoutingProtocolJsonData.NODE_INFO_TINY_RESP,dataobj.getUuid(),null); profile.setDeviceName(profilePublished.getDeviceName());
tinyjson.setDeviceName(controller.getIpv6Router().getDeviceName()); profile.setDeviceDescription(profilePublished.getDeviceDescription());
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(tinyjson),addr); profile.setStatus(NodeInfoQueryStatus.OK);
break; routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(profile), addr);
case KLALBRoutingProtocolJsonData.NODE_INFO_FULL_REQ: break;
// 完整查询设备名称与描述总是正常响应denyExternalEndpointQuery 仅隐藏外部端点列表 case KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ:
boolean denyEndpoints=controller.getConfigItem()!=null&&controller.getConfigItem().isDenyExternalEndpointQuery(); boolean denyEndpoints = controller.getConfigItem() != null
KLALBRoutingProtocolJsonData json=new KLALBRoutingProtocolJsonData(KLALBRoutingProtocolJsonData.NODE_INFO_FULL_RESP,dataobj.getUuid(),denyEndpoints?null:controller.getExternalEndpoints()); && controller.getConfigItem().isDenyExternalEndpointQuery();
json.setDeviceName(controller.getIpv6Router().getDeviceName());// 附带本机设备名称 KLALBController.PublishedNodeInfo endpointsPublished = controller.getPublishedNodeInfo();
if(controller.getConfigItem()!=null) { KLALBRoutingProtocolJsonData endpoints = new KLALBRoutingProtocolJsonData(
json.setDeviceDescription(controller.getConfigItem().getDeviceDescription());// 附带本机设备描述 KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_RESP, dataobj.getUuid(),
} denyEndpoints ? new ArrayList<org.kne.cloud.network.MultiProtocolSocketAddress>()
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(json),addr); : new ArrayList<org.kne.cloud.network.MultiProtocolSocketAddress>(
break; endpointsPublished.getExternalEndpoints()));
} endpoints.setStatus(denyEndpoints ? NodeInfoQueryStatus.DENIED : NodeInfoQueryStatus.OK);
} catch (IOException e) { routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(endpoints), addr);
e.printStackTrace(); break;
} case KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ:
}; KLALBController.PublishedNodeInfo routesPublished = controller.getPublishedNodeInfo();
KLALBRoutingProtocolJsonData routes = new KLALBRoutingProtocolJsonData(
public KLALBRoutingProtocolAPIServer(KLALBRoutingProtocol routingProtocol,KLALBController controller) { KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_RESP, dataobj.getUuid(),
this.controller=controller; new ArrayList<String>(routesPublished.getExtraRoutes()));
this.routingProtocol=routingProtocol; routes.setStatus(NodeInfoQueryStatus.OK);
routingProtocol.addReceiver(rec); routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(routes), addr);
this.releaser=new KLALBRoutingProtocolAPIServerReleaser(this.routingProtocol,rec); break;
clr.register(this, releaser); }
} } catch (IOException e) {
e.printStackTrace();
public KLALBController getController() { }
return controller; };
}
public KLALBRoutingProtocolAPIServer(KLALBRoutingProtocol routingProtocol, KLALBController controller) {
public KLALBRoutingProtocol getRoutingProtocol() { this.controller = controller;
return routingProtocol; this.routingProtocol = routingProtocol;
} routingProtocol.addReceiver(rec);
this.releaser = new KLALBRoutingProtocolAPIServerReleaser(this.routingProtocol, rec);
clr.register(this, releaser);
private KLALBRoutingProtocolAPIServerReleaser releaser; }
public void close() { public KLALBController getController() {
releaser.run(); return controller;
} }
public KLALBRoutingProtocol getRoutingProtocol() {
public boolean isClosed() { return routingProtocol;
return releaser.isReleased(); }
}
private KLALBRoutingProtocolAPIServerReleaser releaser;
}
class KLALBRoutingProtocolAPIServerReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>>{ public void close() {
releaser.run();
private KLALBRoutingProtocol routingProtocol; }
public KLALBRoutingProtocolAPIServerReleaser(KLALBRoutingProtocol routingProtocol,BiConsumer<SocketAddress, JsonDataPacket> resource) { public boolean isClosed() {
super(resource); return releaser.isReleased();
this.routingProtocol=routingProtocol; }
} }
@Override class KLALBRoutingProtocolAPIServerReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>> {
protected void release(BiConsumer<SocketAddress, JsonDataPacket> resource) {
routingProtocol.removeReceiver(resource); private KLALBRoutingProtocol routingProtocol;
}
} public KLALBRoutingProtocolAPIServerReleaser(KLALBRoutingProtocol routingProtocol,
BiConsumer<SocketAddress, JsonDataPacket> resource) {
super(resource);
this.routingProtocol = routingProtocol;
}
@Override
protected void release(BiConsumer<SocketAddress, JsonDataPacket> resource) {
routingProtocol.removeReceiver(resource);
}
}
@@ -1,95 +1,89 @@
package org.kne.cloud.network.srv6; package org.kne.cloud.network.srv6;
import java.util.UUID; import java.util.Objects;
import java.util.UUID;
public class KLALBRoutingProtocolJsonData {
public static final String NODE_INFO_TINY_REQ="nodeinfotinyreq";// 精简查询仅设备名称 public class KLALBRoutingProtocolJsonData {
public static final String NODE_INFO_TINY_RESP="nodeinfotinyresp"; public static final String NODE_PROFILE_REQ = "nodeprofilereq";
public static final String NODE_INFO_FULL_REQ="nodeinfofullreq";// 完整查询开放线路+设备名称+设备描述 public static final String NODE_PROFILE_RESP = "nodeprofileresp";
public static final String NODE_INFO_FULL_RESP="nodeinfofullresp"; public static final String NODE_EXTERNAL_ENDPOINTS_REQ = "nodeexternalendpointsreq";
private String type; public static final String NODE_EXTERNAL_ENDPOINTS_RESP = "nodeexternalendpointsresp";
private UUID uuid; public static final String NODE_EXTRA_ROUTES_REQ = "nodeextraroutesreq";
private Object data; public static final String NODE_EXTRA_ROUTES_RESP = "nodeextraroutesresp";
private String deviceName;// 对端设备名称
private String deviceDescription;// 对端设备描述 private String type;
public String getDeviceName() { private UUID uuid;
return deviceName; private Object data;
} private String deviceName;
public void setDeviceName(String deviceName) { private String deviceDescription;
this.deviceName = deviceName; private NodeInfoQueryStatus status;
}
public String getDeviceDescription() { public KLALBRoutingProtocolJsonData(String type, UUID uuid, Object data) {
return deviceDescription; this.type = type;
} this.uuid = uuid;
public void setDeviceDescription(String deviceDescription) { this.data = data;
this.deviceDescription = deviceDescription; }
}
public String getType() { public String getType() {
return type; return type;
} }
public Object getData() {
return data; public UUID getUuid() {
} return uuid;
public UUID getUuid() { }
return uuid;
} public Object getData() {
@Override return data;
public int hashCode() { }
final int prime = 31;
int result = 1; public String getDeviceName() {
result = prime * result + ((data == null) ? 0 : data.hashCode()); return deviceName;
result = prime * result + ((type == null) ? 0 : type.hashCode()); }
result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
result = prime * result + ((deviceName == null) ? 0 : deviceName.hashCode()); public void setDeviceName(String deviceName) {
result = prime * result + ((deviceDescription == null) ? 0 : deviceDescription.hashCode()); this.deviceName = deviceName;
return result; }
}
@Override public String getDeviceDescription() {
public boolean equals(Object obj) { return deviceDescription;
if (this == obj) }
return true;
if (obj == null) public void setDeviceDescription(String deviceDescription) {
return false; this.deviceDescription = deviceDescription;
if (getClass() != obj.getClass()) }
return false;
KLALBRoutingProtocolJsonData other = (KLALBRoutingProtocolJsonData) obj; public NodeInfoQueryStatus getStatus() {
if (data == null) { return status;
if (other.data != null) }
return false;
} else if (!data.equals(other.data)) public void setStatus(NodeInfoQueryStatus status) {
return false; this.status = status;
if (type == null) { }
if (other.type != null)
return false; @Override
} else if (!type.equals(other.type)) public int hashCode() {
return false; return Objects.hash(type, uuid, data, deviceName, deviceDescription, status);
if (uuid == null) { }
if (other.uuid != null)
return false; @Override
} else if (!uuid.equals(other.uuid)) public boolean equals(Object obj) {
return false; if (this == obj) {
if (deviceName == null) { return true;
if (other.deviceName != null) }
return false; if (!(obj instanceof KLALBRoutingProtocolJsonData)) {
} else if (!deviceName.equals(other.deviceName)) return false;
return false; }
if (deviceDescription == null) { KLALBRoutingProtocolJsonData other = (KLALBRoutingProtocolJsonData) obj;
if (other.deviceDescription != null) return Objects.equals(type, other.type) && Objects.equals(uuid, other.uuid)
return false; && Objects.equals(data, other.data) && Objects.equals(deviceName, other.deviceName)
} else if (!deviceDescription.equals(other.deviceDescription)) && Objects.equals(deviceDescription, other.deviceDescription)
return false; && Objects.equals(status, other.status);
return true; }
}
public KLALBRoutingProtocolJsonData(String type, UUID uuid, Object data) { @Override
super(); public String toString() {
this.type = type; return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data
this.uuid = uuid; + ", deviceName=" + deviceName + ", deviceDescription=" + deviceDescription + ", status=" + status
this.data = data; + "]";
} }
@Override }
public String toString() {
return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data + ", deviceName="
+ deviceName + ", deviceDescription=" + deviceDescription + "]";
}
}
@@ -95,8 +95,12 @@ public abstract class KLALBRoutingProtocolPacket extends NetworkPacket {
return readKLALBPacketFromChannel(Channels.newChannel(in)); return readKLALBPacketFromChannel(Channels.newChannel(in));
} }
public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in) throws IOException { public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in) throws IOException {
while(true) { return readKLALBPacketFromChannel(in, -1);
}
public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in, long packetLength) throws IOException {
while(true) {
ByteBuffer bb=NetworkPacket.bufferAllocator.allocate(40); ByteBuffer bb=NetworkPacket.bufferAllocator.allocate(40);
bb.limit(1); bb.limit(1);
while(bb.hasRemaining()){ while(bb.hasRemaining()){
@@ -109,18 +113,20 @@ public abstract class KLALBRoutingProtocolPacket extends NetworkPacket {
KLALBRoutingProtocolPacket klp; KLALBRoutingProtocolPacket klp;
switch(type) { switch(type) {
case RINFO_REQ: case RINFO_REQ:
klp=new RouterInfoRequestPacket(bb); klp=new RouterInfoRequestPacket(bb);
klp.readFromChannel(in); if(packetLength < 0) klp.readFromChannel(in);
return klp; else klp.readFromChannel(in, packetLength);
case RINFO: return klp;
klp=new RouterInfoPacket(bb); case RINFO:
klp.readFromChannel(in); klp=new RouterInfoPacket(bb);
return klp; if(packetLength < 0) klp.readFromChannel(in);
case JSON_DATA: else klp.readFromChannel(in, packetLength);
klp=new JsonDataPacket(bb); return klp;
klp.readFromChannel(in); case JSON_DATA:
return klp; klp=new JsonDataPacket(bb);
klp.readFromChannel(in);
return klp;
} }
//throw new StreamCorruptedException("unknown package type:"+type); //throw new StreamCorruptedException("unknown package type:"+type);
System.err.println("ignore unknown KLALBRoutingProtocolPacket type:"+type); System.err.println("ignore unknown KLALBRoutingProtocolPacket type:"+type);
@@ -0,0 +1,6 @@
package org.kne.cloud.network.srv6;
public enum NodeInfoQueryStatus {
OK,
DENIED
}
@@ -0,0 +1,44 @@
package org.kne.cloud.network.srv6;
import java.util.Objects;
public final class NodeProfile {
private final String deviceName;
private final String deviceDescription;
public NodeProfile(String deviceName, String deviceDescription) {
this.deviceName = deviceName;
this.deviceDescription = deviceDescription;
}
public String getDeviceName() {
return deviceName;
}
public String getDeviceDescription() {
return deviceDescription;
}
@Override
public int hashCode() {
return Objects.hash(deviceName, deviceDescription);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof NodeProfile)) {
return false;
}
NodeProfile other = (NodeProfile) obj;
return Objects.equals(deviceName, other.deviceName)
&& Objects.equals(deviceDescription, other.deviceDescription);
}
@Override
public String toString() {
return "NodeProfile [deviceName=" + deviceName + ", deviceDescription=" + deviceDescription + "]";
}
}
+26 -25
View File
@@ -60,15 +60,15 @@ public class RouterInfo implements Serializable{
return Objects.equals(locator, other.locator) && Objects.equals(neighborAddresses, other.neighborAddresses); return Objects.equals(locator, other.locator) && Objects.equals(neighborAddresses, other.neighborAddresses);
} }
private List<NeighborInfo> neighborAddresses=new ArrayList<>(); private List<NeighborInfo> neighborAddresses=new ArrayList<>();
private String deviceName=""; // 设备名称广播时携带描述不广播 private long createTime;
private long createTime;
public long getCreateTime() { public long getCreateTime() {
return createTime; return createTime;
} }
private static final long INFO_UPDATETIME=10000000000L; private static final long INFO_UPDATETIME=10000000000L;
private static final long INFO_TIMEOUT=60000000000L; private static final long INFO_TIMEOUT=60000000000L;
private static final int MAX_NEIGHBOR_COUNT = 512;
private long putTime=System.nanoTime(); private long putTime=System.nanoTime();
public boolean checkUpdateTime() { public boolean checkUpdateTime() {
return System.nanoTime()-putTime>INFO_UPDATETIME; return System.nanoTime()-putTime>INFO_UPDATETIME;
@@ -91,31 +91,28 @@ public class RouterInfo implements Serializable{
return neighborAddresses; return neighborAddresses;
} }
public String getDeviceName() { public RouterInfo( long createTime,IPv6AddressGroup locator,long asn) {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public RouterInfo( long createTime,IPv6AddressGroup locator,long asn) {
this.createTime = createTime; this.createTime = createTime;
this.locator=locator; this.locator=locator;
this.asn=asn; this.asn=asn;
} }
public RouterInfo() { public RouterInfo() {
} }
public void writeToStream(DataOutputStream out) throws IOException { public void writeToStream(DataOutputStream out) throws IOException {
locator.writeToStream(out); int neighborCount = neighborAddresses == null ? -1 : neighborAddresses.size();
out.writeLong(asn); if(neighborCount<0 || neighborCount>MAX_NEIGHBOR_COUNT) {
out.writeLong(createTime); throw new IOException("Invalid neighbor count: " + neighborCount);
out.writeInt(neighborAddresses.size()); }
locator.writeToStream(out);
out.writeLong(asn);
out.writeLong(createTime);
out.writeInt(neighborCount);
for( NeighborInfo neighborInfo :neighborAddresses) { for( NeighborInfo neighborInfo :neighborAddresses) {
neighborInfo.writeToStream(out); 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 { protected void writeToChannel(WritableByteChannel dto) throws IOException {
@@ -127,16 +124,20 @@ public class RouterInfo implements Serializable{
public void readFromStream(DataInputStream in) throws IOException { public void readFromStream(DataInputStream in) throws IOException {
locator=new IPv6AddressGroup(in); locator=new IPv6AddressGroup(in);
asn=in.readLong(); asn=in.readLong();
createTime=in.readLong(); createTime=in.readLong();
int size=in.readInt(); int size=in.readInt();
neighborAddresses=new ArrayList<>(size); 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++) { for (int i = 0; i < size; i++) {
NeighborInfo nif=new NeighborInfo(); NeighborInfo nif=new NeighborInfo();
nif.readFromStream(in); nif.readFromStream(in);
neighborAddresses.add(nif); neighborAddresses.add(nif);
} }
deviceName=in.readUTF(); // Consume the legacy device-name slot; names are no longer broadcast here.
in.readUTF();
} }
} }
@@ -1,7 +1,8 @@
package org.kne.cloud.network.srv6; package org.kne.cloud.network.srv6;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.ByteArrayInputStream;
import java.io.Externalizable; import java.io.Externalizable;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInput; import java.io.ObjectInput;
@@ -22,7 +23,12 @@ import java.util.Set;
import org.kne.cloud.network.NetworkPacket; import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.ipv6.IPv6AddressGroup; 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) { public RouterInfoPacket(RouterInfo routerInfo, boolean flood,long asn) {
super(RINFO); super(RINFO);
@@ -34,7 +40,8 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri
super(bb); super(bb);
} }
private RouterInfo rinfo; private RouterInfo rinfo;
private transient int nodeInfoUpdateFlags;
public boolean isFlood() { public boolean isFlood() {
return (getHeader().get(1)&1)==1; return (getHeader().get(1)&1)==1;
@@ -44,9 +51,17 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri
return getHeader().getLong(2); return getHeader().getLong(2);
} }
public RouterInfo getRinfo() { public RouterInfo getRinfo() {
return rinfo; return rinfo;
} }
public int getNodeInfoUpdateFlags() {
return nodeInfoUpdateFlags;
}
public void setNodeInfoUpdateFlags(int flags) {
nodeInfoUpdateFlags = flags & NODE_INFO_UPDATE_FLAGS_MASK;
}
@Override @Override
protected long getHeaderSize() { protected long getHeaderSize() {
return super.getHeaderSize()+9; return super.getHeaderSize()+9;
@@ -65,15 +80,46 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri
@Override @Override
public void writeToChannel(WritableByteChannel dto) throws IOException { public void writeToChannel(WritableByteChannel dto) throws IOException {
super.writeToChannel(dto); super.writeToChannel(dto);
rinfo.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 @Override
public void readFromChannel(ReadableByteChannel din, long length) throws IOException { public void readFromChannel(ReadableByteChannel din, long length) throws IOException {
super.readFromChannel(din, length); super.readFromChannel(din, length);
rinfo=new RouterInfo(); rinfo=new RouterInfo();
rinfo.readFromChannel(din, length); 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;
}
} }
} }