diff --git a/AGENTS.md b/AGENTS.md index c323de6..56cdb69 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 `; 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. diff --git a/dashboard b/dashboard index 0b705f9..8c13af6 160000 --- a/dashboard +++ b/dashboard @@ -1 +1 @@ -Subproject commit 0b705f9ba0008540099e307bbb1e3e6944d7aa71 +Subproject commit 8c13af680a88f38abc033144e1c273b9ab3e2117 diff --git a/src/org/kne/cloud/network/klalb/KLALBController.java b/src/org/kne/cloud/network/klalb/KLALBController.java index e9b3e1d..cd1460a 100644 --- a/src/org/kne/cloud/network/klalb/KLALBController.java +++ b/src/org/kne/cloud/network/klalb/KLALBController.java @@ -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); } diff --git a/src/org/kne/cloud/network/klalb/NodeInfoQueryCoordinator.java b/src/org/kne/cloud/network/klalb/NodeInfoQueryCoordinator.java new file mode 100644 index 0000000..e9d4ed2 --- /dev/null +++ b/src/org/kne/cloud/network/klalb/NodeInfoQueryCoordinator.java @@ -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 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 { + private final boolean received; + private final Optional value; + private final Optional 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 getValue() { + return value; + } + + public Optional getStatus() { + return status; + } + + private static Section missing() { + return new Section(false, null, null); + } + + private static Section received(T value, NodeInfoQueryStatus status) { + return new Section(true, value, status); + } + } + + public static final class Result { + private final Section profile; + private final Section> externalEndpoints; + private final Section> extraRoutes; + + private Result(Section profile, + Section> externalEndpoints, + Section> extraRoutes) { + this.profile = profile; + this.externalEndpoints = externalEndpoints; + this.extraRoutes = extraRoutes; + } + + public Section getProfile() { + return profile; + } + + public Section> getExternalEndpoints() { + return externalEndpoints; + } + + public Section> getExtraRoutes() { + return extraRoutes; + } + } + + private final class Pending { + private final CompletableFuture result = new CompletableFuture<>(); + private final AtomicBoolean finished = new AtomicBoolean(); + private int remaining = 3; + private Section profile = Section.missing(); + private Section> externalEndpoints = Section.missing(); + private Section> 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 endpoints = value.getExternalEndpoints(); + externalEndpoints = Section.received(endpoints == null + ? Collections.emptyList() + : Collections.unmodifiableList(new ArrayList(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 routes = value.getExtraRoutes(); + extraRoutes = Section.received(routes == null + ? Collections.emptyList() + : Collections.unmodifiableList(new ArrayList(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)); + } + } +} diff --git a/src/org/kne/cloud/network/klalb/ui/NetworkGraphPanel.java b/src/org/kne/cloud/network/klalb/ui/NetworkGraphPanel.java index eb832cb..6b0de51 100644 --- a/src/org/kne/cloud/network/klalb/ui/NetworkGraphPanel.java +++ b/src/org/kne/cloud/network/klalb/ui/NetworkGraphPanel.java @@ -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 kns=getNodes().keySet(); for (Iterator iterator = kns.iterator(); iterator.hasNext();) { diff --git a/src/org/kne/cloud/network/klalb/ui/NodeInformationPanel.java b/src/org/kne/cloud/network/klalb/ui/NodeInformationPanel.java index 079315d..8ffa01a 100644 --- a/src/org/kne/cloud/network/klalb/ui/NodeInformationPanel.java +++ b/src/org/kne/cloud/network/klalb/ui/NodeInformationPanel.java @@ -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 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 endpoints, + List routes) { + applyProfile(overviewArea, deviceName, deviceDescription); + applyEndpoints(endpoints == null ? Collections.emptyList() : endpoints); + applyRoutes(extraRoutesEmptyLabel, routes == null ? Collections.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 endpoints=info.getExternalEndpoints().getStatus() + .filter(NodeInfoQueryStatus.OK::equals).isPresent() + ? info.getExternalEndpoints().getValue().orElse(Collections.emptyList()) + : Collections.emptyList(); + applyEndpoints(endpoints); + } + + if (info.getExtraRoutes().isReceived()) { + List routes=info.getExtraRoutes().getStatus().filter(NodeInfoQueryStatus.OK::equals).isPresent() + ? info.getExtraRoutes().getValue().orElse(Collections.emptyList()) + : Collections.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 endpoints) { + listModel.clear(); + if (endpoints != null) for (MultiProtocolSocketAddress item : endpoints) listModel.addElement(item); + } + + private void applyRoutes(javax.swing.JLabel extraRoutesEmptyLabel, List 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; diff --git a/src/org/kne/cloud/network/klalb/web/KLALBWebServer.java b/src/org/kne/cloud/network/klalb/web/KLALBWebServer.java index 430f57d..63a411f 100644 --- a/src/org/kne/cloud/network/klalb/web/KLALBWebServer.java +++ b/src/org/kne/cloud/network/klalb/web/KLALBWebServer.java @@ -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 tinyNameRequestsInFlight = ConcurrentHashMap.newKeySet(); private final ConcurrentMap tinyNameRequestTimes = new ConcurrentHashMap<>(); private final ConcurrentMap tinyNameRequestGenerations = new ConcurrentHashMap<>(); - private final ConcurrentMap nodeInfoFullRevisions = new ConcurrentHashMap<>(); + private final ConcurrentMap 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 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 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 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); } } diff --git a/src/org/kne/cloud/network/srv6/ExternalEndpointsResult.java b/src/org/kne/cloud/network/srv6/ExternalEndpointsResult.java new file mode 100644 index 0000000..6d7205d --- /dev/null +++ b/src/org/kne/cloud/network/srv6/ExternalEndpointsResult.java @@ -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 externalEndpoints; + private final NodeInfoQueryStatus status; + + public ExternalEndpointsResult(List externalEndpoints, NodeInfoQueryStatus status) { + this.externalEndpoints = Collections.unmodifiableList(new ArrayList( + externalEndpoints == null ? Collections.emptyList() : externalEndpoints)); + this.status = status; + } + + public List 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 + "]"; + } +} diff --git a/src/org/kne/cloud/network/srv6/ExtraRoutesResult.java b/src/org/kne/cloud/network/srv6/ExtraRoutesResult.java new file mode 100644 index 0000000..50ba854 --- /dev/null +++ b/src/org/kne/cloud/network/srv6/ExtraRoutesResult.java @@ -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 extraRoutes; + private final NodeInfoQueryStatus status; + + public ExtraRoutesResult(List extraRoutes, NodeInfoQueryStatus status) { + this.extraRoutes = Collections.unmodifiableList(new ArrayList( + extraRoutes == null ? Collections.emptyList() : extraRoutes)); + this.status = status; + } + + public List 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 + "]"; + } +} diff --git a/src/org/kne/cloud/network/srv6/KLALBNodeInformation.java b/src/org/kne/cloud/network/srv6/KLALBNodeInformation.java deleted file mode 100644 index 6a94ba6..0000000 --- a/src/org/kne/cloud/network/srv6/KLALBNodeInformation.java +++ /dev/null @@ -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 openLines; - private String deviceName; - private String deviceDescription; - private List extraRoutes; - - public KLALBNodeInformation(List openLines, String deviceName, - String deviceDescription) { - this(openLines, deviceName, deviceDescription, new ArrayList()); - } - - public KLALBNodeInformation(List openLines, String deviceName, - String deviceDescription, List extraRoutes) { - super(); - this.openLines = openLines; - this.deviceName = deviceName; - this.deviceDescription = deviceDescription; - this.extraRoutes = extraRoutes == null ? new ArrayList() : extraRoutes; - } - - public List getOpenLines() { - return openLines; - } - - public void setOpenLines(List 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 getExtraRoutes() { - return extraRoutes; - } - - public void setExtraRoutes(List extraRoutes) { - this.extraRoutes = extraRoutes == null ? new ArrayList() : extraRoutes; - } - - @Override - public String toString() { - return "KLALBNodeInformation [openLines=" + openLines + ", deviceName=" + deviceName - + ", deviceDescription=" + deviceDescription + ", extraRoutes=" + extraRoutes + "]"; - } -} diff --git a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIClient.java b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIClient.java index f0915da..07671df 100644 --- a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIClient.java +++ b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIClient.java @@ -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 window = new SendPacketSlidingWindow( - new NOCongestionAlgorithm(3000000000L), 1024 * 1024); - - private static final Cleaner clr = Cleaner.create(); - - private BiConsumer 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 connectsm = new ArrayList( - 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 window = new SendPacketSlidingWindow( + new NOCongestionAlgorithm(3000000000L), 1024 * 1024); + + private static final Cleaner clr = Cleaner.create(); + + private BiConsumer 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) 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) 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) routesRequest.callback).accept( + new ExtraRoutesResult(decodeExtraRoutes(dataobj.getData()), dataobj.getStatus())); + } + break; + } + }; + + private PendingRequest acknowledgeResponse(UUID uuid, String expectedRequestType, SocketAddress sourceAddress) { + SendItem 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 decodeExternalEndpoints(Object data) { + if (!(data instanceof List)) { + return new ArrayList(); + } + List encoded = (List) data; + List endpoints = new ArrayList(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 extraRoutes = dataobj.getExtraRoutes(); - if(extraRoutes == null) { - extraRoutes = new ArrayList(); - } - // 组装节点信息(线路 + 设备名称 + 设备描述,精简模式下线路与描述为 null) - KLALBNodeInformation info = new KLALBNodeInformation(connectsm, dataobj.getDeviceName(), - dataobj.getDeviceDescription(), extraRoutes); - ((Consumer) relate.getUserCallback()).accept(info); - } - break; - } - - }; - - 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 callback) - throws IOException { - sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_INFO_TINY_REQ, addr, callback); - } - - /** - * 完整查询:获取对端开放线路 + 设备名称 + 设备描述(查看节点信息时使用)。 - */ - public void requestNodeInfoFull(SocketAddress addr, Consumer callback) - throws IOException { - sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_INFO_FULL_REQ, addr, callback); - } - - private void sendNodeInfoRequest(String type, SocketAddress addr, Consumer 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> { - - private KLALBRoutingProtocol routingProtocol; - private SendPacketSlidingWindow window; - - public KLALBRoutingProtocolAPIClientReleaser(KLALBRoutingProtocol routingProtocol, - BiConsumer resource, SendPacketSlidingWindow window) { - super(resource); - this.routingProtocol = routingProtocol; - this.window = window; - } - - @Override - protected void release(BiConsumer resource) { - window.close(); - routingProtocol.removeReceiver(resource); - } + } + } + return endpoints; + } + + private List decodeExtraRoutes(Object data) { + if (!(data instanceof List)) { + return new ArrayList(); + } + List encoded = (List) data; + List routes = new ArrayList(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 callback) throws IOException { + sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ, addr, callback); + } + + public void requestExternalEndpoints(SocketAddress addr, Consumer callback) + throws IOException { + sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ, addr, callback); + } + + public void requestExtraRoutes(SocketAddress addr, Consumer 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> { + + private KLALBRoutingProtocol routingProtocol; + private SendPacketSlidingWindow window; + + public KLALBRoutingProtocolAPIClientReleaser(KLALBRoutingProtocol routingProtocol, + BiConsumer resource, SendPacketSlidingWindow window) { + super(resource); + this.routingProtocol = routingProtocol; + this.window = window; + } + + @Override + protected void release(BiConsumer resource) { + window.close(); + routingProtocol.removeReceiver(resource); + } } diff --git a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIServer.java b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIServer.java index d6e81f0..01a3cc5 100644 --- a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIServer.java +++ b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIServer.java @@ -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 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(fullPublished.getExternalEndpoints())); - json.setDeviceName(fullPublished.getDeviceName());// 附带本机设备名称 - json.setDeviceDescription(fullPublished.getDeviceDescription());// 附带本机设备描述 - json.setExtraRoutes(new ArrayList(fullPublished.getExtraRoutes())); - routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(json),addr); - break; - } - } catch (IOException e) { - 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>{ - - private KLALBRoutingProtocol routingProtocol; - - public KLALBRoutingProtocolAPIServerReleaser(KLALBRoutingProtocol routingProtocol,BiConsumer resource) { - super(resource); - this.routingProtocol=routingProtocol; - } - - @Override - protected void release(BiConsumer 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 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() + : new ArrayList( + 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(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> { + + private KLALBRoutingProtocol routingProtocol; + + public KLALBRoutingProtocolAPIServerReleaser(KLALBRoutingProtocol routingProtocol, + BiConsumer resource) { + super(resource); + this.routingProtocol = routingProtocol; + } + + @Override + protected void release(BiConsumer resource) { + routingProtocol.removeReceiver(resource); + } } diff --git a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolJsonData.java b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolJsonData.java index 0e8e80c..b714a8d 100644 --- a/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolJsonData.java +++ b/src/org/kne/cloud/network/srv6/KLALBRoutingProtocolJsonData.java @@ -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 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 getExtraRoutes() { - return extraRoutes; + + public NodeInfoQueryStatus getStatus() { + return status; } - public void setExtraRoutes(List 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 + + "]"; + } +} diff --git a/src/org/kne/cloud/network/srv6/NodeInfoQueryStatus.java b/src/org/kne/cloud/network/srv6/NodeInfoQueryStatus.java new file mode 100644 index 0000000..5f7d575 --- /dev/null +++ b/src/org/kne/cloud/network/srv6/NodeInfoQueryStatus.java @@ -0,0 +1,6 @@ +package org.kne.cloud.network.srv6; + +public enum NodeInfoQueryStatus { + OK, + DENIED +} diff --git a/src/org/kne/cloud/network/srv6/NodeProfile.java b/src/org/kne/cloud/network/srv6/NodeProfile.java new file mode 100644 index 0000000..7748b11 --- /dev/null +++ b/src/org/kne/cloud/network/srv6/NodeProfile.java @@ -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 + "]"; + } +}