feat(srv6)!: split node information queries

Replace the combined node-info protocol with independent profile,
external-endpoint, and extra-route requests. Aggregate consumers use
NodeInfoQueryCoordinator and validate response source addresses.

BREAKING CHANGE: nodeinfotinyreq/resp and nodeinfofullreq/resp are
removed. Peers must use the profile, external-endpoint, and extra-route
request pairs.
This commit is contained in:
2026-08-31 01:13:49 +08:00
parent b6acc6d509
commit 3dae7dd876
15 changed files with 878 additions and 486 deletions
+14 -29
View File
@@ -1,54 +1,39 @@
# KLALB Repository Guide
KLALB is a Java SRv6/load-balancing system. The executable entrypoint is `org.kne.cloud.network.klalb.KLALBMain`; runtime configuration is `klalb-config.json` in the repository root.
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 And Run
- This is a plain Eclipse/IntelliJ Java project: sources are `src/`, vendored dependencies are `lib/`, and output is `bin/`. There is no Maven or Gradle.
- `.classpath` targets `JavaSE-25`. Sources contain Chinese text, so manual compilation must use UTF-8:
- 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:
```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)
```
- Run from the repository root. Manual `javac` does not copy resources, so keep `src` on the runtime classpath:
- Run from the repository root. `src` must stay on the classpath because manual compilation does not copy resource bundles:
```powershell
& "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
```
- The native libraries and `klalb-config.json` are resolved from the current directory. Restart a running JVM after recompiling.
- TUN creation normally needs elevation. For non-admin UI/routing checks, set `"enableTUN": false`.
- Current full compilation emits 11 pre-existing varargs/deprecation warnings; exit code `0` is success.
- 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.
## Dashboard
- `dashboard/` is a Git submodule. Commit dashboard changes inside it, then update the parent repository's submodule pointer.
- Run frontend commands from `dashboard/` with pnpm:
```powershell
pnpm install --frozen-lockfile
pnpm lint
pnpm typecheck
pnpm build
pnpm dev
```
- `pnpm build` runs `tsc -b` then Vite and writes `dashboard/dist`, which the Java web server hosts. Vite development proxies `/api` to `http://127.0.0.1:4665`.
- Add shadcn components through `pnpm dlx shadcn@latest add <component>`; do not hand-create replacements for installed shadcn primitives.
- `dashboard/` is a Git submodule. Commit dashboard changes in that repository, then update the parent submodule pointer.
- 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.
- Vite proxies `/api` to `http://127.0.0.1:4665`; use `pnpm dev` only with the Java API running there.
## Verification
- There is no CI or automated test suite. `*Test*` classes are manual harnesses that require real network peers.
- For Java changes, compile and launch the app. For dashboard changes, run `pnpm typecheck` and `pnpm build`.
- 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`.
## Important Boundaries
- `KLALBConfigItem` is a polymorphic JSON array keyed by `Type`. Adding a type requires a subclass and cases in both default config serializer and deserializer; unknown types must remain preserved.
- `/api/config` is field-by-field parsing, not whole-object Gson mapping. Keep legacy key aliases in sync with new fields.
- Vendored Gson is `2.1`: HTTP responses that are `JsonElement` instances must be serialized with `JsonElement.toString()`, not reflective `gson.toJson(Object)`; configuration files must use the configured pretty-print Gson path rather than `JsonElement.toString()`.
- `KLALBConfigItem` is a polymorphic JSON array keyed by case-sensitive `Type`. New types need serializer and deserializer support; preserve unknown items' raw JSON.
- `/api/config` parses fields and legacy aliases explicitly. Web and Swing writes must use `KLALBProxySystem`'s revision-checked detached-candidate commit path, never mutate the canonical config directly.
- Vendored Gson is 2.1: serialize HTTP `JsonElement` values with `toString()`; serialize configuration through the configured pretty-print Gson path.
- UI strings use `UIEnv.getRsb()`; add keys to both `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`.
- Web and Swing configuration writes must use `KLALBProxySystem`'s revisioned detached-candidate commit/event path; do not mutate the canonical config object directly.
- `KLALBController.PublishedNodeInfo` is the thread-safe source for Tiny/Full node-info responses. Publish name, description, external endpoints, and Extra Routes through the controller method so snapshots and Tiny/Full update flags stay consistent.
- `RouterInfo` no longer carries a device name. Its wire format retains an empty legacy UTF slot and `RouterInfoPacket` has optional Tiny/Full invalidation flags. Treat codec changes as compatibility work: preserve old-reader behavior and review a whole-mesh rollout.
- Full node-info carries `extraRoutes` separately from the endpoint `data` list. Keep absent fields compatible with older peers.
- `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.
- `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.
@@ -53,10 +53,11 @@ import org.kne.cloud.network.ntp.NTPContext;
import org.kne.cloud.network.ntp.NTPv4Packet;
import org.kne.cloud.network.ntp.NTPv4Protocol;
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIServer;
import org.kne.cloud.network.srv6.SRv6Router;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIServer;
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.tcp.UDPPacket;
import org.kne.cloud.network.tcp.UDPProtocolRegister;
@@ -389,9 +390,11 @@ public class KLALBController {
try {
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(),
KLALBRoutingProtocol.DEFAULT_PORT);
apiClient.requestNodeInfoFull(iaddr, (v) -> {
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
for (MultiProtocolSocketAddress msa : v.getOpenLines()) {
apiClient.requestExternalEndpoints(iaddr, (v) -> {
if (v == null || v.getStatus() != NodeInfoQueryStatus.OK) return;
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
if (v.getExternalEndpoints() == null) return;
for (MultiProtocolSocketAddress msa : v.getExternalEndpoints()) {
// System.out.print(msa);
addRemoteLines(msa);
}
@@ -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));
}
}
}
@@ -22,13 +22,13 @@ import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.klalb.KLALBController;
import org.kne.cloud.network.srv6.KLALBNodeInformation;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.klalb.KLALBController;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
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 {
@@ -205,7 +205,7 @@ public class NetworkGraphPanel extends GraphPanel {
}
}
private void requestNodeInfoTiny(final IPv6Address address) {
private void requestNodeProfile(final IPv6Address address) {
final long requestTime = System.currentTimeMillis();
final long requestGeneration;
synchronized (nodeInfoLock) {
@@ -225,9 +225,9 @@ public class NetworkGraphPanel extends GraphPanel {
nodeInfoRequestGenerations.put(address, requestGeneration);
}
try {
nodeInfoClient.requestNodeInfoTiny(
nodeInfoClient.requestNodeProfile(
new InetSocketAddress(address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
(info) -> SwingUtilities.invokeLater(() -> handleNodeInfo(address, requestGeneration, info)));
(profile) -> SwingUtilities.invokeLater(() -> handleNodeProfile(address, requestGeneration, profile)));
} catch (IOException e) {
synchronized (nodeInfoLock) {
if (nodeInfoRequestGenerations.get(address) != null
@@ -239,12 +239,12 @@ public class NetworkGraphPanel extends GraphPanel {
}
}
private void handleNodeInfo(IPv6Address address, long requestGeneration, KLALBNodeInformation info) {
private void handleNodeProfile(IPv6Address address, long requestGeneration, NodeProfile profile) {
if (nodeInfoClosed)
return;
boolean currentRequest;
boolean nodeExists = getNodes().containsKey(address);
String deviceName = info == null ? "" : info.getDeviceName();
String deviceName = profile == null ? "" : profile.getDeviceName();
if (deviceName == null || deviceName.isEmpty())
deviceName = "";
synchronized (nodeInfoLock) {
@@ -295,7 +295,7 @@ public class NetworkGraphPanel extends GraphPanel {
node.updateLabel();
}
repaint();
requestNodeInfoTiny(address);
requestNodeProfile(address);
}
public void close() {
@@ -349,7 +349,7 @@ public class NetworkGraphPanel extends GraphPanel {
getNodes().put(inet6Address,new InetGraphNode(inet6Address,Color.BLACK,v2pos.x,v2pos.y,inet6Address.equals(controller.getIpv6Router().getLocator().getAddress())));
}
if(!inet6Address.equals(localAddress))
requestNodeInfoTiny(inet6Address);
requestNodeProfile(inet6Address);
}
Set<IPv6Address> kns=getNodes().keySet();
for (Iterator<IPv6Address> iterator = kns.iterator(); iterator.hasNext();) {
@@ -4,9 +4,9 @@ import java.awt.BorderLayout;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.List;
import javax.swing.Icon;
import javax.swing.ImageIcon;
@@ -22,10 +22,13 @@ import javax.swing.event.ListSelectionListener;
import org.kne.cloud.klalb.uitool.XDefaultListModel;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.klalb.KLALBController;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.klalb.KLALBController;
import org.kne.cloud.network.klalb.NodeInfoQueryCoordinator;
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.JList;
import javax.swing.JMenuItem;
@@ -37,8 +40,9 @@ import java.awt.event.ActionEvent;
import java.util.concurrent.atomic.AtomicLong;
public class NodeInformationPanel extends JPanel {
private IPv6Address address;
private KLALBRoutingProtocolAPIClient client;
private IPv6Address address;
private KLALBRoutingProtocolAPIClient client;
private NodeInfoQueryCoordinator queryCoordinator;
private KLALBController controller;
private Image image;
@@ -174,6 +178,7 @@ public class NodeInformationPanel extends JPanel {
KLALBRoutingProtocol routingProtocol=controller.getIpv6Router().getKlalbRouteProtol();
client=new KLALBRoutingProtocolAPIClient(routingProtocol);
queryCoordinator=new NodeInfoQueryCoordinator(client);
nodeInfoUpdateListener=(updatedAddress, flags) -> {
if(active && address.equals(updatedAddress)
&& (flags & org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED) != 0) {
@@ -186,27 +191,77 @@ public class NodeInformationPanel extends JPanel {
private void requestFullInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel) {
final long generation=fullInfoGeneration.incrementAndGet();
if (controller.getIpv6Router().getLocator().getAddress().equals(address)) {
KLALBController.PublishedNodeInfo published=controller.getPublishedNodeInfo();
applyNodeInfo(overviewArea, extraRoutesEmptyLabel, published.getDeviceName(),
published.getDeviceDescription(), published.getExternalEndpoints(), published.getExtraRoutes());
return;
}
try {
client.requestNodeInfoFull(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (info)->{
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;
listModel.clear();
if(info.getOpenLines()!=null) for (MultiProtocolSocketAddress item : info.getOpenLines()) listModel.addElement(item);
extraRoutesModel.clear();
List<String> routes=info.getExtraRoutes();
if(routes!=null) for(String route : routes) extraRoutesModel.addElement(route);
extraRoutesEmptyLabel.setText(UIEnv.getRsb().getString("noextraroutes"));
extraRoutesEmptyLabel.setVisible(extraRoutesModel.isEmpty());
String dd=info.getDeviceDescription();
overviewArea.setText(buildOverviewText(info.getDeviceName(), dd==null||dd.isEmpty()?null:dd));
if (error != null || info == null) {
return;
}
applyRemoteNodeInfo(overviewArea, extraRoutesEmptyLabel, info);
});
});
} catch (IOException e) {
} 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;
@@ -20,10 +20,11 @@ import org.kne.cloud.network.ipv6.IPv6NetworkLink;
import org.kne.cloud.network.ipv6.RouteItem;
import org.kne.cloud.network.klalb.*;
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.LinkDirection;
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.RouterInfo;
import org.kne.cloud.network.srv6.RouterInfoPacket;
@@ -54,7 +55,7 @@ public class KLALBWebServer {
this.gson = proxySystem.getGson();
this.nodeInfoUpdateListener = (address, flags) -> {
if ((flags & RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED) != 0) {
nodeInfoFullRevisions.merge(address, 1L, Long::sum);
nodeInfoRevisions.merge(address, 1L, Long::sum);
}
if ((flags & RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED) != 0) {
long lifecycleGeneration;
@@ -73,7 +74,6 @@ public class KLALBWebServer {
public synchronized void start() throws IOException {
if (running.get()) return;
nodeInfoFullRevisionEpoch = UUID.randomUUID().toString();
if (sseExecutor == null || sseExecutor.isShutdown()) {
sseExecutor = createSseExecutor();
}
@@ -503,14 +503,13 @@ public class KLALBWebServer {
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> nodeInfoFullRevisions = 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 volatile String nodeInfoFullRevisionEpoch = UUID.randomUUID().toString();
private static ScheduledExecutorService createSseExecutor() {
return Executors.newSingleThreadScheduledExecutor(r -> {
@@ -605,14 +604,14 @@ public class KLALBWebServer {
}
return "";
}
client.requestNodeInfoTiny(
client.requestNodeProfile(
new InetSocketAddress(addr.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
info -> {
profile -> {
synchronized (tinyNameStateLock) {
Long activeRequestGeneration = tinyNameRequestGenerations.get(addr);
if (!Long.valueOf(requestGenerationToken).equals(activeRequestGeneration)) return;
tinyDeviceNameCache.put(addr,
info != null && info.getDeviceName() != null ? info.getDeviceName() : "");
profile != null && profile.getDeviceName() != null ? profile.getDeviceName() : "");
tinyDeviceNameCacheTimes.put(addr, System.currentTimeMillis());
tinyNameRequestsInFlight.remove(addr);
tinyNameRequestTimes.remove(addr);
@@ -689,6 +688,7 @@ public class KLALBWebServer {
// 本机:描述直接取本地控制器配置
KLALBController.PublishedNodeInfo published = kc.getPublishedNodeInfo();
resp.addProperty("isSelf", true);
resp.addProperty("reachable", true);
resp.addProperty("deviceName", published.getDeviceName() != null ? published.getDeviceName() : "");
resp.addProperty("deviceDescription",
published.getDeviceDescription() != null ? published.getDeviceDescription() : "");
@@ -710,41 +710,36 @@ public class KLALBWebServer {
// 远端节点:经 SRv6 虚拟网络发送完整节点信息查询(异步回调,限时等待)
resp.addProperty("isSelf", false);
CompletableFuture<KLALBNodeInformation> future = new CompletableFuture<>();
try {
KLALBRoutingProtocolAPIClient client = getNodeInfoClient(kc);
if (client == null) {
sendError(exchange, 503, "Node info service stopped");
return;
}
client.requestNodeInfoFull(
new InetSocketAddress(target.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
info -> future.complete(info));
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()
: "";
NodeInfoQueryCoordinator.Result info = new NodeInfoQueryCoordinator(client)
.query(new InetSocketAddress(target.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT)).get();
NodeInfoQueryCoordinator.Section<NodeProfile> profile = info.getProfile();
NodeProfile nodeProfile = profile.getValue().orElse(null);
String dname = nodeProfile != null && nodeProfile.getDeviceName() != null
&& !nodeProfile.getDeviceName().isEmpty() ? nodeProfile.getDeviceName() : "";
resp.addProperty("deviceName", dname);
resp.addProperty("deviceDescription",
info != null && info.getDeviceDescription() != null ? info.getDeviceDescription() : "");
resp.addProperty("reachable", info != null);
nodeProfile != null && nodeProfile.getDeviceDescription() != null
? nodeProfile.getDeviceDescription() : "");
resp.addProperty("reachable", profile.isReceived()
|| info.getExternalEndpoints().isReceived() || info.getExtraRoutes().isReceived());
JsonArray lines = new JsonArray();
if (info != null && info.getOpenLines() != null) {
for (MultiProtocolSocketAddress mpsa : info.getOpenLines()) {
if (info.getExternalEndpoints().getStatus().filter(NodeInfoQueryStatus.OK::equals).isPresent()
&& info.getExternalEndpoints().getValue().isPresent()) {
for (MultiProtocolSocketAddress mpsa : info.getExternalEndpoints().getValue().get()) {
lines.add(new JsonPrimitive(mpsa.toString()));
}
}
resp.add("openLines", lines);
JsonArray extraRoutes = new JsonArray();
if (info != null && info.getExtraRoutes() != null) {
for (String route : info.getExtraRoutes()) {
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 : ""));
}
}
@@ -799,7 +794,7 @@ public class KLALBWebServer {
tinyNameRequestTimes.keySet().removeIf(address -> !activeAddresses.contains(address));
tinyNameRequestGenerations.keySet().removeIf(address -> !activeAddresses.contains(address));
}
nodeInfoFullRevisions.keySet().removeIf(address -> !activeAddresses.contains(address));
nodeInfoRevisions.keySet().removeIf(address -> !activeAddresses.contains(address));
if (addrs != null) {
for (IPv6Address addr : addrs.keySet()) {
@@ -811,10 +806,9 @@ public class KLALBWebServer {
String dname = addr.equals(selfAddr) ? kc.getIpv6Router().getDeviceName()
: getTinyDeviceName(kc, addr);
nodeObj.addProperty("deviceName", dname != null ? dname : "");
long fullRevision = addr.equals(selfAddr) ? published.getFullRevision()
: nodeInfoFullRevisions.getOrDefault(addr, 0L);
nodeObj.addProperty("nodeInfoFullRevision", fullRevision);
nodeObj.addProperty("nodeInfoFullRevisionEpoch", nodeInfoFullRevisionEpoch);
long nodeInfoRevision = addr.equals(selfAddr) ? published.getFullRevision()
: nodeInfoRevisions.getOrDefault(addr, 0L);
nodeObj.addProperty("nodeInfoRevision", nodeInfoRevision);
nodesArray.add(nodeObj);
}
}
@@ -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 + "]";
}
}
@@ -1,68 +0,0 @@
package org.kne.cloud.network.srv6;
import java.util.List;
import java.util.ArrayList;
import org.kne.cloud.network.MultiProtocolSocketAddress;
/**
* 节点信息(开放线路 + 设备名称 + 设备描述),由路由协议 JSON API 查询获得。
*/
public class KLALBNodeInformation {
private List<MultiProtocolSocketAddress> openLines;
private String deviceName;
private String deviceDescription;
private List<String> extraRoutes;
public KLALBNodeInformation(List<MultiProtocolSocketAddress> openLines, String deviceName,
String deviceDescription) {
this(openLines, deviceName, deviceDescription, new ArrayList<String>());
}
public KLALBNodeInformation(List<MultiProtocolSocketAddress> openLines, String deviceName,
String deviceDescription, List<String> extraRoutes) {
super();
this.openLines = openLines;
this.deviceName = deviceName;
this.deviceDescription = deviceDescription;
this.extraRoutes = extraRoutes == null ? new ArrayList<String>() : extraRoutes;
}
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;
}
public List<String> getExtraRoutes() {
return extraRoutes;
}
public void setExtraRoutes(List<String> extraRoutes) {
this.extraRoutes = extraRoutes == null ? new ArrayList<String>() : extraRoutes;
}
@Override
public String toString() {
return "KLALBNodeInformation [openLines=" + openLines + ", deviceName=" + deviceName
+ ", deviceDescription=" + deviceDescription + ", extraRoutes=" + extraRoutes + "]";
}
}
@@ -1,129 +1,208 @@
package org.kne.cloud.network.srv6;
import java.io.IOException;
import java.lang.ref.Cleaner;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
import org.kne.opencl64.Releaser;
public class KLALBRoutingProtocolAPIClient {
private KLALBRoutingProtocol routingProtocol;
private SendPacketSlidingWindow<UUID, JsonDataPacket> window = new SendPacketSlidingWindow<UUID, JsonDataPacket>(
new NOCongestionAlgorithm(3000000000L), 1024 * 1024);
private static final Cleaner clr = Cleaner.create();
private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
KLALBRoutingProtocolJsonData dataobj = data.getDecodedData();
InetSocketAddress addrs = (InetSocketAddress) addr;
UUID ruid = dataobj.getUuid();
JsonDataPacket relate = null;
//System.out.println(ruid + " " + window.getSendmap());
switch (dataobj.getType()) {
case KLALBRoutingProtocolJsonData.NODE_INFO_FULL_RESP:
case KLALBRoutingProtocolJsonData.NODE_INFO_TINY_RESP:
if ((relate = window.ack(ruid)) != null) {
List<?> connects = (List<?>) dataobj.getData();
List<MultiProtocolSocketAddress> connectsm = new ArrayList<MultiProtocolSocketAddress>(
connects == null ? 0 : connects.size());
if (connects != null) {
for (Object open : connects) {
if (open instanceof MultiProtocolSocketAddress) {
connectsm.add((MultiProtocolSocketAddress) open);
} else {
connectsm.add(new MultiProtocolSocketAddress((String) open));
}
}
package org.kne.cloud.network.srv6;
import java.io.IOException;
import java.lang.ref.Cleaner;
import java.net.InetSocketAddress;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
import org.kne.cloud.network.klalb.SendItem;
import org.kne.opencl64.Releaser;
public class KLALBRoutingProtocolAPIClient {
private KLALBRoutingProtocol routingProtocol;
private SendPacketSlidingWindow<UUID, JsonDataPacket> window = new SendPacketSlidingWindow<UUID, JsonDataPacket>(
new NOCongestionAlgorithm(3000000000L), 1024 * 1024);
private static final Cleaner clr = Cleaner.create();
private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
KLALBRoutingProtocolJsonData dataobj = data.getDecodedData();
UUID ruid = dataobj.getUuid();
switch (dataobj.getType()) {
case KLALBRoutingProtocolJsonData.NODE_PROFILE_RESP:
PendingRequest profileRequest = acknowledgeResponse(ruid,
KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ, addr);
if (profileRequest != null) {
((Consumer<NodeProfile>) profileRequest.callback)
.accept(new NodeProfile(dataobj.getDeviceName(), dataobj.getDeviceDescription()));
}
break;
case KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_RESP:
PendingRequest endpointsRequest = acknowledgeResponse(ruid,
KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ, addr);
if (endpointsRequest != null) {
((Consumer<ExternalEndpointsResult>) endpointsRequest.callback).accept(
new ExternalEndpointsResult(decodeExternalEndpoints(dataobj.getData()), dataobj.getStatus()));
}
break;
case KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_RESP:
PendingRequest routesRequest = acknowledgeResponse(ruid,
KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ, addr);
if (routesRequest != null) {
((Consumer<ExtraRoutesResult>) routesRequest.callback).accept(
new ExtraRoutesResult(decodeExtraRoutes(dataobj.getData()), dataobj.getStatus()));
}
break;
}
};
private PendingRequest acknowledgeResponse(UUID uuid, String expectedRequestType, SocketAddress sourceAddress) {
SendItem<JsonDataPacket> pending = window.getSendmap().get(uuid);
if (pending == null) {
return null;
}
KLALBRoutingProtocolJsonData request = pending.getPacket().getDecodedData();
if (request == null || !expectedRequestType.equals(request.getType())) {
return null;
}
Object callback = pending.getPacket().getUserCallback();
if (!(callback instanceof PendingRequest)) {
return null;
}
PendingRequest pendingRequest = (PendingRequest) callback;
if (!expectedRequestType.equals(pendingRequest.expectedRequestType)
|| !sameDestination(pendingRequest.expectedDestination, sourceAddress)) {
return null;
}
return window.ack(uuid) == null ? null : pendingRequest;
}
private static boolean sameDestination(SocketAddress expected, SocketAddress actual) {
if (expected instanceof InetSocketAddress && actual instanceof InetSocketAddress) {
InetSocketAddress expectedInet = (InetSocketAddress) expected;
InetSocketAddress actualInet = (InetSocketAddress) actual;
if (expectedInet.getPort() != actualInet.getPort()) return false;
InetAddress expectedAddress = expectedInet.getAddress();
InetAddress actualAddress = actualInet.getAddress();
if (expectedAddress != null && actualAddress != null) return expectedAddress.equals(actualAddress);
if (expectedAddress == null && actualAddress == null) {
return expectedInet.getHostString().equalsIgnoreCase(actualInet.getHostString());
}
return false;
}
return expected != null && expected.equals(actual);
}
private List<MultiProtocolSocketAddress> decodeExternalEndpoints(Object data) {
if (!(data instanceof List<?>)) {
return new ArrayList<MultiProtocolSocketAddress>();
}
List<?> encoded = (List<?>) data;
List<MultiProtocolSocketAddress> endpoints = new ArrayList<MultiProtocolSocketAddress>(encoded.size());
for (Object value : encoded) {
if (value instanceof MultiProtocolSocketAddress) {
endpoints.add((MultiProtocolSocketAddress) value);
} else if (value instanceof String) {
try {
endpoints.add(new MultiProtocolSocketAddress((String) value));
} catch (RuntimeException ignored) {
// Ignore malformed entries while preserving the rest of the response.
}
List<String> extraRoutes = dataobj.getExtraRoutes();
if(extraRoutes == null) {
extraRoutes = new ArrayList<String>();
}
// 组装节点信息(线路 + 设备名称 + 设备描述,精简模式下线路与描述为 null)
KLALBNodeInformation info = new KLALBNodeInformation(connectsm, dataobj.getDeviceName(),
dataobj.getDeviceDescription(), extraRoutes);
((Consumer<KLALBNodeInformation>) relate.getUserCallback()).accept(info);
}
break;
}
};
public KLALBRoutingProtocolAPIClient(KLALBRoutingProtocol routingProtocol) {
this.routingProtocol = routingProtocol;
routingProtocol.addReceiver(rec);
this.releaser = new KLALBRoutingProtocolAPIClientReleaser(this.routingProtocol, rec, window);
clr.register(this, releaser);
}
/**
* 精简查询:仅获取对端设备名称(开销最小,适用于未查看节点详情的场景)。
*/
public void requestNodeInfoTiny(SocketAddress addr, Consumer<KLALBNodeInformation> callback)
throws IOException {
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_INFO_TINY_REQ, addr, callback);
}
/**
* 完整查询:获取对端开放线路 + 设备名称 + 设备描述(查看节点信息时使用)。
*/
public void requestNodeInfoFull(SocketAddress addr, Consumer<KLALBNodeInformation> callback)
throws IOException {
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_INFO_FULL_REQ, addr, callback);
}
private void sendNodeInfoRequest(String type, SocketAddress addr, Consumer<KLALBNodeInformation> callback)
throws IOException {
UUID suid = UUID.randomUUID();
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(type, suid, null);
JsonDataPacket packet = new JsonDataPacket(json);
packet.setUserCallback(callback);
window.put(suid, packet);
routingProtocol.sendJsonPacketToAddress(packet, addr);
}
public KLALBRoutingProtocol getRoutingProtocol() {
return routingProtocol;
}
private KLALBRoutingProtocolAPIClientReleaser releaser;
public void close() {
releaser.run();
}
public boolean isClosed() {
return releaser.isReleased();
}
}
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);
}
}
}
return endpoints;
}
private List<String> decodeExtraRoutes(Object data) {
if (!(data instanceof List<?>)) {
return new ArrayList<String>();
}
List<?> encoded = (List<?>) data;
List<String> routes = new ArrayList<String>(encoded.size());
for (Object value : encoded) {
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,91 +1,98 @@
package org.kne.cloud.network.srv6;
import java.io.IOException;
import java.lang.ref.Cleaner;
import java.net.InetSocketAddress;
package org.kne.cloud.network.srv6;
import java.io.IOException;
import java.lang.ref.Cleaner;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import org.kne.cloud.network.klalb.KLALBController;
import org.kne.opencl64.Releaser;
public class KLALBRoutingProtocolAPIServer {
private KLALBController controller;
private KLALBRoutingProtocol routingProtocol;
private static final Cleaner clr=Cleaner.create();
private BiConsumer<SocketAddress, JsonDataPacket> rec=(addr,data)->{
try {
KLALBRoutingProtocolJsonData dataobj= data.getDecodedData();
InetSocketAddress addrs=(InetSocketAddress) addr;
switch(dataobj.getType()){
case KLALBRoutingProtocolJsonData.NODE_INFO_TINY_REQ:
// 精简查询:仅返回设备名称(设备名称随路由信息广播公开,不受 denyExternalEndpointQuery 限制)
KLALBController.PublishedNodeInfo published = controller.getPublishedNodeInfo();
KLALBRoutingProtocolJsonData tinyjson=new KLALBRoutingProtocolJsonData(KLALBRoutingProtocolJsonData.NODE_INFO_TINY_RESP,dataobj.getUuid(),null);
tinyjson.setDeviceName(published.getDeviceName());
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(tinyjson),addr);
break;
case KLALBRoutingProtocolJsonData.NODE_INFO_FULL_REQ:
// 完整查询:设备名称与描述总是正常响应;denyExternalEndpointQuery 仅隐藏外部端点列表
boolean denyEndpoints=controller.getConfigItem()!=null&&controller.getConfigItem().isDenyExternalEndpointQuery();
KLALBController.PublishedNodeInfo fullPublished = controller.getPublishedNodeInfo();
KLALBRoutingProtocolJsonData json=new KLALBRoutingProtocolJsonData(KLALBRoutingProtocolJsonData.NODE_INFO_FULL_RESP,dataobj.getUuid(),denyEndpoints?null:new ArrayList<org.kne.cloud.network.MultiProtocolSocketAddress>(fullPublished.getExternalEndpoints()));
json.setDeviceName(fullPublished.getDeviceName());// 附带本机设备名称
json.setDeviceDescription(fullPublished.getDeviceDescription());// 附带本机设备描述
json.setExtraRoutes(new ArrayList<String>(fullPublished.getExtraRoutes()));
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(json),addr);
break;
}
} catch (IOException e) {
e.printStackTrace();
}
};
public KLALBRoutingProtocolAPIServer(KLALBRoutingProtocol routingProtocol,KLALBController controller) {
this.controller=controller;
this.routingProtocol=routingProtocol;
routingProtocol.addReceiver(rec);
this.releaser=new KLALBRoutingProtocolAPIServerReleaser(this.routingProtocol,rec);
clr.register(this, releaser);
}
public KLALBController getController() {
return controller;
}
public KLALBRoutingProtocol getRoutingProtocol() {
return routingProtocol;
}
private KLALBRoutingProtocolAPIServerReleaser releaser;
public void close() {
releaser.run();
}
public boolean isClosed() {
return releaser.isReleased();
}
}
class KLALBRoutingProtocolAPIServerReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>>{
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);
}
import org.kne.cloud.network.klalb.KLALBController;
import org.kne.opencl64.Releaser;
public class KLALBRoutingProtocolAPIServer {
private KLALBController controller;
private KLALBRoutingProtocol routingProtocol;
private static final Cleaner clr = Cleaner.create();
private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
try {
KLALBRoutingProtocolJsonData dataobj = data.getDecodedData();
switch (dataobj.getType()) {
case KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ:
KLALBController.PublishedNodeInfo profilePublished = controller.getPublishedNodeInfo();
KLALBRoutingProtocolJsonData profile = new KLALBRoutingProtocolJsonData(
KLALBRoutingProtocolJsonData.NODE_PROFILE_RESP, dataobj.getUuid(), null);
profile.setDeviceName(profilePublished.getDeviceName());
profile.setDeviceDescription(profilePublished.getDeviceDescription());
profile.setStatus(NodeInfoQueryStatus.OK);
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(profile), addr);
break;
case KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ:
boolean denyEndpoints = controller.getConfigItem() != null
&& controller.getConfigItem().isDenyExternalEndpointQuery();
KLALBController.PublishedNodeInfo endpointsPublished = controller.getPublishedNodeInfo();
KLALBRoutingProtocolJsonData endpoints = new KLALBRoutingProtocolJsonData(
KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_RESP, dataobj.getUuid(),
denyEndpoints ? new ArrayList<org.kne.cloud.network.MultiProtocolSocketAddress>()
: new ArrayList<org.kne.cloud.network.MultiProtocolSocketAddress>(
endpointsPublished.getExternalEndpoints()));
endpoints.setStatus(denyEndpoints ? NodeInfoQueryStatus.DENIED : NodeInfoQueryStatus.OK);
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(endpoints), addr);
break;
case KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ:
KLALBController.PublishedNodeInfo routesPublished = controller.getPublishedNodeInfo();
KLALBRoutingProtocolJsonData routes = new KLALBRoutingProtocolJsonData(
KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_RESP, dataobj.getUuid(),
new ArrayList<String>(routesPublished.getExtraRoutes()));
routes.setStatus(NodeInfoQueryStatus.OK);
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(routes), addr);
break;
}
} catch (IOException e) {
e.printStackTrace();
}
};
public KLALBRoutingProtocolAPIServer(KLALBRoutingProtocol routingProtocol, KLALBController controller) {
this.controller = controller;
this.routingProtocol = routingProtocol;
routingProtocol.addReceiver(rec);
this.releaser = new KLALBRoutingProtocolAPIServerReleaser(this.routingProtocol, rec);
clr.register(this, releaser);
}
public KLALBController getController() {
return controller;
}
public KLALBRoutingProtocol getRoutingProtocol() {
return routingProtocol;
}
private KLALBRoutingProtocolAPIServerReleaser releaser;
public void close() {
releaser.run();
}
public boolean isClosed() {
return releaser.isReleased();
}
}
class KLALBRoutingProtocolAPIServerReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>> {
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,109 +1,89 @@
package org.kne.cloud.network.srv6;
package org.kne.cloud.network.srv6;
import java.util.Objects;
import java.util.UUID;
import java.util.List;
public class KLALBRoutingProtocolJsonData {
public static final String NODE_INFO_TINY_REQ="nodeinfotinyreq";// 精简查询:仅设备名称
public static final String NODE_INFO_TINY_RESP="nodeinfotinyresp";
public static final String NODE_INFO_FULL_REQ="nodeinfofullreq";// 完整查询:开放线路+设备名称+设备描述
public static final String NODE_INFO_FULL_RESP="nodeinfofullresp";
private String type;
private UUID uuid;
private Object data;
private String deviceName;// 对端设备名称
private String deviceDescription;// 对端设备描述
private List<String> extraRoutes;
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getDeviceDescription() {
return deviceDescription;
}
public class KLALBRoutingProtocolJsonData {
public static final String NODE_PROFILE_REQ = "nodeprofilereq";
public static final String NODE_PROFILE_RESP = "nodeprofileresp";
public static final String NODE_EXTERNAL_ENDPOINTS_REQ = "nodeexternalendpointsreq";
public static final String NODE_EXTERNAL_ENDPOINTS_RESP = "nodeexternalendpointsresp";
public static final String NODE_EXTRA_ROUTES_REQ = "nodeextraroutesreq";
public static final String NODE_EXTRA_ROUTES_RESP = "nodeextraroutesresp";
private String type;
private UUID uuid;
private Object data;
private String deviceName;
private String deviceDescription;
private NodeInfoQueryStatus status;
public KLALBRoutingProtocolJsonData(String type, UUID uuid, Object data) {
this.type = type;
this.uuid = uuid;
this.data = data;
}
public String getType() {
return type;
}
public UUID getUuid() {
return uuid;
}
public Object getData() {
return data;
}
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;
}
public List<String> getExtraRoutes() {
return extraRoutes;
public NodeInfoQueryStatus getStatus() {
return status;
}
public void setExtraRoutes(List<String> extraRoutes) {
this.extraRoutes = extraRoutes;
public void setStatus(NodeInfoQueryStatus status) {
this.status = status;
}
public String getType() {
return type;
}
public Object getData() {
return data;
}
public UUID getUuid() {
return uuid;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((data == null) ? 0 : data.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
result = prime * result + ((deviceName == null) ? 0 : deviceName.hashCode());
result = prime * result + ((deviceDescription == null) ? 0 : deviceDescription.hashCode());
result = prime * result + ((extraRoutes == null) ? 0 : extraRoutes.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
KLALBRoutingProtocolJsonData other = (KLALBRoutingProtocolJsonData) obj;
if (data == null) {
if (other.data != null)
return false;
} else if (!data.equals(other.data))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
if (uuid == null) {
if (other.uuid != null)
return false;
} else if (!uuid.equals(other.uuid))
return false;
if (deviceName == null) {
if (other.deviceName != null)
return false;
} else if (!deviceName.equals(other.deviceName))
return false;
if (deviceDescription == null) {
if (other.deviceDescription != null)
return false;
} else if (!deviceDescription.equals(other.deviceDescription))
@Override
public int hashCode() {
return Objects.hash(type, uuid, data, deviceName, deviceDescription, status);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof KLALBRoutingProtocolJsonData)) {
return false;
if (extraRoutes == null) {
if (other.extraRoutes != null)
return false;
} else if (!extraRoutes.equals(other.extraRoutes))
return false;
return true;
}
public KLALBRoutingProtocolJsonData(String type, UUID uuid, Object data) {
super();
this.type = type;
this.uuid = uuid;
this.data = data;
}
@Override
public String toString() {
return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data + ", deviceName="
+ deviceName + ", deviceDescription=" + deviceDescription + ", extraRoutes=" + extraRoutes + "]";
}
}
}
KLALBRoutingProtocolJsonData other = (KLALBRoutingProtocolJsonData) obj;
return Objects.equals(type, other.type) && Objects.equals(uuid, other.uuid)
&& Objects.equals(data, other.data) && Objects.equals(deviceName, other.deviceName)
&& Objects.equals(deviceDescription, other.deviceDescription)
&& Objects.equals(status, other.status);
}
@Override
public String toString() {
return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data
+ ", deviceName=" + deviceName + ", deviceDescription=" + deviceDescription + ", status=" + status
+ "]";
}
}
@@ -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 + "]";
}
}