feat(srv6)!: add node-info update invalidation

Publish node metadata through Tiny and Full queries, synchronize Swing and
web topology details, and document the updated protocol workflow.

BREAKING CHANGE: RouterInfo no longer carries device names; peers must use
Tiny node-info queries.
This commit is contained in:
2026-08-28 23:14:04 +08:00
parent 7896ee6638
commit be82f55277
18 changed files with 1130 additions and 373 deletions
@@ -10,12 +10,14 @@ import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
@@ -76,7 +78,50 @@ public class KLALBController {
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW),
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
private List<MultiProtocolSocketAddress> configuredExternalEndpoints = new ArrayList<>();
private List<MultiProtocolSocketAddress> discoveredExternalEndpoints = new ArrayList<>();
public static final class PublishedNodeInfo {
private final String deviceName;
private final String deviceDescription;
private final List<MultiProtocolSocketAddress> externalEndpoints;
private final List<String> extraRoutes;
private final long fullRevision;
private PublishedNodeInfo(String deviceName, String deviceDescription,
List<MultiProtocolSocketAddress> externalEndpoints, List<String> extraRoutes, long fullRevision) {
this.deviceName = deviceName;
this.deviceDescription = deviceDescription;
this.externalEndpoints = Collections.unmodifiableList(
new ArrayList<MultiProtocolSocketAddress>(externalEndpoints));
this.extraRoutes = Collections.unmodifiableList(new ArrayList<String>(extraRoutes));
this.fullRevision = fullRevision;
}
public String getDeviceName() {
return deviceName;
}
public String getDeviceDescription() {
return deviceDescription;
}
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return externalEndpoints;
}
public List<String> getExtraRoutes() {
return extraRoutes;
}
public long getFullRevision() {
return fullRevision;
}
}
private volatile PublishedNodeInfo publishedNodeInfo = new PublishedNodeInfo(null, null,
Collections.<MultiProtocolSocketAddress>emptyList(), Collections.<String>emptyList(), 0L);
private List<MultiProtocolSocketAddress> listensSocketAddress = new CopyOnWriteArrayList<>();
@@ -125,7 +170,8 @@ public class KLALBController {
@Override
public void run() {
try {
List<InetAddress> localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress();
List<InetAddress> localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress();
List<MultiProtocolSocketAddress> currentDiscoveredExternalEndpoints = new ArrayList<>();
for (InetAddress inetAddress : localaddress) {
for (Iterator<MultiProtocolSocketAddress> iterator = listensSocketAddress.iterator(); iterator.hasNext();) {
@@ -137,11 +183,9 @@ public class KLALBController {
MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(tcpl.getProtocol(),
inetAddress.getHostAddress(), tcpl.getPort());
// System.out.println(bind);
synchronized (externalEndpoints) {
if (!externalEndpoints.contains(bind)) {
externalEndpoints.add(bind);
}
}
if (!currentDiscoveredExternalEndpoints.contains(bind)) {
currentDiscoveredExternalEndpoints.add(bind);
}
}
} catch (UnknownHostException e) {
// TODO 自动生成的 catch 块
@@ -149,9 +193,15 @@ public class KLALBController {
}
}
}
lineslock.writeLock().lock();
}
synchronized (externalEndpoints) {
if (!discoveredExternalEndpoints.equals(currentDiscoveredExternalEndpoints)) {
discoveredExternalEndpoints = currentDiscoveredExternalEndpoints;
publishDiscoveredExternalEndpointLocked();
}
}
lineslock.writeLock().lock();
try {
@@ -298,8 +348,8 @@ public class KLALBController {
private boolean checkIsSelf(MultiProtocolSocketAddress inetAddress) throws UnknownHostException {
return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress()
|| externalEndpoints.contains(inetAddress);
return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress()
|| publishedNodeInfo.getExternalEndpoints().contains(inetAddress);
}
private boolean checkIsSelfLocator(InetAddress inetAddress) {
@@ -401,9 +451,104 @@ public class KLALBController {
}
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return externalEndpoints;
}
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return publishedNodeInfo.getExternalEndpoints();
}
public List<MultiProtocolSocketAddress> getExternalEndpointsSnapshot() {
return new ArrayList<MultiProtocolSocketAddress>(publishedNodeInfo.getExternalEndpoints());
}
public PublishedNodeInfo getPublishedNodeInfo() {
return publishedNodeInfo;
}
private List<MultiProtocolSocketAddress> createEffectiveExternalEndpointsLocked() {
List<MultiProtocolSocketAddress> effective = new ArrayList<MultiProtocolSocketAddress>();
for (MultiProtocolSocketAddress endpoint : configuredExternalEndpoints) {
if (!effective.contains(endpoint)) {
effective.add(endpoint);
}
}
for (MultiProtocolSocketAddress endpoint : discoveredExternalEndpoints) {
if (!effective.contains(endpoint)) {
effective.add(endpoint);
}
}
return effective;
}
private void publishNodeInfoLocked(String deviceName, String deviceDescription,
List<MultiProtocolSocketAddress> configEndpoints, List<String> extraRoutes) {
configuredExternalEndpoints = configEndpoints == null
? new ArrayList<MultiProtocolSocketAddress>()
: new ArrayList<MultiProtocolSocketAddress>(configEndpoints);
List<String> publishedExtraRoutes = extraRoutes == null
? new ArrayList<String>() : new ArrayList<String>(extraRoutes);
List<MultiProtocolSocketAddress> effective = createEffectiveExternalEndpointsLocked();
PublishedNodeInfo previous = publishedNodeInfo;
boolean nameChanged = !Objects.equals(previous.getDeviceName(), deviceName);
boolean fullChanged = !Objects.equals(previous.getDeviceDescription(), deviceDescription)
|| !previous.getExternalEndpoints().equals(effective)
|| !previous.getExtraRoutes().equals(publishedExtraRoutes);
externalEndpoints.clear();
externalEndpoints.addAll(effective);
if (srv6Router != null) {
srv6Router.setDeviceName(deviceName);
}
long fullRevision = previous.getFullRevision() + (fullChanged ? 1L : 0L);
publishedNodeInfo = new PublishedNodeInfo(deviceName, deviceDescription, effective, publishedExtraRoutes,
fullRevision);
if (routingProtocol != null) {
if (nameChanged) {
routingProtocol.announceNodeInfoUpdate(
org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED);
}
if (fullChanged) {
routingProtocol.announceNodeInfoUpdate(
org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED);
}
}
}
public void publishNodeInfo(String deviceName, String deviceDescription,
List<MultiProtocolSocketAddress> configEndpoints, List<String> extraRoutes) {
synchronized (externalEndpoints) {
if (configItem != null) {
configItem.setDeviceName(deviceName);
configItem.setDeviceDescription(deviceDescription);
configItem.setExternalEndpoints(new ArrayList<MultiProtocolSocketAddress>(configEndpoints == null
? Collections.<MultiProtocolSocketAddress>emptyList() : configEndpoints));
configItem.setExtraRoutes(new ArrayList<String>(extraRoutes == null
? Collections.<String>emptyList() : extraRoutes));
}
publishNodeInfoLocked(deviceName, deviceDescription, configEndpoints, extraRoutes);
}
}
private void publishDiscoveredExternalEndpointLocked() {
List<MultiProtocolSocketAddress> effective = createEffectiveExternalEndpointsLocked();
if (new HashSet<MultiProtocolSocketAddress>(effective)
.equals(new HashSet<MultiProtocolSocketAddress>(externalEndpoints))) {
return;
}
externalEndpoints.clear();
externalEndpoints.addAll(effective);
PublishedNodeInfo previous = publishedNodeInfo;
publishedNodeInfo = new PublishedNodeInfo(previous.getDeviceName(), previous.getDeviceDescription(), effective,
previous.getExtraRoutes(), previous.getFullRevision() + 1L);
if (routingProtocol != null) {
routingProtocol.announceNodeInfoUpdate(
org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED);
}
}
public void replaceExternalEndpoints(List<MultiProtocolSocketAddress> endpoints) {
PublishedNodeInfo published = publishedNodeInfo;
publishNodeInfo(published.getDeviceName(), published.getDeviceDescription(), endpoints,
published.getExtraRoutes());
}
public IPv6AddressGroup getSelf() {
@@ -602,10 +747,9 @@ public class KLALBController {
}
}
private String generateExternalEndpointsString() {
StringBuilder sbd = new StringBuilder();
for (Iterator<MultiProtocolSocketAddress> iterator = externalEndpoints.iterator(); iterator.hasNext();) {
MultiProtocolSocketAddress klalbRemoteLine = (MultiProtocolSocketAddress) iterator.next();
private String generateExternalEndpointsString() {
StringBuilder sbd = new StringBuilder();
for (MultiProtocolSocketAddress klalbRemoteLine : publishedNodeInfo.getExternalEndpoints()) {
sbd.append(klalbRemoteLine.toString());
sbd.append('\n');
}
@@ -764,8 +908,11 @@ public class KLALBController {
}
}
}).start();
loadSRv6ProtocolStack(selfg, enableVirtualAdapter);
loadController();
loadSRv6ProtocolStack(selfg, enableVirtualAdapter);
if (configItem == null) {
publishNodeInfo(srv6Router.getDeviceName(), null, null, null);
}
loadController();
}
public KLALBController(boolean enableVirtualAdapter, List<InetAddress> dnsaddr) {
@@ -801,10 +948,9 @@ public class KLALBController {
getIpv6Router().setASN(vasn);
}
List<MultiProtocolSocketAddress> linele = configItem.getExternalEndpoints();
if (linele != null) {
getExternalEndpoints().addAll(linele);
}
List<MultiProtocolSocketAddress> linele = configItem.getExternalEndpoints();
publishNodeInfo(configItem.getDeviceName(), configItem.getDeviceDescription(), linele,
configItem.getExtraRoutes());
List<MultiProtocolSocketAddress> linetoc = configItem.getAutoConnections();
if (linetoc != null) {
linetoc.forEach((aline) -> {
@@ -5,9 +5,10 @@ import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Consumer;
import javax.imageio.ImageIO;
@@ -26,7 +27,7 @@ import org.kne.cloud.network.ipv6.IPv6NetworkLink;
import org.kne.cloud.network.klalb.*;
import org.kne.cloud.network.klalb.ui.GraphPanel.GraphNode;
import org.kne.cloud.network.monitor.LinkStatus;
import org.kne.cloud.network.srv6.SRv6Router;
import org.kne.cloud.network.srv6.SRv6Router;
import org.kne.ui.XFrame;
import org.kne.ui.YScrollPane;
@@ -1061,8 +1062,19 @@ public class KLALBStateGUI3 extends XFrame {
}
for (KLALBConfigItem item : config) {
if (item instanceof KLALBControllerConfigItem) {
KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item;
if (item instanceof KLALBControllerConfigItem) {
KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item;
String oldDeviceName = kck.getDeviceName();
String oldDeviceDescription = kck.getDeviceDescription();
List<MultiProtocolSocketAddress> oldExternalEndpoints = kck.getExternalEndpoints() == null
? null
: new ArrayList<MultiProtocolSocketAddress>(kck.getExternalEndpoints());
List<String> oldExtraRoutes = kck.getExtraRoutes() == null
? null : new ArrayList<String>(kck.getExtraRoutes());
String newDeviceName;
String newDeviceDescription;
List<String> newExtraRoutes;
List<MultiProtocolSocketAddress> newExternalEndpoints;
// 保存语言设置
kck.setLanguage(((Language) comboLang.getSelectedItem()).name());
@@ -1070,23 +1082,19 @@ public class KLALBStateGUI3 extends XFrame {
kck.setNogui(nogui.isSelected());
// 保存设备名称
String dnametext = deviceNameSet.getText().trim();
if (dnametext.equals("")) {
kck.setDeviceName(null);
} else {
kck.setDeviceName(dnametext);
}
if (kcontroller != null && kcontroller.getIpv6Router() != null) {
kcontroller.getIpv6Router().setDeviceName(kck.getDeviceName());
}
// 保存设备描述
String ddesctext = deviceDescriptionSet.getText().trim();
if (ddesctext.equals("")) {
kck.setDeviceDescription(null);
} else {
kck.setDeviceDescription(ddesctext);
}
String dnametext = deviceNameSet.getText().trim();
if (dnametext.equals("")) {
newDeviceName = null;
} else {
newDeviceName = dnametext;
}
// 保存设备描述
String ddesctext = deviceDescriptionSet.getText().trim();
if (ddesctext.equals("")) {
newDeviceDescription = null;
} else {
newDeviceDescription = ddesctext;
}
// 保存IPv6地址
@@ -1141,7 +1149,7 @@ public class KLALBStateGUI3 extends XFrame {
}
}
}
kck.setExtraRoutes(eroutes);
newExtraRoutes = eroutes;
// 保存ASN
String asntext = asnFieldSet.getText();
@@ -1232,7 +1240,7 @@ public class KLALBStateGUI3 extends XFrame {
}
}
}
kck.setExternalEndpoints(iaddr1);
newExternalEndpoints = iaddr1;
// 保存自动连接线路表
String[] splt11 = connectLineTabelSet.getText().split("\n");
@@ -1304,7 +1312,23 @@ public class KLALBStateGUI3 extends XFrame {
kck.setNagleDelayTime(nagleDelayTime.getSlider().getValue()*100000L);
kck.setLinkNagleDelayTime(linkNagleDelayTime.getSlider().getValue()*100000L);
kck.setLinkNagleDelayTime(linkNagleDelayTime.getSlider().getValue()*100000L);
boolean deviceNameChanged = !Objects.equals(oldDeviceName, newDeviceName);
boolean deviceDescriptionChanged = !Objects.equals(oldDeviceDescription, newDeviceDescription);
boolean externalEndpointsChanged = !Objects.equals(oldExternalEndpoints, newExternalEndpoints);
boolean extraRoutesChanged = !Objects.equals(oldExtraRoutes, newExtraRoutes);
if (deviceNameChanged || deviceDescriptionChanged || externalEndpointsChanged || extraRoutesChanged) {
if (kcontroller != null) {
kcontroller.publishNodeInfo(newDeviceName, newDeviceDescription, newExternalEndpoints,
newExtraRoutes);
} else {
kck.setDeviceName(newDeviceName);
kck.setDeviceDescription(newDeviceDescription);
kck.setExternalEndpoints(newExternalEndpoints);
kck.setExtraRoutes(newExtraRoutes);
}
}
}
}
@@ -1403,20 +1427,22 @@ public class KLALBStateGUI3 extends XFrame {
if (tsk5 != null) {
tsk5.cancel();
}
tsk5 = new TimerTask() {
@Override
public void run() {
if (isVisible()) {
graph.loadNodes();
for (int i = 0; i < 100; i++) {
graph.runPhy();
}
graph.repaint();
graph.revalidate();
}
}
};
t.scheduleAtFixedRate(tsk5, 1000, 1000);
tsk5 = new TimerTask() {
@Override
public void run() {
SwingUtilities.invokeLater(() -> {
if (isVisible()) {
graph.loadNodes();
for (int i = 0; i < 100; i++) {
graph.runPhy();
}
graph.repaint();
graph.revalidate();
}
});
}
};
t.scheduleAtFixedRate(tsk5, 0, 1000);
}
/**
@@ -1761,9 +1787,18 @@ public class KLALBStateGUI3 extends XFrame {
/**
* 关闭窗口并清理资源
*/
public void close() {
setVisible(false);
if (tsk != null) {
public void close() {
for (int i = 0; i < tabbedPane.getTabCount(); i++) {
Component component = tabbedPane.getComponentAt(i);
if (component instanceof NodeInformationPanel) {
((NodeInformationPanel) component).close();
}
}
if (graph != null) {
graph.close();
}
setVisible(false);
if (tsk != null) {
tsk.cancel();
}
if (st != null) {
@@ -8,28 +8,58 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
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.KLALBRoutingProtocol.LinkDirection;
import org.kne.cloud.network.srv6.KLALBNodeInformation;
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.RouterInfoPacket;
public class NetworkGraphPanel extends GraphPanel {
private KLALBController controller;
private KLALBStateGUI3 klbgui;
private KLALBController controller;
private KLALBStateGUI3 klbgui;
private final KLALBRoutingProtocolAPIClient nodeInfoClient;
private final KLALBRoutingProtocol nodeInfoRoutingProtocol;
private final BiConsumer<IPv6Address, Integer> nodeInfoUpdateListener;
private final Object nodeInfoLock = new Object();
private final Map<IPv6Address, String> nodeNameCache = new HashMap<IPv6Address, String>();
private final Map<IPv6Address, Long> nodeNameCacheTimes = new HashMap<IPv6Address, Long>();
private final Map<IPv6Address, Long> nodeInfoRequestTimes = new HashMap<IPv6Address, Long>();
private final Map<IPv6Address, Long> nodeInfoRequestGenerations = new HashMap<IPv6Address, Long>();
private static final long NODE_INFO_REQUEST_TIMEOUT = 3000L;
private static final long NODE_INFO_CACHE_TTL = 60000L;
private long nextNodeInfoRequestGeneration;
private volatile boolean nodeInfoClosed;
public NetworkGraphPanel(KLALBController controller,KLALBStateGUI3 klbgui) {
super();
this.controller = controller;
this.klbgui=klbgui;
this.nodeInfoRoutingProtocol = controller.getIpv6Router().getKlalbRouteProtol();
this.nodeInfoClient = new KLALBRoutingProtocolAPIClient(nodeInfoRoutingProtocol);
this.nodeInfoUpdateListener = (address, flags) -> {
if ((flags & RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED) != 0) {
SwingUtilities.invokeLater(() -> invalidateTinyNodeInfo(address));
}
};
nodeInfoRoutingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener);
cacheLocalNodeName();
}
public NetworkGraphPanel(KLALBController kc) {
@@ -153,17 +183,145 @@ public class NetworkGraphPanel extends GraphPanel {
}
/**
* 节点标签:显示广播得知的设备名称(若有)+IP地址
* 节点标签:显示 Tiny API 查询的设备名称(若有)+IP地址
*/
private String getNodeText(IPv6Address address) {
StringBuilder sb=new StringBuilder();
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
if(dname!=null)
sb.append(dname).append('\n');
sb.append(InetGraphNode.getText(address));
return sb.toString();
}
private String getNodeText(IPv6Address address) {
StringBuilder sb=new StringBuilder();
String dname;
synchronized (nodeInfoLock) {
dname = nodeNameCache.get(address);
}
if(dname!=null&&!dname.isEmpty())
sb.append(dname).append('\n');
sb.append(InetGraphNode.getText(address));
return sb.toString();
}
private void cacheLocalNodeName() {
IPv6Address localAddress = controller.getIpv6Router().getLocator().getAddress();
synchronized (nodeInfoLock) {
nodeNameCache.put(localAddress, controller.getIpv6Router().getDeviceName());
nodeNameCacheTimes.put(localAddress, System.currentTimeMillis());
}
}
private void requestNodeInfoTiny(final IPv6Address address) {
final long requestTime = System.currentTimeMillis();
final long requestGeneration;
synchronized (nodeInfoLock) {
if (nodeInfoClosed)
return;
Long cachedAt = nodeNameCacheTimes.get(address);
if (nodeNameCache.containsKey(address) && cachedAt != null
&& requestTime - cachedAt < NODE_INFO_CACHE_TTL)
return;
nodeNameCache.remove(address);
nodeNameCacheTimes.remove(address);
Long previousRequestTime = nodeInfoRequestTimes.get(address);
if (previousRequestTime != null && requestTime - previousRequestTime < NODE_INFO_REQUEST_TIMEOUT)
return;
nodeInfoRequestTimes.put(address, requestTime);
requestGeneration = ++nextNodeInfoRequestGeneration;
nodeInfoRequestGenerations.put(address, requestGeneration);
}
try {
nodeInfoClient.requestNodeInfoTiny(
new InetSocketAddress(address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
(info) -> SwingUtilities.invokeLater(() -> handleNodeInfo(address, requestGeneration, info)));
} catch (IOException e) {
synchronized (nodeInfoLock) {
if (nodeInfoRequestGenerations.get(address) != null
&& nodeInfoRequestGenerations.get(address).longValue() == requestGeneration) {
nodeInfoRequestTimes.remove(address);
nodeInfoRequestGenerations.remove(address);
}
}
}
}
private void handleNodeInfo(IPv6Address address, long requestGeneration, KLALBNodeInformation info) {
if (nodeInfoClosed)
return;
boolean currentRequest;
boolean nodeExists = getNodes().containsKey(address);
String deviceName = info == null ? "" : info.getDeviceName();
if (deviceName == null || deviceName.isEmpty())
deviceName = "";
synchronized (nodeInfoLock) {
if (nodeInfoClosed)
return;
Long activeRequestGeneration = nodeInfoRequestGenerations.get(address);
currentRequest = activeRequestGeneration != null
&& activeRequestGeneration.longValue() == requestGeneration;
if (currentRequest) {
nodeInfoRequestTimes.remove(address);
nodeInfoRequestGenerations.remove(address);
if (nodeExists)
nodeNameCache.put(address, deviceName);
else
nodeNameCache.remove(address);
if (nodeExists)
nodeNameCacheTimes.put(address, System.currentTimeMillis());
else
nodeNameCacheTimes.remove(address);
}
}
if (currentRequest && nodeExists) {
((InetGraphNode) getNodes().get(address)).updateLabel();
repaint();
}
}
private void removeNodeInfoState(IPv6Address address) {
synchronized (nodeInfoLock) {
nodeNameCache.remove(address);
nodeNameCacheTimes.remove(address);
nodeInfoRequestTimes.remove(address);
nodeInfoRequestGenerations.remove(address);
}
}
private void invalidateTinyNodeInfo(IPv6Address address) {
synchronized (nodeInfoLock) {
if (nodeInfoClosed)
return;
nodeNameCache.remove(address);
nodeNameCacheTimes.remove(address);
nodeInfoRequestTimes.remove(address);
nodeInfoRequestGenerations.remove(address);
}
InetGraphNode node = (InetGraphNode) getNodes().get(address);
if (node != null) {
node.updateLabel();
}
repaint();
requestNodeInfoTiny(address);
}
public void close() {
synchronized (nodeInfoLock) {
if (nodeInfoClosed)
return;
nodeInfoClosed = true;
nodeNameCache.clear();
nodeNameCacheTimes.clear();
nodeInfoRequestTimes.clear();
nodeInfoRequestGenerations.clear();
}
nodeInfoRoutingProtocol.removeNodeInfoUpdateListener(nodeInfoUpdateListener);
nodeInfoClient.close();
}
@Override
public void removeNotify() {
super.removeNotify();
SwingUtilities.invokeLater(() -> {
if (!isDisplayable() && getParent() == null) {
close();
}
});
}
private double nsPerPixel=5000L;
private class InetGraphEdgeGroup extends GraphEdgeGroup{
public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) {
@@ -181,6 +339,8 @@ public class NetworkGraphPanel extends GraphPanel {
}
protected void loadNodes() {
Map<IPv6Address, Long> addr= controller.getIpv6Router().getKlalbRouteProtol().getAddresses();
IPv6Address localAddress = controller.getIpv6Router().getLocator().getAddress();
cacheLocalNodeName();
Set<IPv6Address> ks=addr.keySet();
for (Iterator<IPv6Address> iterator = ks.iterator(); iterator.hasNext();) {
IPv6Address inet6Address = (IPv6Address) iterator.next();
@@ -188,12 +348,15 @@ public class NetworkGraphPanel extends GraphPanel {
Vector2 v2pos=super.getRandomPos();
getNodes().put(inet6Address,new InetGraphNode(inet6Address,Color.BLACK,v2pos.x,v2pos.y,inet6Address.equals(controller.getIpv6Router().getLocator().getAddress())));
}
if(!inet6Address.equals(localAddress))
requestNodeInfoTiny(inet6Address);
}
Set<IPv6Address> kns=getNodes().keySet();
for (Iterator<IPv6Address> iterator = kns.iterator(); iterator.hasNext();) {
IPv6Address inet6Address = (IPv6Address) iterator.next();
if(!addr.containsKey(inet6Address)) {
iterator.remove();
removeNodeInfoState(inet6Address);
}else {
((InetGraphNode)getNodes().get(inet6Address)).updateLabel();
}
@@ -15,7 +15,7 @@ import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
@@ -32,8 +32,9 @@ import javax.swing.JMenuItem;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseListener;
import java.awt.event.ActionEvent;
import java.util.concurrent.atomic.AtomicLong;
public class NodeInformationPanel extends JPanel {
private IPv6Address address;
@@ -41,7 +42,11 @@ public class NodeInformationPanel extends JPanel {
private KLALBController controller;
private Image image;
private XDefaultListModel<MultiProtocolSocketAddress> listModel=new XDefaultListModel<>();
private XDefaultListModel<MultiProtocolSocketAddress> listModel=new XDefaultListModel<>();
private XDefaultListModel<String> extraRoutesModel=new XDefaultListModel<>();
private volatile boolean active=true;
private final AtomicLong fullInfoGeneration=new AtomicLong();
private final java.util.function.BiConsumer<IPv6Address,Integer> nodeInfoUpdateListener;
public KLALBController getController() {
return controller;
}
@@ -73,16 +78,20 @@ public class NodeInformationPanel extends JPanel {
overviewArea.setWrapStyleWord(true);
overviewArea.setFont(UIEnv.getFont().deriveFont(14.0f));
overviewArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
String ddesc=null;
if(address.equals(controller.getIpv6Router().getLocator().getAddress())&&controller.getConfigItem()!=null) {
ddesc=controller.getConfigItem().getDeviceDescription();
if(ddesc!=null&&ddesc.isEmpty()) {
ddesc=null;
}
}
overviewArea.setText(buildOverviewText(dname, ddesc));
panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER);
overviewArea.setText(buildOverviewText(null, null));
panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER);
JPanel extraRoutesPanel = new JPanel(new BorderLayout());
JList<String> extraRoutesList = new JList<String>(extraRoutesModel);
extraRoutesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
extraRoutesList.setFont(UIEnv.getFont().deriveFont(14.0f));
extraRoutesPanel.add(new JScrollPane(extraRoutesList), BorderLayout.CENTER);
javax.swing.JLabel extraRoutesEmptyLabel=new javax.swing.JLabel(UIEnv.getRsb().getString("noextraroutes"));
extraRoutesEmptyLabel.setText("...");
extraRoutesEmptyLabel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
extraRoutesEmptyLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
extraRoutesPanel.add(extraRoutesEmptyLabel, BorderLayout.SOUTH);
tabbedPane.addTab(UIEnv.getRsb().getString("extraroutes"), null, extraRoutesPanel, null);
JPanel panel_1 = new JPanel();
panel_1.setLayout(new BorderLayout(0, 0));
@@ -163,28 +172,59 @@ public class NodeInformationPanel extends JPanel {
panel_1.add(btnNewButton, BorderLayout.SOUTH);
client=new KLALBRoutingProtocolAPIClient(controller.getIpv6Router().getKlalbRouteProtol());
try {
client.requestNodeInfoFull(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (info)->{
listModel.clear();
for (MultiProtocolSocketAddress multiProtocolSocketAddress : info.getOpenLines()) {
listModel.addElement(multiProtocolSocketAddress);
}
// 用对端返回的设备名称/描述更新概览(旧版本节点无该字段时保留原显示)
String dn=info.getDeviceName();
if(dn==null||dn.isEmpty()) {
dn=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
}
String dd=info.getDeviceDescription();
if(dd!=null&&dd.isEmpty()) {
dd=null;
}
overviewArea.setText(buildOverviewText(dn, dd));
});
} catch (IOException e) {
e.printStackTrace();
}
}
KLALBRoutingProtocol routingProtocol=controller.getIpv6Router().getKlalbRouteProtol();
client=new KLALBRoutingProtocolAPIClient(routingProtocol);
nodeInfoUpdateListener=(updatedAddress, flags) -> {
if(active && address.equals(updatedAddress)
&& (flags & org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED) != 0) {
requestFullInfo(overviewArea, extraRoutesEmptyLabel);
}
};
routingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener);
requestFullInfo(overviewArea, extraRoutesEmptyLabel);
}
private void requestFullInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel) {
final long generation=fullInfoGeneration.incrementAndGet();
try {
client.requestNodeInfoFull(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (info)->{
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));
});
});
} catch (IOException e) {
if(active) e.printStackTrace();
}
}
public synchronized void close() {
if (!active)
return;
active=false;
fullInfoGeneration.incrementAndGet();
KLALBRoutingProtocol routingProtocol=controller.getIpv6Router().getKlalbRouteProtol();
routingProtocol.removeNodeInfoUpdateListener(nodeInfoUpdateListener);
if(client!=null) {
client.close();
client=null;
}
}
@Override
public void removeNotify() {
close();
super.removeNotify();
}
private String buildOverviewText(String dname,String ddesc) {
StringBuilder sb=new StringBuilder();
@@ -7,6 +7,7 @@ import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import com.sun.net.httpserver.*;
import com.google.gson.*;
@@ -25,6 +26,7 @@ import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.NeighborInfo;
import org.kne.cloud.network.srv6.RouterInfo;
import org.kne.cloud.network.srv6.RouterInfoPacket;
import org.kne.cloud.network.srv6.SRv6Router;
/**
@@ -35,14 +37,12 @@ public class KLALBWebServer {
private final KLALBProxySystem proxySystem;
private final MultiProtocolSocketAddress listen;
private HttpServer server;
private ExecutorService httpExecutor;
private final Gson gson;
private final ScheduledExecutorService sseExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "KLALB-Web-SSE");
t.setDaemon(true);
return t;
});
private ScheduledExecutorService sseExecutor = createSseExecutor();
private final Set<HttpExchange> sseClients = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final AtomicBoolean running = new AtomicBoolean(false);
private final Object configUpdateLock = new Object();
public KLALBWebServer(KLALBProxySystem proxySystem, int port) {
this(proxySystem, new MultiProtocolSocketAddress("http", "0.0.0.0", port));
@@ -52,15 +52,37 @@ public class KLALBWebServer {
this.proxySystem = proxySystem;
this.listen = listen;
this.gson = proxySystem.getGson();
this.nodeInfoUpdateListener = (address, flags) -> {
if ((flags & RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED) != 0) {
nodeInfoFullRevisions.merge(address, 1L, Long::sum);
}
if ((flags & RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED) != 0) {
long lifecycleGeneration;
synchronized (this) {
lifecycleGeneration = nodeInfoLifecycleGeneration;
}
invalidateTinyDeviceName(address);
KLALBController kc = this.proxySystem.getKlalbController();
if (kc != null && kc.getIpv6Router() != null
&& kc.getIpv6Router().getKlalbRouteProtol() != null) {
refreshTinyDeviceNameAsync(kc, address, lifecycleGeneration);
}
}
};
}
public synchronized void start() throws IOException {
if (running.get()) return;
nodeInfoFullRevisionEpoch = UUID.randomUUID().toString();
if (sseExecutor == null || sseExecutor.isShutdown()) {
sseExecutor = createSseExecutor();
}
InetSocketAddress socketAddress = "0.0.0.0".equals(listen.getHost())
? new InetSocketAddress(listen.getPort()) : listen.getSocketAddress();
server = HttpServer.create(socketAddress, 0);
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
server.setExecutor(executor);
// API Contexts
server.createContext("/api/status", this::handleStatus);
@@ -76,7 +98,13 @@ public class KLALBWebServer {
// Static Files / SPA Fallback Handler
server.createContext("/", this::handleStatic);
server.start();
try {
server.start();
httpExecutor = executor;
} catch (RuntimeException e) {
executor.shutdownNow();
throw e;
}
running.set(true);
startSseBroadcaster();
@@ -84,7 +112,11 @@ public class KLALBWebServer {
}
public synchronized void stop() {
if (!running.get()) return;
if (!running.get()) {
closeHttpExecutor();
closeNodeInfoClient();
return;
}
running.set(false);
sseExecutor.shutdownNow();
for (HttpExchange client : sseClients) {
@@ -97,9 +129,18 @@ public class KLALBWebServer {
server.stop(1);
server = null;
}
closeHttpExecutor();
closeNodeInfoClient();
System.out.println("KLALB Web Dashboard stopped.");
}
private void closeHttpExecutor() {
if (httpExecutor != null) {
httpExecutor.shutdownNow();
httpExecutor = null;
}
}
public boolean isRunning() {
return running.get();
}
@@ -456,14 +497,160 @@ public class KLALBWebServer {
}
private KLALBRoutingProtocolAPIClient nodeInfoClient;
private static final long TINY_NAME_REQUEST_TIMEOUT_MS = 3000L;
private final ConcurrentMap<IPv6Address, String> tinyDeviceNameCache = new ConcurrentHashMap<>();
private final ConcurrentMap<IPv6Address, Long> tinyDeviceNameCacheTimes = new ConcurrentHashMap<>();
private final Set<IPv6Address> tinyNameRequestsInFlight = ConcurrentHashMap.newKeySet();
private final ConcurrentMap<IPv6Address, Long> tinyNameRequestTimes = new ConcurrentHashMap<>();
private final ConcurrentMap<IPv6Address, Long> tinyNameRequestGenerations = new ConcurrentHashMap<>();
private final ConcurrentMap<IPv6Address, Long> nodeInfoFullRevisions = 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 -> {
Thread t = new Thread(r, "KLALB-Web-SSE");
t.setDaemon(true);
return t;
});
}
private synchronized KLALBRoutingProtocolAPIClient getNodeInfoClient(KLALBController kc) {
return getNodeInfoClient(kc, -1L);
}
private synchronized KLALBRoutingProtocolAPIClient getNodeInfoClient(KLALBController kc,
long expectedLifecycleGeneration) {
if (!running.get() || (expectedLifecycleGeneration >= 0
&& expectedLifecycleGeneration != nodeInfoLifecycleGeneration)
|| kc == null || kc.getIpv6Router() == null
|| kc.getIpv6Router().getKlalbRouteProtol() == null) {
return null;
}
if (nodeInfoClient == null) {
nodeInfoClient = new KLALBRoutingProtocolAPIClient(kc.getIpv6Router().getKlalbRouteProtol());
nodeInfoRoutingProtocol = kc.getIpv6Router().getKlalbRouteProtol();
nodeInfoClient = new KLALBRoutingProtocolAPIClient(nodeInfoRoutingProtocol);
nodeInfoRoutingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener);
}
return nodeInfoClient;
}
private synchronized void closeNodeInfoClient() {
nodeInfoLifecycleGeneration++;
if (nodeInfoRoutingProtocol != null) {
nodeInfoRoutingProtocol.removeNodeInfoUpdateListener(nodeInfoUpdateListener);
nodeInfoRoutingProtocol = null;
}
if (nodeInfoClient != null) {
nodeInfoClient.close();
nodeInfoClient = null;
}
synchronized (tinyNameStateLock) {
tinyDeviceNameCache.clear();
tinyDeviceNameCacheTimes.clear();
tinyNameRequestsInFlight.clear();
tinyNameRequestTimes.clear();
tinyNameRequestGenerations.clear();
}
}
private String getTinyDeviceName(KLALBController kc, IPv6Address addr) {
return getTinyDeviceName(kc, addr, -1L);
}
private String getTinyDeviceName(KLALBController kc, IPv6Address addr, long lifecycleGeneration) {
String cachedName;
long requestStartedAt = 0L;
long requestGeneration;
synchronized (tinyNameStateLock) {
cachedName = tinyDeviceNameCache.get(addr);
Long cachedAt = tinyDeviceNameCacheTimes.get(addr);
long now = System.currentTimeMillis();
if (cachedName != null && cachedAt != null && now - cachedAt < TINY_NAME_CACHE_TTL_MS) return cachedName;
tinyDeviceNameCache.remove(addr);
tinyDeviceNameCacheTimes.remove(addr);
if (tinyNameRequestsInFlight.contains(addr)) {
Long requestedAt = tinyNameRequestTimes.get(addr);
if (requestedAt != null && now - requestedAt < TINY_NAME_REQUEST_TIMEOUT_MS) {
return "";
}
tinyNameRequestsInFlight.remove(addr);
tinyNameRequestTimes.remove(addr);
}
if (!tinyNameRequestsInFlight.add(addr)) return "";
requestStartedAt = now;
tinyNameRequestTimes.put(addr, requestStartedAt);
requestGeneration = ++nextTinyNameRequestGeneration;
tinyNameRequestGenerations.put(addr, requestGeneration);
}
final long requestGenerationToken = requestGeneration;
try {
KLALBRoutingProtocolAPIClient client = lifecycleGeneration < 0
? getNodeInfoClient(kc) : getNodeInfoClient(kc, lifecycleGeneration);
if (client == null) {
synchronized (tinyNameStateLock) {
if (Long.valueOf(requestGenerationToken).equals(tinyNameRequestGenerations.get(addr))) {
tinyNameRequestsInFlight.remove(addr);
tinyNameRequestTimes.remove(addr);
tinyNameRequestGenerations.remove(addr);
}
}
return "";
}
client.requestNodeInfoTiny(
new InetSocketAddress(addr.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
info -> {
synchronized (tinyNameStateLock) {
Long activeRequestGeneration = tinyNameRequestGenerations.get(addr);
if (!Long.valueOf(requestGenerationToken).equals(activeRequestGeneration)) return;
tinyDeviceNameCache.put(addr,
info != null && info.getDeviceName() != null ? info.getDeviceName() : "");
tinyDeviceNameCacheTimes.put(addr, System.currentTimeMillis());
tinyNameRequestsInFlight.remove(addr);
tinyNameRequestTimes.remove(addr);
tinyNameRequestGenerations.remove(addr);
}
});
} catch (IOException e) {
synchronized (tinyNameStateLock) {
Long activeRequestGeneration = tinyNameRequestGenerations.get(addr);
if (Long.valueOf(requestGenerationToken).equals(activeRequestGeneration)) {
tinyNameRequestsInFlight.remove(addr);
tinyNameRequestTimes.remove(addr);
tinyNameRequestGenerations.remove(addr);
}
}
}
return "";
}
private void refreshTinyDeviceNameAsync(KLALBController kc, IPv6Address address, long lifecycleGeneration) {
CompletableFuture.runAsync(() -> {
synchronized (this) {
if (!running.get() || nodeInfoLifecycleGeneration != lifecycleGeneration) return;
getTinyDeviceName(kc, address, lifecycleGeneration);
}
});
}
private void invalidateTinyDeviceName(IPv6Address address) {
synchronized (tinyNameStateLock) {
tinyDeviceNameCache.remove(address);
tinyDeviceNameCacheTimes.remove(address);
tinyNameRequestsInFlight.remove(address);
tinyNameRequestTimes.remove(address);
tinyNameRequestGenerations.remove(address);
}
}
private void handleNodeInfo(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
@@ -500,18 +687,23 @@ public class KLALBWebServer {
if (isSelf) {
// 本机:描述直接取本地控制器配置
KLALBControllerConfigItem cfg = proxySystem.getControllerConfig();
KLALBController.PublishedNodeInfo published = kc.getPublishedNodeInfo();
resp.addProperty("isSelf", true);
String dname = router.getDeviceName() != null ? router.getDeviceName()
: (cfg != null && cfg.getDeviceName() != null ? cfg.getDeviceName() : "");
resp.addProperty("deviceName", dname);
resp.addProperty("deviceName", published.getDeviceName() != null ? published.getDeviceName() : "");
resp.addProperty("deviceDescription",
cfg != null && cfg.getDeviceDescription() != null ? cfg.getDeviceDescription() : "");
published.getDeviceDescription() != null ? published.getDeviceDescription() : "");
JsonArray lines = new JsonArray();
for (MultiProtocolSocketAddress mpsa : kc.getExternalEndpoints()) {
for (MultiProtocolSocketAddress mpsa : published.getExternalEndpoints()) {
lines.add(new JsonPrimitive(mpsa.toString()));
}
resp.add("openLines", lines);
JsonArray extraRoutes = new JsonArray();
if (published.getExtraRoutes() != null) {
for (String route : published.getExtraRoutes()) {
extraRoutes.add(new JsonPrimitive(route != null ? route : ""));
}
}
resp.add("extraRoutes", extraRoutes);
sendJsonResponse(exchange, 200, resp);
return;
}
@@ -520,7 +712,12 @@ public class KLALBWebServer {
resp.addProperty("isSelf", false);
CompletableFuture<KLALBNodeInformation> future = new CompletableFuture<>();
try {
getNodeInfoClient(kc).requestNodeInfoFull(
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));
@@ -533,8 +730,8 @@ public class KLALBWebServer {
String dname = info != null && info.getDeviceName() != null && !info.getDeviceName().isEmpty()
? info.getDeviceName()
: router.getKlalbRouteProtol().getDeviceName(target);
resp.addProperty("deviceName", dname != null ? dname : "");
: "";
resp.addProperty("deviceName", dname);
resp.addProperty("deviceDescription",
info != null && info.getDeviceDescription() != null ? info.getDeviceDescription() : "");
resp.addProperty("reachable", info != null);
@@ -545,11 +742,19 @@ public class KLALBWebServer {
}
}
resp.add("openLines", lines);
JsonArray extraRoutes = new JsonArray();
if (info != null && info.getExtraRoutes() != null) {
for (String route : info.getExtraRoutes()) {
extraRoutes.add(new JsonPrimitive(route != null ? route : ""));
}
}
resp.add("extraRoutes", extraRoutes);
} catch (Exception e) {
resp.addProperty("deviceName", "");
resp.addProperty("deviceDescription", "");
resp.addProperty("reachable", false);
resp.add("openLines", new JsonArray());
resp.add("extraRoutes", new JsonArray());
}
sendJsonResponse(exchange, 200, resp);
}
@@ -584,6 +789,17 @@ public class KLALBWebServer {
KLALBRoutingProtocol rproto = kc.getIpv6Router().getKlalbRouteProtol();
Map<IPv6Address, Long> addrs = rproto.getAddresses();
IPv6Address selfAddr = kc.getIpv6Router().getLocator().getAddress();
KLALBController.PublishedNodeInfo published = kc.getPublishedNodeInfo();
Set<IPv6Address> activeAddresses = addrs == null
? Collections.<IPv6Address>emptySet() : new HashSet<IPv6Address>(addrs.keySet());
synchronized (tinyNameStateLock) {
tinyDeviceNameCache.keySet().removeIf(address -> !activeAddresses.contains(address));
tinyDeviceNameCacheTimes.keySet().removeIf(address -> !activeAddresses.contains(address));
tinyNameRequestsInFlight.removeIf(address -> !activeAddresses.contains(address));
tinyNameRequestTimes.keySet().removeIf(address -> !activeAddresses.contains(address));
tinyNameRequestGenerations.keySet().removeIf(address -> !activeAddresses.contains(address));
}
nodeInfoFullRevisions.keySet().removeIf(address -> !activeAddresses.contains(address));
if (addrs != null) {
for (IPv6Address addr : addrs.keySet()) {
@@ -592,8 +808,13 @@ public class KLALBWebServer {
nodeObj.addProperty("address", addr.toString());
nodeObj.addProperty("compressedAddress", addr.toCompressedString());
nodeObj.addProperty("isSelf", addr.equals(selfAddr));
String dname = rproto.getDeviceName(addr);
String dname = addr.equals(selfAddr) ? kc.getIpv6Router().getDeviceName()
: getTinyDeviceName(kc, addr);
nodeObj.addProperty("deviceName", dname != null ? dname : "");
long fullRevision = addr.equals(selfAddr) ? published.getFullRevision()
: nodeInfoFullRevisions.getOrDefault(addr, 0L);
nodeObj.addProperty("nodeInfoFullRevision", fullRevision);
nodeObj.addProperty("nodeInfoFullRevisionEpoch", nodeInfoFullRevisionEpoch);
nodesArray.add(nodeObj);
}
}
@@ -644,21 +865,33 @@ public class KLALBWebServer {
sendError(exchange, 404, "Configuration not found");
}
} else if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) {
synchronized (configUpdateLock) {
String body = readRequestBody(exchange);
try {
JsonObject json = new JsonParser().parse(body).getAsJsonObject();
KLALBControllerConfigItem current = proxySystem.getControllerConfig();
if (current != null) {
String oldDeviceName = current.getDeviceName();
String oldDeviceDescription = current.getDeviceDescription();
List<MultiProtocolSocketAddress> oldExternalEndpoints = current.getExternalEndpoints() == null
? null
: new ArrayList<MultiProtocolSocketAddress>(current.getExternalEndpoints());
List<String> oldExtraRoutes = current.getExtraRoutes() == null
? null : new ArrayList<String>(current.getExtraRoutes());
String newDeviceName = oldDeviceName;
String newDeviceDescription = oldDeviceDescription;
List<MultiProtocolSocketAddress> newExternalEndpoints = current.getExternalEndpoints();
List<String> newExtraRoutes = current.getExtraRoutes();
if (json.has("deviceName") && !json.get("deviceName").isJsonNull()) {
current.setDeviceName(json.get("deviceName").getAsString());
newDeviceName = json.get("deviceName").getAsString();
} else if (json.has("DeviceName") && !json.get("DeviceName").isJsonNull()) {
current.setDeviceName(json.get("DeviceName").getAsString());
newDeviceName = json.get("DeviceName").getAsString();
}
if (json.has("deviceDescription") && !json.get("deviceDescription").isJsonNull()) {
current.setDeviceDescription(json.get("deviceDescription").getAsString());
newDeviceDescription = json.get("deviceDescription").getAsString();
} else if (json.has("DeviceDescription") && !json.get("DeviceDescription").isJsonNull()) {
current.setDeviceDescription(json.get("DeviceDescription").getAsString());
newDeviceDescription = json.get("DeviceDescription").getAsString();
}
if (json.has("language") && !json.get("language").isJsonNull()) {
@@ -733,7 +966,7 @@ public class KLALBWebServer {
list.add(new MultiProtocolSocketAddress(el.getAsString()));
}
}
current.setExternalEndpoints(list);
newExternalEndpoints = list;
}
if (json.has("autoConnections") || json.has("AutoConnections") || json.has("connectLineTable") || json.has("ConnectLineTable")) {
@@ -783,7 +1016,7 @@ public class KLALBWebServer {
list.add(el.getAsString());
}
}
current.setExtraRoutes(list);
newExtraRoutes = list;
}
if (json.has("networkInterfaceExcepts") || json.has("NetworkInterfaceExcepts")) {
@@ -845,6 +1078,23 @@ public class KLALBWebServer {
current.setDenyExternalEndpointBroadcast(json.get("denyLineTableBroadcast").getAsBoolean());
}
boolean deviceNameChanged = !Objects.equals(oldDeviceName, newDeviceName);
boolean deviceDescriptionChanged = !Objects.equals(oldDeviceDescription, newDeviceDescription);
boolean externalEndpointsChanged = !Objects.equals(oldExternalEndpoints, newExternalEndpoints);
boolean extraRoutesChanged = !Objects.equals(oldExtraRoutes, newExtraRoutes);
if (deviceNameChanged || deviceDescriptionChanged || externalEndpointsChanged || extraRoutesChanged) {
KLALBController kc = proxySystem.getKlalbController();
if (kc != null) {
kc.publishNodeInfo(newDeviceName, newDeviceDescription, newExternalEndpoints,
newExtraRoutes);
} else {
current.setDeviceName(newDeviceName);
current.setDeviceDescription(newDeviceDescription);
current.setExternalEndpoints(newExternalEndpoints);
current.setExtraRoutes(newExtraRoutes);
}
}
// Trigger GUI save consumer or save directly
if (proxySystem.getKLALBGUI() != null && proxySystem.getKLALBGUI().getSaveComsumer() != null) {
proxySystem.getKLALBGUI().getSaveComsumer().accept(proxySystem.getConfig());
@@ -862,6 +1112,7 @@ public class KLALBWebServer {
} catch (Exception e) {
sendError(exchange, 400, "Failed to update configuration: " + e.getMessage());
}
}
} else {
sendError(exchange, 405, "Method not allowed");
}