001/* 002 * Copyright (C) Photon Vision. 003 * 004 * This program is free software: you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License as published by 006 * the Free Software Foundation, either version 3 of the License, or 007 * (at your option) any later version. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program. If not, see <https://www.gnu.org/licenses/>. 016 */ 017 018package org.photonvision.common.networking; 019 020import java.io.IOException; 021import java.net.NetworkInterface; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026import java.util.stream.Collectors; 027import org.photonvision.common.hardware.Platform; 028import org.photonvision.common.logging.LogGroup; 029import org.photonvision.common.logging.Logger; 030import org.photonvision.common.util.ShellExec; 031 032public class NetworkUtils { 033 private static final Logger logger = new Logger(NetworkUtils.class, LogGroup.General); 034 035 public enum NMType { 036 NMTYPE_ETHERNET("ethernet"), 037 NMTYPE_WIFI("wifi"), 038 NMTYPE_UNKNOWN(""); 039 040 NMType(String id) { 041 identifier = id; 042 } 043 044 private final String identifier; 045 046 public static NMType typeForString(String s) { 047 for (var t : NMType.values()) { 048 if (t.identifier.equals(s)) { 049 return t; 050 } 051 } 052 return NMTYPE_UNKNOWN; 053 } 054 } 055 056 public static class NMDeviceInfo { 057 public NMDeviceInfo(String c, String d, String type) { 058 connName = c; 059 devName = d; 060 nmType = NMType.typeForString(type); 061 } 062 063 public final String connName; // Human-readable name used by "nmcli con" 064 public final String devName; // underlying device, used by dhclient 065 public final NMType nmType; 066 067 @Override 068 public String toString() { 069 return "NMDeviceInfo [connName=" 070 + connName 071 + ", devName=" 072 + devName 073 + ", nmType=" 074 + nmType 075 + "]"; 076 } 077 } 078 079 private static List<NMDeviceInfo> allInterfaces = new ArrayList<>(); 080 private static long lastReadTimestamp = 0; 081 082 public static List<NMDeviceInfo> getAllInterfaces() { 083 long now = System.currentTimeMillis(); 084 if (now - lastReadTimestamp < 5000) return allInterfaces; 085 else lastReadTimestamp = now; 086 087 var ret = new ArrayList<NMDeviceInfo>(); 088 089 if (!Platform.isLinux()) { 090 // Can only determine interface name on Linux, give up 091 return ret; 092 } 093 094 try { 095 var shell = new ShellExec(true, false); 096 shell.executeBashCommand( 097 "nmcli -t -f GENERAL.CONNECTION,GENERAL.DEVICE,GENERAL.TYPE device show"); 098 String out = shell.getOutput(); 099 if (out == null) { 100 return new ArrayList<>(); 101 } 102 Pattern pattern = 103 Pattern.compile("GENERAL.CONNECTION:(.*)\nGENERAL.DEVICE:(.*)\nGENERAL.TYPE:(.*)"); 104 Matcher matcher = pattern.matcher(out); 105 while (matcher.find()) { 106 if (!matcher.group(2).equals("lo")) { 107 // only include non-loopback devices 108 ret.add(new NMDeviceInfo(matcher.group(1), matcher.group(2), matcher.group(3))); 109 } 110 } 111 } catch (IOException e) { 112 logger.error("Could not get active network interfaces!", e); 113 } 114 115 logger.debug("Found network interfaces: " + ret); 116 117 allInterfaces = ret; 118 return ret; 119 } 120 121 public static List<NMDeviceInfo> getAllActiveInterfaces() { 122 // Seems like if an interface exists but isn't actually connected, the connection name will be 123 // an empty string. Check here and only return connections with non-empty names 124 return getAllInterfaces().stream() 125 .filter(it -> !it.connName.trim().isEmpty()) 126 .collect(Collectors.toList()); 127 } 128 129 public static List<NMDeviceInfo> getAllWiredInterfaces() { 130 return getAllInterfaces().stream() 131 .filter(it -> it.nmType.equals(NMType.NMTYPE_ETHERNET)) 132 .collect(Collectors.toList()); 133 } 134 135 public static List<NMDeviceInfo> getAllActiveWiredInterfaces() { 136 return getAllWiredInterfaces().stream() 137 .filter(it -> !it.connName.isBlank()) 138 .collect(Collectors.toList()); 139 } 140 141 public static NMDeviceInfo getNMinfoForConnName(String connName) { 142 for (NMDeviceInfo info : getAllActiveInterfaces()) { 143 if (info.connName.equals(connName)) { 144 return info; 145 } 146 } 147 return null; 148 } 149 150 public static NMDeviceInfo getNMinfoForDevName(String devName) { 151 for (NMDeviceInfo info : getAllActiveInterfaces()) { 152 if (info.devName.equals(devName)) { 153 return info; 154 } 155 } 156 logger.warn("Could not find a match for network device " + devName); 157 return null; 158 } 159 160 public static String getActiveConnection(String devName) { 161 var shell = new ShellExec(true, true); 162 try { 163 shell.executeBashCommand( 164 "nmcli -g GENERAL.CONNECTION dev show \"" + devName + "\"", true, false); 165 return shell.getOutput().strip(); 166 } catch (Exception e) { 167 logger.error("Exception from nmcli!"); 168 } 169 return ""; 170 } 171 172 public static boolean connDoesNotExist(String connName) { 173 var shell = new ShellExec(true, true); 174 try { 175 shell.executeBashCommand( 176 "nmcli -g GENERAL.STATE connection show \"" + connName + "\"", true, false); 177 return (shell.getExitCode() == 10); 178 } catch (Exception e) { 179 logger.error("Exception from nmcli!"); 180 } 181 return false; 182 } 183 184 public static String getIPAddresses(String iFaceName) { 185 if (iFaceName == null || iFaceName.isBlank()) { 186 return ""; 187 } 188 List<String> addresses = new ArrayList<String>(); 189 try { 190 var iFace = NetworkInterface.getByName(iFaceName); 191 if (iFace != null && iFace.isUp()) { 192 for (var addr : iFace.getInterfaceAddresses()) { 193 var addrStr = addr.getAddress().toString(); 194 if (addrStr.startsWith("/")) { 195 addrStr = addrStr.substring(1); 196 } 197 addrStr = addrStr + "/" + addr.getNetworkPrefixLength(); 198 addresses.add(addrStr); 199 } 200 } 201 } catch (Exception e) { 202 e.printStackTrace(); 203 } 204 return String.join(", ", addresses); 205 } 206}