forked from KNEMC/KLALB
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:
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user