From 787f1236234957751dcca5558bc41443f41882f5 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sat, 19 Oct 2024 20:28:00 -0500 Subject: [PATCH 01/11] set java version to 17 to enable Text Blocks --- shared/common.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/common.gradle b/shared/common.gradle index 33d1a4c8b2..a8fb28b164 100644 --- a/shared/common.gradle +++ b/shared/common.gradle @@ -3,8 +3,8 @@ apply plugin: "java" apply plugin: "jacoco" java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } wpilibTools.deps.wpilibVersion = wpilibVersion From 4778007e0f5a2fc14063bf6e4ff7165a870b5c38 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sat, 19 Oct 2024 21:15:33 -0500 Subject: [PATCH 02/11] More robust network management This commit changes the way that photonvision interacts with nmcli to control networking on the coprocessor. Instead of modifying an existing connection, Photonvision adds new connections for DHCP and Static IP configurations. It then activiates the proper one at startup and any time that the network configuration is changed. It also now uses the interface name and not the connection name and it checks that the interface is available before making any changes. If the saved interface is not found, it updates the stored interface name and applies the network settings to the current interface. This should minimize the failure to control the network if the network interface wasn't available when PhotonVision first booted. --- .../stores/settings/GeneralSettingsStore.ts | 2 +- .../common/configuration/NetworkConfig.java | 14 +- .../common/networking/NetworkManager.java | 272 ++++++++++++------ .../common/networking/NetworkUtils.java | 31 +- 4 files changed, 214 insertions(+), 105 deletions(-) diff --git a/photon-client/src/stores/settings/GeneralSettingsStore.ts b/photon-client/src/stores/settings/GeneralSettingsStore.ts index 4ea4f06ac2..f5940789fa 100644 --- a/photon-client/src/stores/settings/GeneralSettingsStore.ts +++ b/photon-client/src/stores/settings/GeneralSettingsStore.ts @@ -78,7 +78,7 @@ export const useSettingsStore = defineStore("settings", { return this.general.gpuAcceleration !== undefined; }, networkInterfaceNames(): string[] { - return this.network.networkInterfaceNames.map((i) => i.connName); + return this.network.networkInterfaceNames.map((i) => i.devName); } }, actions: { diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java b/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java index 97e84aee13..7363aacd8f 100644 --- a/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java +++ b/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java @@ -26,7 +26,6 @@ import java.util.Map; import org.photonvision.common.hardware.Platform; import org.photonvision.common.networking.NetworkMode; -import org.photonvision.common.networking.NetworkUtils; import org.photonvision.common.util.file.JacksonUtils; public class NetworkConfig { @@ -51,22 +50,13 @@ public class NetworkConfig { @JsonIgnore public static final String NM_IP_STRING = "${ipaddr}"; public String networkManagerIface; + // TODO: remove these strings if no longer needed public String setStaticCommand = "nmcli con mod ${interface} ipv4.addresses ${ipaddr}/8 ipv4.method \"manual\" ipv6.method \"disabled\""; public String setDHCPcommand = "nmcli con mod ${interface} ipv4.method \"auto\" ipv6.method \"disabled\""; public NetworkConfig() { - if (Platform.isLinux()) { - // Default to the name of the first Ethernet connection. Otherwise, "Wired connection 1" is a - // reasonable guess - this.networkManagerIface = - NetworkUtils.getAllWiredInterfaces().stream() - .map(it -> it.connName) - .findFirst() - .orElse("Wired connection 1"); - } - // We can (usually) manage networking on Linux devices, and if we can, we should try to. Command // line inhibitions happen at a level above this class setShouldManage(deviceCanManageNetwork()); @@ -112,7 +102,7 @@ public Map toHashMap() { @JsonIgnore public String getPhysicalInterfaceName() { - return NetworkUtils.getNMinfoForConnName(this.networkManagerIface).devName; + return this.networkManagerIface; } @JsonIgnore diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java index 47b6540708..40eadeb70d 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java @@ -52,102 +52,54 @@ public void initialize(boolean shouldManage) { return; } - var config = ConfigManager.getInstance().getConfig().getNetworkConfig(); - logger.info("Setting " + config.connectionType + " with team " + config.ntServerAddress); - if (Platform.isLinux()) { - if (!PlatformUtils.isRoot()) { - logger.error("Cannot manage hostname without root!"); - } + if (!Platform.isLinux()) { + logger.info("Not managing network on non-Linux platforms."); + return; + } - // always set hostname - if (!config.hostname.isEmpty()) { - try { - var shell = new ShellExec(true, false); - shell.executeBashCommand("cat /etc/hostname | tr -d \" \\t\\n\\r\""); - var oldHostname = shell.getOutput().replace("\n", ""); - - var setHostnameRetCode = - shell.executeBashCommand( - "echo $NEW_HOSTNAME > /etc/hostname".replace("$NEW_HOSTNAME", config.hostname)); - setHostnameRetCode = - shell.executeBashCommand("hostnamectl set-hostname " + config.hostname); - - // Add to /etc/hosts - var addHostRetCode = - shell.executeBashCommand( - String.format( - "sed -i \"s/127.0.1.1.*%s/127.0.1.1\\t%s/g\" /etc/hosts", - oldHostname, config.hostname)); + if (!PlatformUtils.isRoot()) { + logger.error("Cannot manage hostname without root!"); + return; + } - shell.executeBashCommand("sudo service avahi-daemon restart"); + var physicalDevices = NetworkUtils.getAllWiredInterfaces(); - var success = setHostnameRetCode == 0 && addHostRetCode == 0; - if (!success) { - logger.error( - "Setting hostname returned non-zero codes (hostname/hosts) " - + setHostnameRetCode - + "|" - + addHostRetCode - + "!"); - } else { - logger.info("Set hostname to " + config.hostname); - } - } catch (Exception e) { - logger.error("Failed to set hostname!", e); - } + if (physicalDevices.size() == 0) { + logger.warn("No network interfaces available. Maybe ethernet isn't connected?"); + // start polling for an interface? + return; + } - } else { - logger.warn("Got empty hostname?"); - } + var config = ConfigManager.getInstance().getConfig().getNetworkConfig(); - if (config.connectionType == NetworkMode.DHCP) { - var shell = new ShellExec(); - try { - // set nmcli back to DHCP, and re-run dhclient -- this ought to grab a new IP address - shell.executeBashCommand( - config.setDHCPcommand.replace( - NetworkConfig.NM_IFACE_STRING, config.getEscapedInterfaceName())); - shell.executeBashCommand("dhclient " + config.getPhysicalInterfaceName(), false); - } catch (Exception e) { - logger.error("Exception while setting DHCP!"); - } - } else if (config.connectionType == NetworkMode.STATIC) { - var shell = new ShellExec(); - if (!config.staticIp.isEmpty()) { - try { - shell.executeBashCommand( - config - .setStaticCommand - .replace(NetworkConfig.NM_IFACE_STRING, config.getEscapedInterfaceName()) - .replace(NetworkConfig.NM_IP_STRING, config.staticIp)); - - if (Platform.isRaspberryPi()) { - // Pi's need to manually have their interface adjusted?? and the 5-second sleep is - // integral in my testing (Matt) - shell.executeBashCommand( - "sh -c 'nmcli con down " - + config.getEscapedInterfaceName() - + "; nmcli con up " - + config.getEscapedInterfaceName() - + "'"); - } else { - // for now just bring down /up -- more testing needed on beelink et al. - shell.executeBashCommand( - "sh -c 'nmcli con down " - + config.getEscapedInterfaceName() - + "; nmcli con up " - + config.getEscapedInterfaceName() - + "'"); - } - } catch (Exception e) { - logger.error("Error while setting static IP!", e); - } - } else { - logger.warn("Got empty static IP?"); - } + if (physicalDevices.stream().noneMatch(it -> (it.devName.equals(config.networkManagerIface)))) { + try { + var iFace = physicalDevices.stream().findFirst().orElseThrow(); + logger.warn("The configured interface doesn't match any available interface. Applying configuration to " + iFace.devName); + // update NetworkConfig with actual interface + config.networkManagerIface = iFace.devName; + ConfigManager.getInstance().requestSave(); + } catch (Exception e) { + // already checked that there is at least one item in physicalDevices, so this should never happen + logger.error("No valid network interfaces to manage", e); + return; } + } + + logger.info("Setting " + config.connectionType + " with team " + config.ntServerAddress + " on " + config.networkManagerIface); + + // always set hostname (unless it's blank) + if (!config.hostname.isBlank()) { + setHostname(config.hostname); } else { - logger.info("Not managing network on non-Linux platforms"); + logger.warn("Got empty hostname?"); + // should we force it back to photonvision in this case? + } + + if (config.connectionType == NetworkMode.DHCP) { + setConnectionDHCP(config); + } else if (config.connectionType == NetworkMode.STATIC) { + setConnectionStatic(config); } } @@ -162,4 +114,146 @@ public void reinitialize() { "restartServer", true)); } + + private void setHostname(String hostname) { + try { + var shell = new ShellExec(true, false); + shell.executeBashCommand("cat /etc/hostname | tr -d \" \\t\\n\\r\""); + var oldHostname = shell.getOutput().replace("\n", ""); + logger.debug("Old host name: >" + oldHostname +"<"); + logger.debug("New host name: >" + hostname +"<"); + + if (!oldHostname.equals(hostname)) { + var setHostnameRetCode = + shell.executeBashCommand( + "echo $NEW_HOSTNAME > /etc/hostname".replace("$NEW_HOSTNAME", hostname)); + setHostnameRetCode = + shell.executeBashCommand("hostnamectl set-hostname " + hostname); + + // Add to /etc/hosts + var addHostRetCode = + shell.executeBashCommand( + String.format( + "sed -i \"s/127.0.1.1.*%s/127.0.1.1\\t%s/g\" /etc/hosts", + oldHostname, hostname)); + + if (Platform.isRaspberryPi()) { + // TODO: test on RaspberryPi if this is still needed + shell.executeBashCommand("sudo service avahi-daemon restart"); + } else { + shell.executeBashCommand("sudo systemctl restart avahi-daemon.service"); + } + + var success = setHostnameRetCode == 0 && addHostRetCode == 0; + if (!success) { + logger.error( + "Setting hostname returned non-zero codes (hostname/hosts) " + + setHostnameRetCode + + "|" + + addHostRetCode + + "!"); + } else { + logger.info("Set hostname to " + hostname); + } + } + } catch (Exception e) { + logger.error("Failed to set hostname!", e); + } + } + + private void setConnectionDHCP(NetworkConfig config) { + String connName = "dhcp-" + config.networkManagerIface; + + String addDHCPcommand = """ + nmcli connection add + con-name "${connection}" + ifname "${interface}" + type ethernet + autoconnect no + ipv4.method auto + ipv6.method disabled + """; + addDHCPcommand = addDHCPcommand.replaceAll("[\\n]", " "); + + var shell = new ShellExec(); + try { + // set nmcli back to DHCP, and re-run dhclient -- this ought to grab a new IP address + if (NetworkUtils.connDoesNotExist(connName)) { + // create connection + logger.info("Creating the DHCP connection " + connName ); + shell.executeBashCommand( + addDHCPcommand + .replace("${connection}", connName) + .replace("${interface}", config.networkManagerIface) + ); + } + // activate it + logger.info("Activating the DHCP connection " + connName ); + shell.executeBashCommand("nmcli connection up " + connName, false); + + if (Platform.isRaspberryPi()) { + shell.executeBashCommand("dhclient " + config.networkManagerIface, false); + } + } catch (Exception e) { + logger.error("Exception while setting DHCP!", e); + } + } + + private void setConnectionStatic(NetworkConfig config) { + String connName = "static-" + config.networkManagerIface; + String addStaticCommand = """ + nmcli connection add + con-name "${connection}" + ifname "${interface}" + type ethernet + autoconnect no + ipv4.addresses ${ipaddr}/8 + ipv4.gateway ${gateway} + ipv4.method "manual" + ipv6.method "disabled" + """; + addStaticCommand = addStaticCommand.replaceAll("[\\n]", " "); + + String modStaticCommand = "nmcli connection mod \"${connection}\" ipv4.addresses ${ipaddr}/8 ipv4.gateway ${gateway}"; + + if (config.staticIp.isBlank()) { + logger.warn("Got empty static IP?"); + return; + } + + // guess at the gateway from the staticIp + String[] parts = config.staticIp.split("\\."); + parts[parts.length-1] = "1"; + String gateway = String.join(".", parts); + + var shell = new ShellExec(); + try { + // set nmcli back to DHCP, and re-run dhclient -- this ought to grab a new IP address + if (NetworkUtils.connDoesNotExist(connName)) { + // create connection + logger.info("Creating the Static connection " + connName ); + shell.executeBashCommand( + addStaticCommand + .replace("${connection}", connName) + .replace("${interface}", config.networkManagerIface) + .replace("${ipaddr}", config.staticIp) + .replace("${gateway}", gateway) + ); + } else { + // modify it in case the static IP address is different + logger.info("Modifying the Static connection " + connName ); + shell.executeBashCommand( + modStaticCommand + .replace("${connection}", connName) + .replace("${ipaddr}", config.staticIp) + .replace("${gateway}", gateway) + ); + } + // activate it + logger.info("Activating the Static connection " + connName ); + shell.executeBashCommand("nmcli connection up " + connName, false); + } catch (Exception e) { + logger.error("Error while setting static IP!", e); + } + } } diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java index cf84309e57..f1efbe5131 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java @@ -102,13 +102,16 @@ public static List getAllInterfaces() { Pattern.compile("GENERAL.CONNECTION:(.*)\nGENERAL.DEVICE:(.*)\nGENERAL.TYPE:(.*)"); Matcher matcher = pattern.matcher(out); while (matcher.find()) { - ret.add(new NMDeviceInfo(matcher.group(1), matcher.group(2), matcher.group(3))); + if (!matcher.group(2).equals("lo")) { + // only include non-loopback devices + ret.add(new NMDeviceInfo(matcher.group(1), matcher.group(2), matcher.group(3))); + } } } catch (IOException e) { - logger.error("Could not get active NM ifaces!", e); + logger.error("Could not get active network interfaces!", e); } - logger.debug("Found network interfaces:\n" + ret); + logger.debug("Found network interfaces: " + ret); allInterfaces = ret; return ret; @@ -136,4 +139,26 @@ public static NMDeviceInfo getNMinfoForConnName(String connName) { } return null; } + + public static NMDeviceInfo getNMinfoForDevName(String devName) { + for (NMDeviceInfo info : getAllActiveInterfaces()) { + if (info.devName.equals(devName)) { + return info; + } + } + logger.warn("Could not find a match for network device " + devName); + return null; + } + + public static boolean connDoesNotExist(String connName) { + var shell = new ShellExec(true, true); + try { + // set nmcli back to DHCP, and re-run dhclient -- this ought to grab a new IP address + shell.executeBashCommand("nmcli -f GENERAL.STATE connection show \"" + connName + "\""); + return (shell.getExitCode() == 10); + } catch (Exception e) { + logger.error("Exception from nmcli!"); + } + return false; + } } From 2b00200807b5fc531ff14c3abf7870f7a30deac6 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sat, 19 Oct 2024 21:48:25 -0500 Subject: [PATCH 03/11] improve message --- .../java/org/photonvision/common/networking/NetworkManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java index 40eadeb70d..b7b98facbb 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java @@ -58,7 +58,7 @@ public void initialize(boolean shouldManage) { } if (!PlatformUtils.isRoot()) { - logger.error("Cannot manage hostname without root!"); + logger.error("Cannot manage network without root!"); return; } From 766ae885e32be69ea92674a623213e513b733fce Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sat, 19 Oct 2024 23:09:13 -0500 Subject: [PATCH 04/11] quote connections when bringing up --- .../org/photonvision/common/networking/NetworkManager.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java index b7b98facbb..3c63be37be 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java @@ -93,7 +93,6 @@ public void initialize(boolean shouldManage) { setHostname(config.hostname); } else { logger.warn("Got empty hostname?"); - // should we force it back to photonvision in this case? } if (config.connectionType == NetworkMode.DHCP) { @@ -189,7 +188,7 @@ private void setConnectionDHCP(NetworkConfig config) { } // activate it logger.info("Activating the DHCP connection " + connName ); - shell.executeBashCommand("nmcli connection up " + connName, false); + shell.executeBashCommand("nmcli connection up \"${connection}\"".replace("${connection}", connName), false); if (Platform.isRaspberryPi()) { shell.executeBashCommand("dhclient " + config.networkManagerIface, false); @@ -251,7 +250,7 @@ private void setConnectionStatic(NetworkConfig config) { } // activate it logger.info("Activating the Static connection " + connName ); - shell.executeBashCommand("nmcli connection up " + connName, false); + shell.executeBashCommand("nmcli connection up \"${connection}\"".replace("${connection}", connName), false); } catch (Exception e) { logger.error("Error while setting static IP!", e); } From 6aa4df7a1db0092658332dd58404af502cce3b5f Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sat, 19 Oct 2024 23:22:23 -0500 Subject: [PATCH 05/11] try configured interface if none found --- .../common/networking/NetworkManager.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java index 3c63be37be..634f2835fe 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java @@ -63,7 +63,6 @@ public void initialize(boolean shouldManage) { } var physicalDevices = NetworkUtils.getAllWiredInterfaces(); - if (physicalDevices.size() == 0) { logger.warn("No network interfaces available. Maybe ethernet isn't connected?"); // start polling for an interface? @@ -71,18 +70,23 @@ public void initialize(boolean shouldManage) { } var config = ConfigManager.getInstance().getConfig().getNetworkConfig(); - if (physicalDevices.stream().noneMatch(it -> (it.devName.equals(config.networkManagerIface)))) { try { + // if the configured interface isn't in the list of available ones, try one that is var iFace = physicalDevices.stream().findFirst().orElseThrow(); logger.warn("The configured interface doesn't match any available interface. Applying configuration to " + iFace.devName); - // update NetworkConfig with actual interface + // update NetworkConfig with found interface config.networkManagerIface = iFace.devName; ConfigManager.getInstance().requestSave(); } catch (Exception e) { - // already checked that there is at least one item in physicalDevices, so this should never happen - logger.error("No valid network interfaces to manage", e); - return; + // if there are no available interfaces, go with the one from settings + logger.warn("No physical interface found. Maybe ethernet isn't connected?"); + if (config.networkManagerIface.isBlank()) { + // if it's also empty, there is nothing to configure + logger.error("No valid network interfaces to manage", e); + // TODO: add a thread that monitors the network + return; + } } } From d7b27a703e5eab72fdbad80deddd117f9c19dd87 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 20 Oct 2024 00:08:14 -0500 Subject: [PATCH 06/11] minor clean up --- .../org/photonvision/common/networking/NetworkManager.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java index 634f2835fe..4ce5ab1cf1 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java @@ -17,6 +17,8 @@ package org.photonvision.common.networking; +import java.util.NoSuchElementException; + import org.photonvision.common.configuration.ConfigManager; import org.photonvision.common.configuration.NetworkConfig; import org.photonvision.common.dataflow.DataChangeDestination; @@ -78,12 +80,12 @@ public void initialize(boolean shouldManage) { // update NetworkConfig with found interface config.networkManagerIface = iFace.devName; ConfigManager.getInstance().requestSave(); - } catch (Exception e) { + } catch (NoSuchElementException e) { // if there are no available interfaces, go with the one from settings logger.warn("No physical interface found. Maybe ethernet isn't connected?"); if (config.networkManagerIface.isBlank()) { // if it's also empty, there is nothing to configure - logger.error("No valid network interfaces to manage", e); + logger.error("No valid network interfaces to manage"); // TODO: add a thread that monitors the network return; } From 82336296bd013bc7ab296324d763a90973e2e950 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 20 Oct 2024 14:17:13 -0500 Subject: [PATCH 07/11] reinitialize network when device connects --- .../configuration/PhotonConfiguration.java | 2 +- .../common/networking/NetworkManager.java | 41 ++++++++++++++++--- .../common/networking/NetworkUtils.java | 10 ++++- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java b/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java index 614c00135c..c776a6cb77 100644 --- a/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java +++ b/photon-core/src/main/java/org/photonvision/common/configuration/PhotonConfiguration.java @@ -120,7 +120,7 @@ public Map toHashMap() { // Hack active interfaces into networkSettings var netConfigMap = networkConfig.toHashMap(); - netConfigMap.put("networkInterfaceNames", NetworkUtils.getAllWiredInterfaces()); + netConfigMap.put("networkInterfaceNames", NetworkUtils.getAllActiveWiredInterfaces()); netConfigMap.put("networkingDisabled", NetworkManager.getInstance().networkingIsDisabled); settingsSubmap.put("networkSettings", netConfigMap); diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java index 4ce5ab1cf1..02b88ee338 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java @@ -17,6 +17,8 @@ package org.photonvision.common.networking; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.NoSuchElementException; import org.photonvision.common.configuration.ConfigManager; @@ -29,7 +31,9 @@ import org.photonvision.common.hardware.PlatformUtils; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; +import org.photonvision.common.networking.NetworkUtils.NMDeviceInfo; import org.photonvision.common.util.ShellExec; +import org.photonvision.common.util.TimedTaskManager; public class NetworkManager { private static final Logger logger = new Logger(NetworkManager.class, LogGroup.General); @@ -64,17 +68,17 @@ public void initialize(boolean shouldManage) { return; } - var physicalDevices = NetworkUtils.getAllWiredInterfaces(); - if (physicalDevices.size() == 0) { - logger.warn("No network interfaces available. Maybe ethernet isn't connected?"); - // start polling for an interface? - return; + // Start tasks to monitor the network interface(s) + var ethernetDevices = NetworkUtils.getAllWiredInterfaces(); + for (NMDeviceInfo deviceInfo : ethernetDevices) { + TimedTaskManager.getInstance().addTask("deviceStatus-"+deviceInfo.devName, deviceStatus(deviceInfo.devName), 5000); } + var physicalDevices = NetworkUtils.getAllActiveWiredInterfaces(); var config = ConfigManager.getInstance().getConfig().getNetworkConfig(); if (physicalDevices.stream().noneMatch(it -> (it.devName.equals(config.networkManagerIface)))) { try { - // if the configured interface isn't in the list of available ones, try one that is + // if the configured interface isn't in the list of available ones, select one that is var iFace = physicalDevices.stream().findFirst().orElseThrow(); logger.warn("The configured interface doesn't match any available interface. Applying configuration to " + iFace.devName); // update NetworkConfig with found interface @@ -261,4 +265,29 @@ private void setConnectionStatic(NetworkConfig config) { logger.error("Error while setting static IP!", e); } } + + // Detects changes in the carrier and reinitializes after re-connect + private Runnable deviceStatus(String devName) { + Path file = Path.of("/sys/class/net/{device}/carrier".replace("{device}", devName)); + logger.debug("Watching device at path: " + file.toString()); + var last = new Object() {boolean carrier = true;}; + return () -> + { + try { + boolean carrier = Files.readString(file).trim().equals("1"); + if (carrier != last.carrier) { + if (carrier) { + // carrier came back + logger.info("Interface " + devName + " has re-connected, reinitializing"); + reinitialize(); + } else { + logger.warn("Interface " + devName + " is disconnected, check Ethernet!"); + } + } + last.carrier = carrier; + } catch (Exception e) { + logger.error("Could not check network status", e); + } + }; + } } diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java index f1efbe5131..0f9a282ec7 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkUtils.java @@ -126,8 +126,14 @@ public static List getAllActiveInterfaces() { } public static List getAllWiredInterfaces() { - return getAllActiveInterfaces().stream() - .filter(it -> it.nmType == NMType.NMTYPE_ETHERNET) + return getAllInterfaces().stream() + .filter(it -> it.nmType.equals(NMType.NMTYPE_ETHERNET)) + .collect(Collectors.toList()); + } + + public static List getAllActiveWiredInterfaces() { + return getAllWiredInterfaces().stream() + .filter(it -> !it.connName.isBlank()) .collect(Collectors.toList()); } From 4763ed31be4caf8d5c436c48e6f8880111d578c9 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 20 Oct 2024 14:35:58 -0500 Subject: [PATCH 08/11] =?UTF-8?q?one=20single=20space=20=E2=98=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/photonvision/common/networking/NetworkManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java index 02b88ee338..4596dec895 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java @@ -271,7 +271,7 @@ private Runnable deviceStatus(String devName) { Path file = Path.of("/sys/class/net/{device}/carrier".replace("{device}", devName)); logger.debug("Watching device at path: " + file.toString()); var last = new Object() {boolean carrier = true;}; - return () -> + return () -> { try { boolean carrier = Files.readString(file).trim().equals("1"); From 8c063a7182f64b0280be3ba84c5a148e7d35dfb4 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 20 Oct 2024 15:15:06 -0500 Subject: [PATCH 09/11] check for null --- .../org/photonvision/common/configuration/NetworkConfig.java | 2 +- .../java/org/photonvision/common/networking/NetworkManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java b/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java index 7363aacd8f..b38cbdb497 100644 --- a/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java +++ b/photon-core/src/main/java/org/photonvision/common/configuration/NetworkConfig.java @@ -49,7 +49,7 @@ public class NetworkConfig { @JsonIgnore public static final String NM_IFACE_STRING = "${interface}"; @JsonIgnore public static final String NM_IP_STRING = "${ipaddr}"; - public String networkManagerIface; + public String networkManagerIface = ""; // TODO: remove these strings if no longer needed public String setStaticCommand = "nmcli con mod ${interface} ipv4.addresses ${ipaddr}/8 ipv4.method \"manual\" ipv6.method \"disabled\""; diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java index 4596dec895..92f3dbc883 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java @@ -87,7 +87,7 @@ public void initialize(boolean shouldManage) { } catch (NoSuchElementException e) { // if there are no available interfaces, go with the one from settings logger.warn("No physical interface found. Maybe ethernet isn't connected?"); - if (config.networkManagerIface.isBlank()) { + if (config.networkManagerIface == null || config.networkManagerIface.isBlank()) { // if it's also empty, there is nothing to configure logger.error("No valid network interfaces to manage"); // TODO: add a thread that monitors the network From 54ea6d70c67979b055fcd9d4eb8c13629b29c60e Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 20 Oct 2024 22:05:30 -0500 Subject: [PATCH 10/11] allow checking if a task is active --- .../java/org/photonvision/common/util/TimedTaskManager.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/photon-core/src/main/java/org/photonvision/common/util/TimedTaskManager.java b/photon-core/src/main/java/org/photonvision/common/util/TimedTaskManager.java index 7107faae32..6916702391 100644 --- a/photon-core/src/main/java/org/photonvision/common/util/TimedTaskManager.java +++ b/photon-core/src/main/java/org/photonvision/common/util/TimedTaskManager.java @@ -79,4 +79,8 @@ public void cancelTask(String identifier) { activeTasks.remove(identifier); } } + + public boolean taskActive(String identifier) { + return activeTasks.containsKey(identifier); + } } From a710397cc09d6f2b584c61f0ee5dce01f321fc22 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 20 Oct 2024 22:06:28 -0500 Subject: [PATCH 11/11] remove things not needed for RaspberryPi --- .../common/networking/NetworkManager.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java index 92f3dbc883..a53451b3a6 100644 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkManager.java @@ -71,7 +71,10 @@ public void initialize(boolean shouldManage) { // Start tasks to monitor the network interface(s) var ethernetDevices = NetworkUtils.getAllWiredInterfaces(); for (NMDeviceInfo deviceInfo : ethernetDevices) { - TimedTaskManager.getInstance().addTask("deviceStatus-"+deviceInfo.devName, deviceStatus(deviceInfo.devName), 5000); + var task = "deviceStatus-" + deviceInfo.devName; + if (!TimedTaskManager.getInstance().taskActive(task)) { + TimedTaskManager.getInstance().addTask(task, deviceStatus(deviceInfo.devName), 5000); + } } var physicalDevices = NetworkUtils.getAllActiveWiredInterfaces(); @@ -146,12 +149,7 @@ private void setHostname(String hostname) { "sed -i \"s/127.0.1.1.*%s/127.0.1.1\\t%s/g\" /etc/hosts", oldHostname, hostname)); - if (Platform.isRaspberryPi()) { - // TODO: test on RaspberryPi if this is still needed - shell.executeBashCommand("sudo service avahi-daemon restart"); - } else { - shell.executeBashCommand("sudo systemctl restart avahi-daemon.service"); - } + shell.executeBashCommand("systemctl restart avahi-daemon.service"); var success = setHostnameRetCode == 0 && addHostRetCode == 0; if (!success) { @@ -199,10 +197,6 @@ private void setConnectionDHCP(NetworkConfig config) { // activate it logger.info("Activating the DHCP connection " + connName ); shell.executeBashCommand("nmcli connection up \"${connection}\"".replace("${connection}", connName), false); - - if (Platform.isRaspberryPi()) { - shell.executeBashCommand("dhclient " + config.networkManagerIface, false); - } } catch (Exception e) { logger.error("Exception while setting DHCP!", e); } @@ -269,7 +263,7 @@ private void setConnectionStatic(NetworkConfig config) { // Detects changes in the carrier and reinitializes after re-connect private Runnable deviceStatus(String devName) { Path file = Path.of("/sys/class/net/{device}/carrier".replace("{device}", devName)); - logger.debug("Watching device at path: " + file.toString()); + logger.debug("Watching network interface at path: " + file.toString()); var last = new Object() {boolean carrier = true;}; return () -> {