forked from KNEMC/KLALB
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7896ee6638
|
||
|
|
ed94423855
|
||
|
|
c3172a04b1
|
||
|
|
cc803f47a7 | ||
|
|
3bb95988fc | ||
|
|
e87e3bb953 | ||
|
|
41db17f1ec |
@@ -12,3 +12,7 @@ dashboard/.pnpm-store/
|
|||||||
dashboard/.env.local
|
dashboard/.env.local
|
||||||
dashboard/.env.*.local
|
dashboard/.env.*.local
|
||||||
|
|
||||||
|
/klalb-config.json
|
||||||
|
/klalbconfig-old.json
|
||||||
|
|
||||||
|
/.slim/
|
||||||
@@ -39,7 +39,7 @@ No test suite, no CI. Classes named `*Test*` (`nathole/`, `ntp/`) are manual `ma
|
|||||||
|
|
||||||
- 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`, ...).
|
- Entrypoint `org.kne.cloud.network.klalb.KLALBMain`: load config → build `KLALBProxySystem` → open Swing GUI (`KLALBStateGUI3`) unless `"nogui": true` → start `KLALBWebServer` (if `"webUI": true` or web server enabled, default port `4665`) → interactive console (`help`, `links-state`, `route`, `kperf`, ...).
|
||||||
- `org.kne.cloud.network.klalb.web.KLALBWebServer` — built-in HTTP/SSE server (JDK `HttpServer`):
|
- `org.kne.cloud.network.klalb.web.KLALBWebServer` — built-in HTTP/SSE server (JDK `HttpServer`):
|
||||||
- API endpoints: `/api/status`, `/api/events` (SSE stream, 200ms intervals), `/api/links`, `/api/links/action`, `/api/links/reconnect`, `/api/routes`, `/api/nodes` (topology graph), `/api/interfaces`, `/api/config`.
|
- 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`.
|
||||||
- Static file hosting / SPA fallback: serves `dashboard/dist/` assets directly.
|
- Static file hosting / SPA fallback: serves `dashboard/dist/` assets directly.
|
||||||
- `org.kne.cloud.network` — generic socket framework: `VirtualSocket*` hierarchy, `SocketBridge` port-forwarding proxies, `ProtocolDetector` (multi-protocol mux on one port), `MultiProtocolSocketAddress` = URI-style addresses (`tcp://`, `udp://`, `kltp://`, `ntp://`) dispatched through the `SocketType` registry.
|
- `org.kne.cloud.network` — generic socket framework: `VirtualSocket*` hierarchy, `SocketBridge` port-forwarding proxies, `ProtocolDetector` (multi-protocol mux on one port), `MultiProtocolSocketAddress` = URI-style addresses (`tcp://`, `udp://`, `kltp://`, `ntp://`) dispatched through the `SocketType` registry.
|
||||||
- `...network.klalb` — app core: `KLALBController` (the virtual SRv6 network), `KLALBRemoteLink` (WAN lines), `*Packet` wire-format classes, virtual socket implementations.
|
- `...network.klalb` — app core: `KLALBController` (the virtual SRv6 network), `KLALBRemoteLink` (WAN lines), `*Packet` wire-format classes, virtual socket implementations.
|
||||||
@@ -59,7 +59,7 @@ No test suite, no CI. Classes named `*Test*` (`nathole/`, `ntp/`) are manual `ma
|
|||||||
Located in `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.
|
- **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`).
|
- **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`, `#/topology`, `#/settings`, etc.) for seamless SPA hosting under Java `KLALBWebServer`.
|
- **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).
|
- **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`.
|
- **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**:
|
- **Commands**:
|
||||||
@@ -69,7 +69,9 @@ Located in `dashboard/`:
|
|||||||
- **Pages & data flow**:
|
- **Pages & data flow**:
|
||||||
- Overview / Connections read the SSE stream (`use-klalb-sse.ts`, 200ms pushes of status + links).
|
- 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.
|
- 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.
|
- 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/`.
|
- 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).
|
- React hooks lint rule forbids `setState` synchronously inside effects — initialize form state via component `key` remount + lazy `useState(() => ...)` initializers (see `SettingsForm` pattern).
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule dashboard updated: f38d640ac4...89474b9e91
@@ -1,113 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"language": "ZH_CN",
|
|
||||||
"nogui": false,
|
|
||||||
"VirtualAddress": "2486:1:0:0:0:0:0:8889",
|
|
||||||
"VirtualASN": 2142606939373348329,
|
|
||||||
"DNS": [
|
|
||||||
"2486:1:0:0:0:0:0:8888"
|
|
||||||
],
|
|
||||||
"TCPListen": "tcp://0.0.0.0:4565",
|
|
||||||
"UDPListen": "udp://0.0.0.0:4572",
|
|
||||||
"VirtualSocketName": "kltp",
|
|
||||||
"externalEndpoints": [
|
|
||||||
"tcp://kne03.yoyo250.fun:4565",
|
|
||||||
"tcp://kne04.yoyo250.fun:4565",
|
|
||||||
"tcp://07f4acdef99b.ofalias.net:4565",
|
|
||||||
"tcp://kne01.yoyo250.fun:4565",
|
|
||||||
"tcp://kne02.yoyo250.fun:4565"
|
|
||||||
],
|
|
||||||
"autoConnections": [
|
|
||||||
"tcp://07f4acdef99b.ofalias.net:4565",
|
|
||||||
"tcp://kne01.yoyo250.fun:4565",
|
|
||||||
"tcp://kne02.yoyo250.fun:4565"
|
|
||||||
],
|
|
||||||
"ntpServers": [
|
|
||||||
"ntp://ntp1.aliyun.com",
|
|
||||||
"ntp://ntp2.aliyun.com",
|
|
||||||
"ntp://ntp3.aliyun.com",
|
|
||||||
"ntp://ntp4.aliyun.com",
|
|
||||||
"ntp://ntp5.aliyun.com",
|
|
||||||
"ntp://ntp6.aliyun.com",
|
|
||||||
"ntp://ntp7.aliyun.com",
|
|
||||||
"ntp://ntp1.tencent.com",
|
|
||||||
"ntp://ntp2.tencent.com",
|
|
||||||
"ntp://ntp4.tencent.com",
|
|
||||||
"ntp://ntp5.tencent.com",
|
|
||||||
"ntp://time.google.com",
|
|
||||||
"ntp://time.apple.com",
|
|
||||||
"ntp://pool.ntp.org",
|
|
||||||
"ntp://ntp.ntsc.ac.cn",
|
|
||||||
"ntp://us.ntp.org.cn"
|
|
||||||
],
|
|
||||||
"ExtraRoutes": [],
|
|
||||||
"denyExternalEndpointQuery": false,
|
|
||||||
"denyExternalEndpointBroadcast": false,
|
|
||||||
"congestionAlgorithm": "BBR",
|
|
||||||
"burstLimit": 2.0,
|
|
||||||
"delayUpperBound": 1.2,
|
|
||||||
"delayLowerBound": 1.1,
|
|
||||||
"nagleDelayTime": 1000000,
|
|
||||||
"linkNagleDelayTime": 0,
|
|
||||||
"linkConnectionsCount": 1,
|
|
||||||
"enableTUN": false,
|
|
||||||
"TUNName": "KLALB_SRv6",
|
|
||||||
"performanceStrategy": "multiscatter",
|
|
||||||
"DeviceName": "Device Name",
|
|
||||||
"DeviceDescription": "The Description of Device",
|
|
||||||
"webUI": true,
|
|
||||||
"webListen": "http://0.0.0.0:4665",
|
|
||||||
"NetworkInterfaceExcepts": [],
|
|
||||||
"Type": "KLALBController"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Listen": "kltp://[::0]:5201",
|
|
||||||
"Bridge": {
|
|
||||||
"DEFAULT": "SocketBridge"
|
|
||||||
},
|
|
||||||
"Connect": {
|
|
||||||
"DEFAULT": "tcp://127.0.0.1:5201"
|
|
||||||
},
|
|
||||||
"Type": "SocketBridge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Listen": "tcp://[::1]:5202",
|
|
||||||
"Bridge": {
|
|
||||||
"DEFAULT": "SocketBridge"
|
|
||||||
},
|
|
||||||
"Connect": {
|
|
||||||
"DEFAULT": "kltp://[2486:5acd:e339:4837:a6d9:3aed:4de2:30fd]:5201"
|
|
||||||
},
|
|
||||||
"Type": "SocketBridge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Listen": "tcp://127.0.0.1:35000",
|
|
||||||
"Bridge": {
|
|
||||||
"DEFAULT": "SocketBridge"
|
|
||||||
},
|
|
||||||
"Connect": {
|
|
||||||
"DEFAULT": "kltp://[2486:1::8888]:23333"
|
|
||||||
},
|
|
||||||
"Type": "SocketBridge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Listen": "kltp://[::0]:25565",
|
|
||||||
"Bridge": {
|
|
||||||
"DEFAULT": "SocketBridge"
|
|
||||||
},
|
|
||||||
"Connect": {
|
|
||||||
"DEFAULT": "tcp://127.0.0.1:25566"
|
|
||||||
},
|
|
||||||
"Type": "SocketBridge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Listen": "tcp://127.0.0.1:35565",
|
|
||||||
"Bridge": {
|
|
||||||
"DEFAULT": "SocketBridge"
|
|
||||||
},
|
|
||||||
"Connect": {
|
|
||||||
"DEFAULT": "kltp://[2486:1::8888]:25565"
|
|
||||||
},
|
|
||||||
"Type": "SocketBridge"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -116,3 +116,4 @@ webapisettings=Web API settings
|
|||||||
enablewebapi=Enable Web API
|
enablewebapi=Enable Web API
|
||||||
weblistenaddr=Web API listen address
|
weblistenaddr=Web API listen address
|
||||||
invaildweblistenaddr=Invalid Web API listen address
|
invaildweblistenaddr=Invalid Web API listen address
|
||||||
|
enabletun=Enable TUN adapter
|
||||||
|
|||||||
@@ -116,3 +116,4 @@ webapisettings=Web API 设置
|
|||||||
enablewebapi=启用 Web API
|
enablewebapi=启用 Web API
|
||||||
weblistenaddr=Web API 监听地址:端口
|
weblistenaddr=Web API 监听地址:端口
|
||||||
invaildweblistenaddr=无效的 Web API 监听地址:端口
|
invaildweblistenaddr=无效的 Web API 监听地址:端口
|
||||||
|
enabletun=启用TUN虚拟网卡
|
||||||
@@ -75,6 +75,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
private JTextField addressField; // 地址显示框
|
private JTextField addressField; // 地址显示框
|
||||||
private JTextField addressFieldSet; // 地址设置框
|
private JTextField addressFieldSet; // 地址设置框
|
||||||
private JTextField asnFieldSet; // ASN设置框
|
private JTextField asnFieldSet; // ASN设置框
|
||||||
|
private JCheckBox enableTUN; // 虚拟网卡启用禁用
|
||||||
private JTextField tunDeviceName; // 虚拟网卡名称设置框
|
private JTextField tunDeviceName; // 虚拟网卡名称设置框
|
||||||
private JTextField webListenSet; // Web API 监听地址设置框
|
private JTextField webListenSet; // Web API 监听地址设置框
|
||||||
private NetworkGraphPanel graph; // 网络图面板
|
private NetworkGraphPanel graph; // 网络图面板
|
||||||
@@ -822,6 +823,12 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
asnFieldSet = asn.getTextField();
|
asnFieldSet = asn.getTextField();
|
||||||
settings.getView().add(asn);
|
settings.getView().add(asn);
|
||||||
|
|
||||||
|
//虚拟网卡
|
||||||
|
CheckBoxSettingItem enableTUNs = new CheckBoxSettingItem(UIEnv.getRsb().getString("enabletun"),
|
||||||
|
CONST.itemwidth, CONST.settingheight);
|
||||||
|
enableTUN=enableTUNs.getCheckBox();
|
||||||
|
settings.getView().add(enableTUNs);
|
||||||
|
|
||||||
TextSettingItem tunName = new TextSettingItem(UIEnv.getRsb().getString("tunname"),
|
TextSettingItem tunName = new TextSettingItem(UIEnv.getRsb().getString("tunname"),
|
||||||
CONST.itemwidth, CONST.settingheight);
|
CONST.itemwidth, CONST.settingheight);
|
||||||
tunDeviceName = tunName.getTextField();
|
tunDeviceName = tunName.getTextField();
|
||||||
@@ -1151,6 +1158,8 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kck.setEnableTUN( enableTUN.isSelected());
|
||||||
|
|
||||||
String tunNameText=tunDeviceName.getText().trim();
|
String tunNameText=tunDeviceName.getText().trim();
|
||||||
if(tunNameText.equals("") || tunNameText.equalsIgnoreCase("null")) {
|
if(tunNameText.equals("") || tunNameText.equalsIgnoreCase("null")) {
|
||||||
kck.setTUNName(null);
|
kck.setTUNName(null);
|
||||||
@@ -1571,9 +1580,14 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
Long vasn = kck.getVirtualASN();
|
Long vasn = kck.getVirtualASN();
|
||||||
asnFieldSet.setText(vasn != null ? vasn.toString() : "");
|
asnFieldSet.setText(vasn != null ? vasn.toString() : "");
|
||||||
|
|
||||||
|
enableTUN.setSelected(kck.isEnableTUN());
|
||||||
|
|
||||||
|
|
||||||
String tunname=kck.getTUNName();
|
String tunname=kck.getTUNName();
|
||||||
tunDeviceName.setText(tunname!=null?tunname:"");
|
tunDeviceName.setText(tunname!=null?tunname:"");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 加载Web API设置
|
// 加载Web API设置
|
||||||
webApiEnabled.setSelected(kck.isWebUI());
|
webApiEnabled.setSelected(kck.isWebUI());
|
||||||
webListenSet.setText(kck.getWebListen() != null ? kck.getWebListen().toString()
|
webListenSet.setText(kck.getWebListen() != null ? kck.getWebListen().toString()
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ public class NetworkGraphPanel extends GraphPanel {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private double nsPerPixel=1000L;
|
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) {
|
||||||
super(nodeA, nodeB);
|
super(nodeA, nodeB);
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ 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.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.SRv6Router;
|
import org.kne.cloud.network.srv6.SRv6Router;
|
||||||
@@ -66,9 +68,9 @@ public class KLALBWebServer {
|
|||||||
server.createContext("/api/links", this::handleLinks);
|
server.createContext("/api/links", this::handleLinks);
|
||||||
server.createContext("/api/links/action", this::handleLinkAction);
|
server.createContext("/api/links/action", this::handleLinkAction);
|
||||||
server.createContext("/api/links/reconnect", this::handleReconnectAll);
|
server.createContext("/api/links/reconnect", this::handleReconnectAll);
|
||||||
server.createContext("/api/routes", this::handleRoutes);
|
server.createContext("/api/routing-table", this::handleRoutingTable);
|
||||||
server.createContext("/api/nodes", this::handleNodes);
|
server.createContext("/api/nodes", this::handleNodes);
|
||||||
server.createContext("/api/interfaces", this::handleInterfaces);
|
server.createContext("/api/node-info", this::handleNodeInfo);
|
||||||
server.createContext("/api/config", this::handleConfig);
|
server.createContext("/api/config", this::handleConfig);
|
||||||
|
|
||||||
// Static Files / SPA Fallback Handler
|
// Static Files / SPA Fallback Handler
|
||||||
@@ -426,7 +428,7 @@ public class KLALBWebServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRoutes(HttpExchange exchange) throws IOException {
|
private void handleRoutingTable(HttpExchange exchange) throws IOException {
|
||||||
if (handleCorsPreflight(exchange)) return;
|
if (handleCorsPreflight(exchange)) return;
|
||||||
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
sendError(exchange, 405, "Method not allowed");
|
sendError(exchange, 405, "Method not allowed");
|
||||||
@@ -453,6 +455,119 @@ public class KLALBWebServer {
|
|||||||
sendJsonResponse(exchange, 200, routesArray);
|
sendJsonResponse(exchange, 200, routesArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private KLALBRoutingProtocolAPIClient nodeInfoClient;
|
||||||
|
|
||||||
|
private synchronized KLALBRoutingProtocolAPIClient getNodeInfoClient(KLALBController kc) {
|
||||||
|
if (nodeInfoClient == null) {
|
||||||
|
nodeInfoClient = new KLALBRoutingProtocolAPIClient(kc.getIpv6Router().getKlalbRouteProtol());
|
||||||
|
}
|
||||||
|
return nodeInfoClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleNodeInfo(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
sendError(exchange, 405, "Method not allowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> params = parseQuery(exchange.getRequestURI().getRawQuery());
|
||||||
|
String addressStr = params.get("address");
|
||||||
|
if (addressStr == null || addressStr.trim().isEmpty()) {
|
||||||
|
sendError(exchange, 400, "address parameter is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KLALBController kc = proxySystem.getKlalbController();
|
||||||
|
if (kc == null || kc.getIpv6Router() == null || kc.getIpv6Router().getKlalbRouteProtol() == null) {
|
||||||
|
sendError(exchange, 503, "Controller not ready");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPv6Address target;
|
||||||
|
try {
|
||||||
|
target = new IPv6Address((Inet6Address) InetAddress.getByName(addressStr.trim()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendError(exchange, 400, "Invalid IPv6 address: " + addressStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SRv6Router router = kc.getIpv6Router();
|
||||||
|
boolean isSelf = router.getLocator().getAddress().equals(target);
|
||||||
|
|
||||||
|
JsonObject resp = new JsonObject();
|
||||||
|
resp.addProperty("address", target.toString());
|
||||||
|
|
||||||
|
if (isSelf) {
|
||||||
|
// 本机:描述直接取本地控制器配置
|
||||||
|
KLALBControllerConfigItem cfg = proxySystem.getControllerConfig();
|
||||||
|
resp.addProperty("isSelf", true);
|
||||||
|
String dname = router.getDeviceName() != null ? router.getDeviceName()
|
||||||
|
: (cfg != null && cfg.getDeviceName() != null ? cfg.getDeviceName() : "");
|
||||||
|
resp.addProperty("deviceName", dname);
|
||||||
|
resp.addProperty("deviceDescription",
|
||||||
|
cfg != null && cfg.getDeviceDescription() != null ? cfg.getDeviceDescription() : "");
|
||||||
|
JsonArray lines = new JsonArray();
|
||||||
|
for (MultiProtocolSocketAddress mpsa : kc.getExternalEndpoints()) {
|
||||||
|
lines.add(new JsonPrimitive(mpsa.toString()));
|
||||||
|
}
|
||||||
|
resp.add("openLines", lines);
|
||||||
|
sendJsonResponse(exchange, 200, resp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 远端节点:经 SRv6 虚拟网络发送完整节点信息查询(异步回调,限时等待)
|
||||||
|
resp.addProperty("isSelf", false);
|
||||||
|
CompletableFuture<KLALBNodeInformation> future = new CompletableFuture<>();
|
||||||
|
try {
|
||||||
|
getNodeInfoClient(kc).requestNodeInfoFull(
|
||||||
|
new InetSocketAddress(target.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
|
||||||
|
info -> future.complete(info));
|
||||||
|
|
||||||
|
KLALBNodeInformation info;
|
||||||
|
try {
|
||||||
|
info = future.get(3, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException te) {
|
||||||
|
info = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String dname = info != null && info.getDeviceName() != null && !info.getDeviceName().isEmpty()
|
||||||
|
? info.getDeviceName()
|
||||||
|
: router.getKlalbRouteProtol().getDeviceName(target);
|
||||||
|
resp.addProperty("deviceName", dname != null ? dname : "");
|
||||||
|
resp.addProperty("deviceDescription",
|
||||||
|
info != null && info.getDeviceDescription() != null ? info.getDeviceDescription() : "");
|
||||||
|
resp.addProperty("reachable", info != null);
|
||||||
|
JsonArray lines = new JsonArray();
|
||||||
|
if (info != null && info.getOpenLines() != null) {
|
||||||
|
for (MultiProtocolSocketAddress mpsa : info.getOpenLines()) {
|
||||||
|
lines.add(new JsonPrimitive(mpsa.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.add("openLines", lines);
|
||||||
|
} catch (Exception e) {
|
||||||
|
resp.addProperty("deviceName", "");
|
||||||
|
resp.addProperty("deviceDescription", "");
|
||||||
|
resp.addProperty("reachable", false);
|
||||||
|
resp.add("openLines", new JsonArray());
|
||||||
|
}
|
||||||
|
sendJsonResponse(exchange, 200, resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> parseQuery(String rawQuery) {
|
||||||
|
Map<String, String> params = new HashMap<>();
|
||||||
|
if (rawQuery == null || rawQuery.isEmpty()) return params;
|
||||||
|
for (String pair : rawQuery.split("&")) {
|
||||||
|
int idx = pair.indexOf('=');
|
||||||
|
if (idx <= 0) continue;
|
||||||
|
try {
|
||||||
|
params.put(URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8),
|
||||||
|
URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleNodes(HttpExchange exchange) throws IOException {
|
private void handleNodes(HttpExchange exchange) throws IOException {
|
||||||
if (handleCorsPreflight(exchange)) return;
|
if (handleCorsPreflight(exchange)) return;
|
||||||
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
@@ -517,33 +632,6 @@ public class KLALBWebServer {
|
|||||||
sendJsonResponse(exchange, 200, result);
|
sendJsonResponse(exchange, 200, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleInterfaces(HttpExchange exchange) throws IOException {
|
|
||||||
if (handleCorsPreflight(exchange)) return;
|
|
||||||
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
|
||||||
sendError(exchange, 405, "Method not allowed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
KLALBController kc = proxySystem.getKlalbController();
|
|
||||||
JsonArray ifacesArray = new JsonArray();
|
|
||||||
if (kc != null && kc.getNetworkInterfaceManager() != null) {
|
|
||||||
List<NetworkInterface> ifaces = kc.getNetworkInterfaceManager().getAllAvaliableNetworkInterface();
|
|
||||||
for (NetworkInterface nif : ifaces) {
|
|
||||||
JsonObject obj = new JsonObject();
|
|
||||||
obj.addProperty("name", nif.getName());
|
|
||||||
obj.addProperty("displayName", nif.getDisplayName());
|
|
||||||
JsonArray ips = new JsonArray();
|
|
||||||
List<InetAddress> addrs = kc.getNetworkInterfaceManager().getNetworkInterfaceAddress(nif);
|
|
||||||
for (InetAddress a : addrs) {
|
|
||||||
ips.add(new JsonPrimitive(a.getHostAddress()));
|
|
||||||
}
|
|
||||||
obj.add("addresses", ips);
|
|
||||||
ifacesArray.add(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sendJsonResponse(exchange, 200, ifacesArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleConfig(HttpExchange exchange) throws IOException {
|
private void handleConfig(HttpExchange exchange) throws IOException {
|
||||||
if (handleCorsPreflight(exchange)) return;
|
if (handleCorsPreflight(exchange)) return;
|
||||||
String method = exchange.getRequestMethod();
|
String method = exchange.getRequestMethod();
|
||||||
|
|||||||
Reference in New Issue
Block a user