From 5b21e1d89d4f5c72cef55849bfdadb5373394e08 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 10 Nov 2024 17:54:45 -0600 Subject: [PATCH 01/19] add connExists() --- .../photonvision/common/networking/NetworkUtils.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 0f9a282ec7..4e658b7320 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 @@ -156,6 +156,18 @@ public static NMDeviceInfo getNMinfoForDevName(String devName) { return null; } + public static boolean connExists(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 -g GENERAL.STATE connection show \"" + connName + "\""); + return (shell.getExitCode() == 0); + } catch (Exception e) { + logger.error("Exception from nmcli!"); + } + return false; + } + public static boolean connDoesNotExist(String connName) { var shell = new ShellExec(true, true); try { From 9d233e98824ebd5c9473db9000f71f1ad2bbe2d8 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 10 Nov 2024 17:55:34 -0600 Subject: [PATCH 02/19] add link-local and update dhcp conn each time its activated --- .../common/networking/NetworkManager.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 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 e2f44103ff..4fa595ae0f 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 @@ -169,27 +169,27 @@ private void setConnectionDHCP(NetworkConfig config) { String connName = "dhcp-" + config.networkManagerIface; String addDHCPcommand = """ - nmcli connection add + nmcli connection ${action} con-name "${connection}" ifname "${interface}" type ethernet - autoconnect no + autoconnect yes ipv4.method auto + ipv4.dhcp-timeout infinity + ipv4.link-local enabled ipv6.method disabled """; addDHCPcommand = addDHCPcommand.replaceAll("[\\n]", " "); var shell = new ShellExec(); try { - if (NetworkUtils.connDoesNotExist(connName)) { - // create connection - logger.info("Creating the DHCP connection " + connName ); - shell.executeBashCommand( - addDHCPcommand - .replace("${connection}", connName) - .replace("${interface}", config.networkManagerIface) - ); - } + logger.info("Updating the DHCP connection " + connName ); + shell.executeBashCommand("nmcli connection add " + + addDHCPcommand + .replace("${action}", NetworkUtils.connExists(connName) ? "add" : "modify") + .replace("${connection}", connName) + .replace("${interface}", config.networkManagerIface) + ); // activate it logger.info("Activating the DHCP connection " + connName ); shell.executeBashCommand("nmcli connection up \"${connection}\"".replace("${connection}", connName), false); From 73cc0025fdd29c940655aea7482044fef1ac4317 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 10 Nov 2024 19:30:50 -0600 Subject: [PATCH 03/19] this works --- .../common/networking/NetworkManager.java | 25 +++++++++++++------ .../common/networking/NetworkUtils.java | 2 +- 2 files changed, 18 insertions(+), 9 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 4fa595ae0f..11e4eb1c78 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 @@ -169,26 +169,35 @@ private void setConnectionDHCP(NetworkConfig config) { String connName = "dhcp-" + config.networkManagerIface; String addDHCPcommand = """ - nmcli connection ${action} + nmcli conn add con-name "${connection}" ifname "${interface}" type ethernet + """.replaceAll("[\\n]", " "); + + String modDHCPCommand = """ + nmcli conn modify "${connection}" autoconnect yes ipv4.method auto ipv4.dhcp-timeout infinity ipv4.link-local enabled ipv6.method disabled - """; - addDHCPcommand = addDHCPcommand.replaceAll("[\\n]", " "); + """.replaceAll("[\\n]", " "); var shell = new ShellExec(); try { + if (NetworkUtils.connDoesNotExist(connName)) { + logger.info("Updating the DHCP connection " + connName ); + shell.executeBashCommand( + addDHCPcommand + .replace("${connection}", connName) + .replace("${interface}", config.networkManagerIface) + ); + } logger.info("Updating the DHCP connection " + connName ); - shell.executeBashCommand("nmcli connection add " + - addDHCPcommand - .replace("${action}", NetworkUtils.connExists(connName) ? "add" : "modify") - .replace("${connection}", connName) - .replace("${interface}", config.networkManagerIface) + shell.executeBashCommand( + modDHCPCommand + .replace("${connection}", connName) ); // activate it logger.info("Activating the DHCP connection " + connName ); 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 4e658b7320..cc3a61b90f 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 @@ -172,7 +172,7 @@ 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 + "\""); + shell.executeBashCommand("nmcli -g GENERAL.STATE connection show \"" + connName + "\""); return (shell.getExitCode() == 10); } catch (Exception e) { logger.error("Exception from nmcli!"); From 0bcce11f0e47e65988b5f5e81adc6f0dbc7a215e Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 10 Nov 2024 19:31:10 -0600 Subject: [PATCH 04/19] Revert "add connExists()" This reverts commit 5b21e1d89d4f5c72cef55849bfdadb5373394e08. --- .../photonvision/common/networking/NetworkUtils.java | 12 ------------ 1 file changed, 12 deletions(-) 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 cc3a61b90f..a644472ff2 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 @@ -156,18 +156,6 @@ public static NMDeviceInfo getNMinfoForDevName(String devName) { return null; } - public static boolean connExists(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 -g GENERAL.STATE connection show \"" + connName + "\""); - return (shell.getExitCode() == 0); - } catch (Exception e) { - logger.error("Exception from nmcli!"); - } - return false; - } - public static boolean connDoesNotExist(String connName) { var shell = new ShellExec(true, true); try { From 7a90692f6521a4d1139af6491b161f871b78f890 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 10 Nov 2024 19:31:56 -0600 Subject: [PATCH 05/19] remove out-of-place comment --- .../java/org/photonvision/common/networking/NetworkUtils.java | 1 - 1 file changed, 1 deletion(-) 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 a644472ff2..fb05b99ed3 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 @@ -159,7 +159,6 @@ public static NMDeviceInfo getNMinfoForDevName(String devName) { 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 -g GENERAL.STATE connection show \"" + connName + "\""); return (shell.getExitCode() == 10); } catch (Exception e) { From 0bf9d270cbb8288e642355a33cfcb0319d885c5b Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Sun, 10 Nov 2024 20:00:13 -0600 Subject: [PATCH 06/19] wpiformat --- .../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 11e4eb1c78..0ffe6f63dc 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 @@ -198,7 +198,7 @@ private void setConnectionDHCP(NetworkConfig config) { shell.executeBashCommand( modDHCPCommand .replace("${connection}", connName) - ); + ); // activate it logger.info("Activating the DHCP connection " + connName ); shell.executeBashCommand("nmcli connection up \"${connection}\"".replace("${connection}", connName), false); From e817bbbe9a118e09c3d620cb8a42cfbb23685479 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Wed, 1 Jan 2025 15:42:18 -0600 Subject: [PATCH 07/19] refactor and improve interface monitoring --- .../common/networking/NetworkManager.java | 173 +++++++++--------- .../common/networking/NetworkingCommands.java | 46 +++++ 2 files changed, 129 insertions(+), 90 deletions(-) create mode 100644 photon-core/src/main/java/org/photonvision/common/networking/NetworkingCommands.java 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 227ad68f09..9fa4c673c4 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,11 +17,11 @@ package org.photonvision.common.networking; +import java.net.NetworkInterface; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.NoSuchElementException; - import org.photonvision.common.configuration.ConfigManager; import org.photonvision.common.configuration.NetworkConfig; import org.photonvision.common.dataflow.DataChangeDestination; @@ -81,7 +81,9 @@ public void initialize(boolean shouldManage) { try { // 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); + logger.warn( + "The configured interface doesn't match any available interface. Applying configuration to " + + iFace.devName); // update NetworkConfig with found interface config.networkManagerIface = iFace.devName; ConfigManager.getInstance().requestSave(); @@ -96,7 +98,13 @@ public void initialize(boolean shouldManage) { } } - logger.info("Setting " + config.connectionType + " with team " + config.ntServerAddress + " on " + config.networkManagerIface); + logger.info( + "Setting " + + config.connectionType + + " with team " + + config.ntServerAddress + + " on " + + config.networkManagerIface); // always set hostname (unless it's blank) if (!config.hostname.isBlank()) { @@ -129,15 +137,14 @@ private void setHostname(String hostname) { 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 +"<"); + 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); + setHostnameRetCode = shell.executeBashCommand("hostnamectl set-hostname " + hostname); // Add to /etc/hosts var addHostRetCode = @@ -168,40 +175,22 @@ private void setHostname(String hostname) { private void setConnectionDHCP(NetworkConfig config) { String connName = "dhcp-" + config.networkManagerIface; - String addDHCPcommand = """ - nmcli conn add - con-name "${connection}" - ifname "${interface}" - type ethernet - """.replaceAll("[\\n]", " "); - - String modDHCPCommand = """ - nmcli conn modify "${connection}" - autoconnect yes - ipv4.method auto - ipv4.dhcp-timeout infinity - ipv4.link-local enabled - ipv6.method disabled - """.replaceAll("[\\n]", " "); - var shell = new ShellExec(); try { if (NetworkUtils.connDoesNotExist(connName)) { - logger.info("Updating the DHCP connection " + connName ); + logger.info("Creating DHCP connection " + connName); shell.executeBashCommand( - addDHCPcommand - .replace("${connection}", connName) - .replace("${interface}", config.networkManagerIface) - ); + NetworkingCommands.addConnectionCommand + .replace("${connection}", connName) + .replace("${interface}", config.networkManagerIface)); } - logger.info("Updating the DHCP connection " + connName ); + logger.info("Updating the DHCP connection " + connName); shell.executeBashCommand( - modDHCPCommand - .replace("${connection}", connName) - ); + NetworkingCommands.modDHCPCommand.replace("${connection}", connName)); // activate it - logger.info("Activating the DHCP connection " + connName ); - shell.executeBashCommand("nmcli connection up \"${connection}\"".replace("${connection}", connName), false); + logger.info("Activating DHCP connection " + connName); + shell.executeBashCommand( + "nmcli connection up \"${connection}\"".replace("${connection}", connName), false); } catch (Exception e) { logger.error("Exception while setting DHCP!", e); } @@ -209,20 +198,6 @@ private void setConnectionDHCP(NetworkConfig config) { 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?"); @@ -231,34 +206,30 @@ private void setConnectionStatic(NetworkConfig config) { // guess at the gateway from the staticIp String[] parts = config.staticIp.split("\\."); - parts[parts.length-1] = "1"; + parts[parts.length - 1] = "1"; String gateway = String.join(".", parts); var shell = new ShellExec(); try { if (NetworkUtils.connDoesNotExist(connName)) { // create connection - logger.info("Creating the Static connection " + connName ); + logger.info("Creating 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) - ); + NetworkingCommands.addConnectionCommand + .replace("${connection}", connName) + .replace("${interface}", config.networkManagerIface)); } + // modify it in case the static IP address is different + logger.info("Updating the Static connection " + connName); + shell.executeBashCommand( + NetworkingCommands.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 \"${connection}\"".replace("${connection}", connName), false); + logger.info("Activating the Static connection " + connName); + shell.executeBashCommand( + "nmcli connection up \"${connection}\"".replace("${connection}", connName), false); } catch (Exception e) { logger.error("Error while setting static IP!", e); } @@ -267,6 +238,13 @@ private void setConnectionStatic(NetworkConfig config) { // Detects changes in the carrier and reinitializes after re-connect private void monitorDevice(String devName, int millisInterval) { String taskName = "deviceStatus-" + devName; + NetworkInterface iFace; + try { + iFace = NetworkInterface.getByName(devName); + } catch (Exception e) { + logger.error("Could not get NetworkInterface " + devName, e); + return; + } if (TimedTaskManager.getInstance().taskActive(taskName)) { // task is already running return; @@ -276,31 +254,46 @@ private void monitorDevice(String devName, int millisInterval) { logger.error("Can't find " + path + ", so can't monitor " + devName); return; } - logger.debug("Watching network interface at path: " + path); - var last = new Object() {boolean carrier = true; boolean exceptionLogged = false;}; - Runnable task = () -> { - try { - boolean carrier = Files.readString(path).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!"); + var last = + new Object() { + boolean carrier = true; + boolean exceptionLogged = false; + String addresses = ""; + }; + Runnable task = + () -> { + try { + boolean carrier = Files.readString(path).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!"); + } + } + if (iFace.isUp()) { + String tmpAddresses = ""; + tmpAddresses = iFace.getInterfaceAddresses().toString(); + if (last.addresses != tmpAddresses) { + // addresses have changed, log the difference + last.addresses = tmpAddresses; + logger.info("Interface " + devName + " has address(es): " + last.addresses); + } + } + last.carrier = carrier; + last.exceptionLogged = false; + } catch (Exception e) { + if (!last.exceptionLogged) { + // Log the exception only once, but keep trying + logger.error("Could not check network status for " + devName, e); + last.exceptionLogged = true; + } } - } - last.carrier = carrier; - last.exceptionLogged = false; - } catch (Exception e) { - if (!last.exceptionLogged) { - // Log the exception only once, but keep trying - logger.error("Could not check network status for " + devName, e); - last.exceptionLogged = true; - } - } - }; + }; TimedTaskManager.getInstance().addTask(taskName, task, millisInterval); + logger.debug("Watching network interface at path: " + path); } } diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkingCommands.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkingCommands.java new file mode 100644 index 0000000000..d985f98d2c --- /dev/null +++ b/photon-core/src/main/java/org/photonvision/common/networking/NetworkingCommands.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.photonvision.common.networking; + +// using a separate class because Spotless fails on text blocks +// spotless:off +public class NetworkingCommands { + public static final String addConnectionCommand = """ + nmcli connection add + con-name "${connection}" + ifname "${interface}" + type ethernet + """.replaceAll("[\\n]", " "); + + public static final String modStaticCommand = """ + nmcli connection modify ${connection} + autoconnect yes + ipv4.method manual + ipv6.method disabled + ipv4.addresses ${ipaddr}/8 + ipv4.gateway ${gateway} + """.replaceAll("[\\n]", " "); + + public static final String modDHCPCommand = """ + nmcli connection modify "${connection}" + autoconnect yes + ipv4.method auto + ipv6.method disabled + """.replaceAll("[\\n]", " "); +} +//spotless:on From 1e919a3bf264b68eae37187f48522c2e5bbc9e7a Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Wed, 1 Jan 2025 15:43:45 -0600 Subject: [PATCH 08/19] This class isn't used and shadows Java's NetworkInterface class --- .../common/networking/NetworkInterface.java | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 photon-core/src/main/java/org/photonvision/common/networking/NetworkInterface.java diff --git a/photon-core/src/main/java/org/photonvision/common/networking/NetworkInterface.java b/photon-core/src/main/java/org/photonvision/common/networking/NetworkInterface.java deleted file mode 100644 index 8130dccb62..0000000000 --- a/photon-core/src/main/java/org/photonvision/common/networking/NetworkInterface.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) Photon Vision. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.photonvision.common.networking; - -import java.net.InterfaceAddress; -import org.photonvision.common.logging.LogGroup; -import org.photonvision.common.logging.Logger; - -@SuppressWarnings("WeakerAccess") -public class NetworkInterface { - private static final Logger logger = new Logger(NetworkInterface.class, LogGroup.General); - - public final String name; - public final String displayName; - public final String ipAddress; - public final String netmask; - public final String broadcast; - - public NetworkInterface(java.net.NetworkInterface inetface, InterfaceAddress ifaceAddress) { - name = inetface.getName(); - displayName = inetface.getDisplayName(); - - var inetAddress = ifaceAddress.getAddress(); - ipAddress = inetAddress.getHostAddress(); - netmask = getIPv4LocalNetMask(ifaceAddress); - - // TODO: (low) hack to "get" gateway, this is gross and bad, pls fix - var splitIPAddr = ipAddress.split("\\."); - splitIPAddr[3] = "1"; - splitIPAddr[3] = "255"; - broadcast = String.join(".", splitIPAddr); - } - - private static String getIPv4LocalNetMask(InterfaceAddress interfaceAddress) { - var netPrefix = interfaceAddress.getNetworkPrefixLength(); - try { - // Since this is for IPv4, it's 32 bits, so set the sign value of - // the int to "negative"... - int shiftby = (1 << 31); - // For the number of bits of the prefix -1 (we already set the sign bit) - for (int i = netPrefix - 1; i > 0; i--) { - // Shift the sign right... Java makes the sign bit sticky on a shift... - // So no need to "set it back up"... - shiftby = (shiftby >> 1); - } - // Transform the resulting value in xxx.xxx.xxx.xxx format, like if - /// it was a standard address... - // Return the address thus created... - return ((shiftby >> 24) & 255) - + "." - + ((shiftby >> 16) & 255) - + "." - + ((shiftby >> 8) & 255) - + "." - + (shiftby & 255); - // return InetAddress.getByName(maskString); - } catch (Exception e) { - logger.error("Failed to get netmask!", e); - } - // Something went wrong here... - return null; - } -} From ab8aabebd7e1edf50ff9baf3ffa157ac57ae213c Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Wed, 1 Jan 2025 15:45:55 -0600 Subject: [PATCH 09/19] Methods to find the IP address --- .../common/networking/NetworkUtils.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) 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 fb05b99ed3..677174a7e1 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 @@ -18,7 +18,10 @@ package org.photonvision.common.networking; import java.io.IOException; +import java.net.Inet4Address; +import java.net.NetworkInterface; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -166,4 +169,37 @@ public static boolean connDoesNotExist(String connName) { } return false; } + + public String getIPAddresses() { + String addresses = ""; + try { + var interfaceList = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (var iFace : interfaceList) { + if (iFace.isLoopback()) continue; + for (var address : iFace.getInterfaceAddresses()) { + if (address.getAddress() instanceof Inet4Address) { + addresses.concat(address.getAddress().toString()); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return addresses; + } + + public String getIPAddresses(String iFaceName) { + String addresses = ""; + try { + var iFace = NetworkInterface.getByName(iFaceName); + for (var address : iFace.getInterfaceAddresses()) { + if (address.getAddress() instanceof Inet4Address) { + addresses.concat(address.getAddress().toString()); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return addresses; + } } From e9479cbc89ff8af005c2389851005590a7997952 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Wed, 1 Jan 2025 16:22:57 -0600 Subject: [PATCH 10/19] also monitor the active connection --- .../common/networking/NetworkManager.java | 10 ++++++++++ .../photonvision/common/networking/NetworkUtils.java | 11 +++++++++++ 2 files changed, 21 insertions(+) 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 9fa4c673c4..3afa4a471b 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 @@ -21,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; import java.util.NoSuchElementException; import org.photonvision.common.configuration.ConfigManager; import org.photonvision.common.configuration.NetworkConfig; @@ -38,6 +39,7 @@ public class NetworkManager { private static final Logger logger = new Logger(NetworkManager.class, LogGroup.General); + private HashMap activeConnections = new HashMap(); private NetworkManager() {} @@ -72,6 +74,8 @@ public void initialize(boolean shouldManage) { // Start tasks to monitor the network interface(s) var ethernetDevices = NetworkUtils.getAllWiredInterfaces(); for (NMDeviceInfo deviceInfo : ethernetDevices) { + activeConnections.put( + deviceInfo.devName, NetworkUtils.getActiveConnection(deviceInfo.devName)); monitorDevice(deviceInfo.devName, 5000); } @@ -191,6 +195,7 @@ private void setConnectionDHCP(NetworkConfig config) { logger.info("Activating DHCP connection " + connName); shell.executeBashCommand( "nmcli connection up \"${connection}\"".replace("${connection}", connName), false); + activeConnections.put(config.networkManagerIface, connName); } catch (Exception e) { logger.error("Exception while setting DHCP!", e); } @@ -230,6 +235,7 @@ private void setConnectionStatic(NetworkConfig config) { logger.info("Activating the Static connection " + connName); shell.executeBashCommand( "nmcli connection up \"${connection}\"".replace("${connection}", connName), false); + activeConnections.put(config.networkManagerIface, connName); } catch (Exception e) { logger.error("Error while setting static IP!", e); } @@ -281,6 +287,10 @@ private void monitorDevice(String devName, int millisInterval) { last.addresses = tmpAddresses; logger.info("Interface " + devName + " has address(es): " + last.addresses); } + var conn = NetworkUtils.getActiveConnection(devName); + if (activeConnections.get(devName) != conn) { + logger.warn("Unexpected connection " + conn + "active on " + devName); + } } last.carrier = carrier; last.exceptionLogged = false; 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 677174a7e1..74fe80bce3 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 @@ -159,6 +159,17 @@ public static NMDeviceInfo getNMinfoForDevName(String devName) { return null; } + public static String getActiveConnection(String devName) { + var shell = new ShellExec(true, true); + try { + shell.executeBashCommand("nmcli -g GENERAL.CONNECTION dev show \"" + devName + "\""); + return shell.getOutput().strip(); + } catch (Exception e) { + logger.error("Exception from nmcli!"); + } + return ""; + } + public static boolean connDoesNotExist(String connName) { var shell = new ShellExec(true, true); try { From ae8206233cbcb2f8ee31a2796dd1ec070cb35f27 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Wed, 1 Jan 2025 17:29:31 -0600 Subject: [PATCH 11/19] don't log every time we check network --- .../common/networking/NetworkManager.java | 23 +++++++++------- .../common/networking/NetworkUtils.java | 6 +++-- .../photonvision/common/util/ShellExec.java | 26 +++++++++++++++++++ 3 files changed, 43 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 3afa4a471b..934fdd894e 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 @@ -244,13 +244,6 @@ private void setConnectionStatic(NetworkConfig config) { // Detects changes in the carrier and reinitializes after re-connect private void monitorDevice(String devName, int millisInterval) { String taskName = "deviceStatus-" + devName; - NetworkInterface iFace; - try { - iFace = NetworkInterface.getByName(devName); - } catch (Exception e) { - logger.error("Could not get NetworkInterface " + devName, e); - return; - } if (TimedTaskManager.getInstance().taskActive(taskName)) { // task is already running return; @@ -279,17 +272,27 @@ private void monitorDevice(String devName, int millisInterval) { logger.warn("Interface " + devName + " is disconnected, check Ethernet!"); } } + NetworkInterface iFace; + iFace = NetworkInterface.getByName(devName); if (iFace.isUp()) { String tmpAddresses = ""; tmpAddresses = iFace.getInterfaceAddresses().toString(); - if (last.addresses != tmpAddresses) { + if (!last.addresses.equals(tmpAddresses)) { // addresses have changed, log the difference last.addresses = tmpAddresses; logger.info("Interface " + devName + " has address(es): " + last.addresses); } var conn = NetworkUtils.getActiveConnection(devName); - if (activeConnections.get(devName) != conn) { - logger.warn("Unexpected connection " + conn + "active on " + devName); + if (!conn.equals(activeConnections.get(devName))) { + logger.warn( + "Unexpected connection " + + conn + + " active on " + + devName + + ". Expected " + + activeConnections.get(devName)); + logger.info("Reinitializing"); + reinitialize(); } } last.carrier = carrier; 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 74fe80bce3..77c4a8e31e 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 @@ -162,7 +162,8 @@ public static NMDeviceInfo getNMinfoForDevName(String devName) { public static String getActiveConnection(String devName) { var shell = new ShellExec(true, true); try { - shell.executeBashCommand("nmcli -g GENERAL.CONNECTION dev show \"" + devName + "\""); + shell.executeBashCommandNoLog( + "nmcli -g GENERAL.CONNECTION dev show \"" + devName + "\"", true); return shell.getOutput().strip(); } catch (Exception e) { logger.error("Exception from nmcli!"); @@ -173,7 +174,8 @@ public static String getActiveConnection(String devName) { public static boolean connDoesNotExist(String connName) { var shell = new ShellExec(true, true); try { - shell.executeBashCommand("nmcli -g GENERAL.STATE connection show \"" + connName + "\""); + shell.executeBashCommandNoLog( + "nmcli -g GENERAL.STATE connection show \"" + connName + "\"", true); return (shell.getExitCode() == 10); } catch (Exception e) { logger.error("Exception from nmcli!"); diff --git a/photon-core/src/main/java/org/photonvision/common/util/ShellExec.java b/photon-core/src/main/java/org/photonvision/common/util/ShellExec.java index c8433ec158..89dc38e24b 100644 --- a/photon-core/src/main/java/org/photonvision/common/util/ShellExec.java +++ b/photon-core/src/main/java/org/photonvision/common/util/ShellExec.java @@ -72,6 +72,32 @@ public int executeBashCommand(String command, boolean wait) throws IOException { return retcode; } + /** + * Execute a bash command. We can handle complex bash commands including multiple executions (; | + * and ||), quotes, expansions ($), escapes (\), e.g.: "cd /abc/def; mv ghi 'older ghi '$(whoami)" + * This runs the commands with the default logging. + * + * @param command Bash command to execute + * @return true if bash got started, but your command may have failed. + */ + public int executeBashCommandNoLog(String command, boolean wait) throws IOException { + boolean success = false; + Runtime r = Runtime.getRuntime(); + // Use bash -c, so we can handle things like multi commands separated by ; and + // things like quotes, $, |, and \. My tests show that command comes as + // one argument to bash, so we do not need to quote it to make it one thing. + // Also, exec may object if it does not have an executable file as the first thing, + // so having bash here makes it happy provided bash is installed and in path. + String[] commands = {"bash", "-c", command}; + + Process process = r.exec(commands); + + // Consume streams, older jvm's had a memory leak if streams were not read, + // some other jvm+OS combinations may block unless streams are consumed. + int retcode = doProcess(wait, process); + return retcode; + } + /** * Execute a command in current folder, and wait for process to end * From 11cc601849955e82ec0915c8bf35abff46ace030 Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Wed, 1 Jan 2025 19:32:52 -0600 Subject: [PATCH 12/19] show current IP address on settings page --- .../src/components/settings/MetricsCard.vue | 4 ++ .../stores/settings/GeneralSettingsStore.ts | 7 +++- photon-client/src/types/SettingTypes.ts | 1 + .../hardware/metrics/MetricsManager.java | 11 ++++++ .../common/networking/NetworkUtils.java | 37 ++++++------------- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/photon-client/src/components/settings/MetricsCard.vue b/photon-client/src/components/settings/MetricsCard.vue index 53e14d22d5..4bc9eb2b5d 100644 --- a/photon-client/src/components/settings/MetricsCard.vue +++ b/photon-client/src/components/settings/MetricsCard.vue @@ -22,6 +22,10 @@ const generalMetrics = computed(() => [ header: "Platform", value: useSettingsStore().general.hardwarePlatform || "Unknown" }, + { + header: "IP Address", + value: useSettingsStore().metrics.ipAddress || "Unknown" + }, { header: "GPU Acceleration", value: useSettingsStore().general.gpuAcceleration || "Unknown" diff --git a/photon-client/src/stores/settings/GeneralSettingsStore.ts b/photon-client/src/stores/settings/GeneralSettingsStore.ts index ae0a41461c..530f1eb7f0 100644 --- a/photon-client/src/stores/settings/GeneralSettingsStore.ts +++ b/photon-client/src/stores/settings/GeneralSettingsStore.ts @@ -10,6 +10,7 @@ import { NetworkConnectionType } from "@/types/SettingTypes"; import { useStateStore } from "@/stores/StateStore"; import axios from "axios"; import type { WebsocketSettingsUpdate } from "@/types/WebsocketDataTypes"; +import { isNullOrUndefined } from "node:util"; interface GeneralSettingsStore { general: GeneralSettings; @@ -62,7 +63,8 @@ export const useSettingsStore = defineStore("settings", { cpuThr: undefined, cpuUptime: undefined, diskUtilPct: undefined, - npuUsage: undefined + npuUsage: undefined, + ipAddress: undefined }, currentFieldLayout: { field: { @@ -95,7 +97,8 @@ export const useSettingsStore = defineStore("settings", { cpuThr: data.cpuThr || undefined, cpuUptime: data.cpuUptime || undefined, diskUtilPct: data.diskUtilPct || undefined, - npuUsage: data.npuUsage || undefined + npuUsage: data.npuUsage || undefined, + ipAddress: data.ipAddress || undefined }; }, updateGeneralSettingsFromWebsocket(data: WebsocketSettingsUpdate) { diff --git a/photon-client/src/types/SettingTypes.ts b/photon-client/src/types/SettingTypes.ts index 9b7e7372d2..ef3a81d94a 100644 --- a/photon-client/src/types/SettingTypes.ts +++ b/photon-client/src/types/SettingTypes.ts @@ -23,6 +23,7 @@ export interface MetricData { cpuUptime?: string; diskUtilPct?: string; npuUsage?: string; + ipAddress?: string; } export enum NetworkConnectionType { diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsManager.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsManager.java index c51729a086..60e857a091 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsManager.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsManager.java @@ -20,6 +20,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.HashMap; +import org.photonvision.common.configuration.ConfigManager; import org.photonvision.common.configuration.HardwareConfig; import org.photonvision.common.dataflow.DataChangeService; import org.photonvision.common.dataflow.events.OutgoingUIEvent; @@ -31,6 +32,7 @@ import org.photonvision.common.hardware.metrics.cmds.RK3588Cmds; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; +import org.photonvision.common.networking.NetworkUtils; import org.photonvision.common.util.ShellExec; public class MetricsManager { @@ -119,6 +121,14 @@ public String getUsedRam() { return safeExecute(cmds.ramUsageCommand); } + public String getIpAddress() { + String dev = ConfigManager.getInstance().getConfig().getNetworkConfig().networkManagerIface; + logger.info("Requesting IP addresses for " + dev); + String addr = NetworkUtils.getIPAddresses(dev); + logger.info("Got value " + addr); + return addr; + } + public void publishMetrics() { logger.debug("Publishing Metrics..."); final var metrics = new HashMap(); @@ -133,6 +143,7 @@ public void publishMetrics() { metrics.put("gpuMemUtil", this.getMallocedMemory()); metrics.put("diskUtilPct", this.getUsedDiskPct()); metrics.put("npuUsage", this.getNpuUsage()); + metrics.put("ipAddress", this.getIpAddress()); DataChangeService.getInstance().publishEvent(OutgoingUIEvent.wrappedOf("metrics", metrics)); } 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 77c4a8e31e..13e50db28a 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 @@ -18,10 +18,8 @@ package org.photonvision.common.networking; import java.io.IOException; -import java.net.Inet4Address; import java.net.NetworkInterface; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -183,36 +181,23 @@ public static boolean connDoesNotExist(String connName) { return false; } - public String getIPAddresses() { - String addresses = ""; - try { - var interfaceList = Collections.list(NetworkInterface.getNetworkInterfaces()); - for (var iFace : interfaceList) { - if (iFace.isLoopback()) continue; - for (var address : iFace.getInterfaceAddresses()) { - if (address.getAddress() instanceof Inet4Address) { - addresses.concat(address.getAddress().toString()); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - return addresses; - } - - public String getIPAddresses(String iFaceName) { - String addresses = ""; + public static String getIPAddresses(String iFaceName) { + List addresses = new ArrayList(); try { var iFace = NetworkInterface.getByName(iFaceName); - for (var address : iFace.getInterfaceAddresses()) { - if (address.getAddress() instanceof Inet4Address) { - addresses.concat(address.getAddress().toString()); + for (var addr : iFace.getInterfaceAddresses()) { + var addrStr = addr.getAddress().toString(); + if (addrStr.startsWith("/")) { + addrStr = addrStr.substring(1); } + addrStr = addrStr + "/" + addr.getNetworkPrefixLength(); + addresses.add(addrStr); } + // addresses = iFace.inetAddresses().map(a -> + // a.getAddress().toString()).collect(Collectors.joining(",")); } catch (Exception e) { e.printStackTrace(); } - return addresses; + return String.join(", ", addresses); } } From 96d4887aad5f802ae54cde76fe4e8bc2b85ee4df Mon Sep 17 00:00:00 2001 From: Craig Schardt Date: Wed, 1 Jan 2025 19:54:07 -0600 Subject: [PATCH 13/19] linting --- .../cameras/CameraCalibrationCard.vue | 2 +- .../stores/settings/GeneralSettingsStore.ts | 1 - .../src/views/CameraMatchingView.vue | 32 +++++++++---------- photon-client/src/views/DashboardView.vue | 4 +-- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/photon-client/src/components/cameras/CameraCalibrationCard.vue b/photon-client/src/components/cameras/CameraCalibrationCard.vue index 47271283f2..b3f033cc9b 100644 --- a/photon-client/src/components/cameras/CameraCalibrationCard.vue +++ b/photon-client/src/components/cameras/CameraCalibrationCard.vue @@ -242,7 +242,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => { {{ value.verticalFOV !== undefined ? value.verticalFOV.toFixed(2) + "°" : "-" }} {{ value.diagonalFOV !== undefined ? value.diagonalFOV.toFixed(2) + "°" : "-" }} -