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
@@ -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);
}
}