Network/src/dorkbox/network/other/NetworkUtil.java

1361 lines
50 KiB
Java

package dorkbox.network.other;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dorkbox.util.OS;
/**
* Network Utilities. MAC, IP, NameSpace, etc
*/
@SuppressWarnings("unused")
public
class NetworkUtil {
private static final Logger logger = LoggerFactory.getLogger(NetworkUtil.class.getSimpleName());
public static final String LOCALHOST = "localhost";
public static final String LOOPBACK = "127.0.0.1";
public static final String WILDCARD_IPV4;
static {
if (OS.isWindows()) {
// silly windows can't work with 0.0.0.0, BUT we can't use loopback because we might need to reach this machine from a different host
String IP = LOOPBACK;
try {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("1.1.1.1", 80));
IP = socket.getLocalAddress().getHostAddress();
} catch (Exception e) {
logger.error("Unable to determine outbound traffic local address. Using loopback instead.", e);
}
WILDCARD_IPV4 = IP;
} else {
// NetworkUtil.EXTERNAL_IPV4
WILDCARD_IPV4 = "0.0.0.0";
}
}
public static
class MAC {
public static final int MAC_ADDRESS_LENGTH = 6;
private static final Random random = new Random();
private static Set<String> fakeMacsInUse = new HashSet<>();
private static final Pattern MAC_ADDRESS_PATTERN = Pattern.compile("^([a-fA-F0-9]{2}[:\\\\.-]?){5}[a-fA-F0-9]{2}$");
public
enum MacDelimiter {
COLON(":"), PERIOD("."), SPACE(" ");
private final String delimiter;
MacDelimiter(String delimiter) {
this.delimiter = delimiter;
}
String getDelimiter() {
return delimiter;
}
}
static {
synchronized (fakeMacsInUse) {
// String mac = getMacAddress(Args_Interfaces.BRIDGE_IP.getAsString(), null);
// fakeMacsInUse.add(mac);
}
}
public static
String getMacAddress(String ip, Logger logger) {
try {
byte[] mac = getMacAddressByte(ip, logger);
if (mac == null) {
if (logger != null) {
logger.error("Unable to get MAC address for IP '{}'", ip);
}
return "";
}
StringBuilder s = new StringBuilder(18);
for (byte b : mac) {
if (s.length() > 0) {
s.append(':');
}
s.append(String.format("%02x", b));
}
return s.toString();
} catch (Exception e) {
if (logger != null) {
logger.error("Unable to get MAC address for IP '{}'", ip, e);
}
else {
e.printStackTrace();
}
}
if (logger != null) {
logger.error("Unable to get MAC address for IP '{}'", ip);
}
return "";
}
public static
byte[] getMacAddressByte(String ip, Logger logger) {
try {
InetAddress addr = InetAddress.getByName(ip);
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(addr);
if (networkInterface == null) {
if (logger != null) {
logger.error("Unable to get MAC address for IP '{}'", ip);
}
return null;
}
byte[] mac = networkInterface.getHardwareAddress();
if (mac == null) {
if (logger != null) {
logger.error("Unable to get MAC address for IP '{}'", ip);
}
return null;
}
return mac;
} catch (Exception e) {
if (logger != null) {
logger.error("Unable to get MAC address for IP '{}'", ip, e);
}
else {
e.printStackTrace();
}
}
return null;
}
/**
* will also make sure that this mac doesn't already exist
*
* @return a unique MAC that can be used for VPN devices + setup. This is a LOWERCASE string!
*/
public static
String getFakeMac() {
synchronized (fakeMacsInUse) {
String mac = fakeVpnMac();
// gotta make sure it doesn't already exist
while (fakeMacsInUse.contains(mac)) {
mac = fakeVpnMac();
}
fakeMacsInUse.add(mac);
return mac;
}
}
/**
* will also make sure that this mac doesn't already exist
*
* @return a unique MAC that can be used for VPN devices + setup. This is a LOWERCASE string!
*/
public static
String getFakeDockerMac() {
synchronized (fakeMacsInUse) {
String mac = fakeDockerMac();
// gotta make sure it doesn't already exist
while (fakeMacsInUse.contains(mac)) {
mac = fakeDockerMac();
}
fakeMacsInUse.add(mac);
return mac;
}
}
/**
* Removes a mac that was in use, to free it's use later on
*/
public static
void freeFakeMac(String fakeMac) {
synchronized (fakeMacsInUse) {
fakeMacsInUse.remove(fakeMac);
}
}
/**
* Removes a mac that was in use, to free it's use later on
*/
public static
void freeFakeMac(Collection<String> fakeMacs) {
synchronized (fakeMacsInUse) {
fakeMacsInUse.removeAll(fakeMacs);
}
}
/**
* http://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
*
* @return a mac that is safe to use for fake interfaces. THIS IS LOWERCASE!
*/
public static
String fakeVpnMac() {
String vpnID = randHex();
while (vpnID.equals("d0")) {
// this is what is used for docker. NEVER assign it to a VPN mac!!
vpnID = randHex();
}
return "02:" + vpnID + ":" + randHex() + ":" + randHex() + ":" + randHex() + ":" + randHex();
}
/**
* http://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
*
* @return a mac that is safe to use for fake interfaces. THIS IS LOWERCASE!
*/
public static
String fakeDockerMac() {
return "02:" + "d0" + ":" + randHex() + ":" + randHex() + ":" + randHex() + ":" + randHex();
}
public static
String randHex() {
final int i = random.nextInt(255);
if (i < 16) {
return "0" + Integer.toHexString(i);
}
else {
return Integer.toHexString(i);
}
}
/**
* will also make sure that this mac doesn't already exist
*
* @return a unique MAC that can be used for VPN devices + setup
*/
public static
String getFakeVpnMac(String vpnKeyDirForExistingVpnMacs) {
synchronized (fakeMacsInUse) {
String mac = fakeVpnMac();
// gotta make sure it doesn't already exist
while (fakeMacsInUse.contains(mac) || new File(vpnKeyDirForExistingVpnMacs, mac + ".crt").exists()) {
mac = fakeVpnMac();
}
fakeMacsInUse.add(mac);
return mac;
}
}
/**
* Converts a long into a properly formatted lower-case string
*/
public static
String toStringLowerCase(final long mac) {
// we only use the right-most 6 bytes.
// byte[] macBytes = ByteUtils.fromLong(mac);
// mac should have 6 bytes.
final StringBuilder buf = new StringBuilder();
// for (int i = 2; i < macBytes.length; i++) {
// final byte b = macBytes[i];
// if (buf.length() != 0) {
// buf.append(':');
// }
// if (b >= 0 && b < 16) {
// buf.append('0');
// }
// buf.append(Integer.toHexString((b < 0) ? b + 256 : b));
//
// }
return buf.toString();
}
public static
void writeStringLowerCase(final long mac, final Writer writer) throws Exception {
// we only use the right-most 6 bytes.
// byte[] macBytes = ByteUtils.fromLong(mac);
//
// // mac should have 6 bytes.
// int bytesWritten = 0;
// for (int i = 2; i < macBytes.length; i++) {
// final byte b = macBytes[i];
// if (bytesWritten != 0) {
// writer.write(':');
// bytesWritten++;
// }
// if (b >= 0 && b < 16) {
// writer.write('0');
// }
// writer.write(Integer.toHexString((b < 0) ? b + 256 : b));
// bytesWritten += 2;
// }
}
public static
String toStringLowerCase(final byte[] mac) {
// mac should have 6 bytes.
final StringBuilder buf = new StringBuilder();
for (byte b : mac) {
if (buf.length() != 0) {
buf.append(':');
}
if (b >= 0 && b < 16) {
buf.append('0');
}
buf.append(Integer.toHexString((b < 0) ? b + 256 : b));
}
return buf.toString();
}
public static
String toStringUpperCase(final byte[] mac) {
final StringBuilder buf = new StringBuilder();
for (byte b : mac) {
if (buf.length() != 0) {
buf.append(':');
}
if (b >= 0 && b < 16) {
buf.append('0');
}
buf.append(Integer.toHexString((b < 0) ? b + 256 : b)
.toUpperCase());
}
return buf.toString();
}
public static
byte[] toBytes(final String mac) {
String s = mac.replaceAll(":", "");
return new BigInteger(s, 16).toByteArray();
}
public static
long toLong(final byte[] mac) {
return ((long)mac[5] & 0xff)
+ (((long)mac[4] & 0xff) << 8)
+ (((long)mac[3] & 0xff) << 16)
+ (((long)mac[2] & 0xff) << 24)
+ (((long)mac[1] & 0xff) << 32)
+ (((long)mac[0] & 0xff) << 40);
}
public static
long toLong(final String mac) {
return toLong(mac, MacDelimiter.COLON);
}
public static
long toLong(final String mac, MacDelimiter delimiter) {
byte[] addressInBytes = new byte[MAC_ADDRESS_LENGTH];
try {
String[] elements;
if (delimiter != null) {
elements = mac.split(delimiter.getDelimiter());
}
else {
elements = new String[MAC_ADDRESS_LENGTH];
int index = 0;
int substringPos = 0;
while (index < MAC_ADDRESS_LENGTH) {
elements[index] = mac.substring(substringPos, substringPos+2);
index++;
substringPos += 2;
}
}
for (int i = 0; i < MAC_ADDRESS_LENGTH; i++) {
String element = elements[i];
addressInBytes[i] = (byte) Integer.parseInt(element, 16);
}
} catch (Exception e) {
logger.error("Error parsing MAC address '{}'", mac, e);
}
return toLong(addressInBytes);
}
public static
boolean isValidMacAddress(String macAsString) {
if(macAsString == null) {
return false;
}
if(macAsString.isEmpty()) {
return false;
}
// Check whether mac is separated by a colon, period, space, or nothing at all.
// Must standardize to a colon.
String normalizedMac = macAsString;
if(normalizedMac.split(":").length != 6) {
// Does not already use colons, must modify the mac to use colons.
if (normalizedMac.contains(".")) {
// Period notation
normalizedMac = normalizedMac.replace(".", ":");
}
else if (normalizedMac.contains(" ")) {
// Space notation
normalizedMac = normalizedMac.replace(" ", ":");
}
else {
// No delimiter, manually add colons.
normalizedMac = "";
int index = 0;
int substringPos = 0;
// We ensure that the substring function will not exceed the length of the given string if the string is not at least
//(MAC_ADDRESS_LENGTH * 2) characters long.
while (index < MAC_ADDRESS_LENGTH && macAsString.length() >= substringPos+2) {
// Reconstruct the string, adding colons.
if(index != MAC_ADDRESS_LENGTH-1) {
normalizedMac = normalizedMac.concat(macAsString.substring(substringPos, substringPos+2).concat(":"));
}
else {
normalizedMac = normalizedMac.concat(macAsString.substring(substringPos, substringPos+2));
}
index++;
substringPos += 2;
}
}
}
Matcher matcher = MAC_ADDRESS_PATTERN.matcher(normalizedMac);
return matcher.matches();
}
/**
* Returns the type of delmiter based on how a mac address is constructed. Returns null if no delimiter.
* @return a MacDelimiter if there is one, or null if the mac address is one long string.
*/
public static
MacDelimiter getMacDelimiter(String macAsString) {
if (macAsString.contains(":")) {
return MacDelimiter.COLON;
}
else if (macAsString.contains(".")) {
return MacDelimiter.PERIOD;
}
else if (macAsString.contains(" ")) {
return MacDelimiter.SPACE;
}
return null;
}
public static
void assign(final String interfaceName, final String macAddress) {
// ShellExecutor.Companion.run("/sbin/ifconfig", interfaceName + " hw ether " + macAddress);
}
}
public static
class NameSpace {
private static Map<String, Map<String, String>> nameSpaceToIifToIp = new HashMap<>();
public static
class Route {
public static
void flush(String nameSpace) {
run(nameSpace, "/sbin/ip route flush cache");
}
}
public static
void add(String nameSpace) {
// ShellExecutor.Companion.run("/sbin/ip", "netns add " + nameSpace);
}
public static
void delete(String nameSpace) {
// ShellExecutor.Companion.run("/sbin/ip", "netns del " + nameSpace);
}
public static
void dhcpStart(String nameSpace, String id, String interfaceName) {
dhcpStop(nameSpace, id, interfaceName);
String dhcpPidFile = "/var/run/dhclient-" + id + ".pid";
run(nameSpace, "/sbin/dhclient", "-pf", dhcpPidFile, interfaceName);
}
public static
void dhcpStop(final String nameSpace, final String id, final String interfaceName) {
String dhcpPidFile = "/var/run/dhclient-" + id + ".pid";
// close the dhclient if it was already running (based on pid file), and delete the pid file
run(nameSpace, "/sbin/dhclient", "-r -pf", dhcpPidFile, interfaceName);
// short break
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new File(dhcpPidFile).delete();
}
public static
void run(String nameSpace, String... args) {
List<String> newArgs = new ArrayList<>(args.length + 3);
newArgs.add("netns");
newArgs.add("exec");
newArgs.add(nameSpace);
newArgs.addAll(Arrays.asList(args));
// ShellExecutor.Companion.run("/sbin/ip", newArgs.toArray(new String[0]));
}
/**
* On client disconnect, it will get the IP address for the interface from the cache, instead of from `ifconfig` (since the
* interface
* is down at this point)
*/
public static
String getIpFromIf(final String nameSpace, final String interfaceName, boolean isOnClientConnect) {
if (isOnClientConnect) {
String ifaceInfo = runWithOutput(nameSpace, "/sbin/ifconfig", interfaceName);
String str = "inet addr:";
int index = ifaceInfo.indexOf(str);
if (index > -1) {
index += str.length();
String possibleAddr = ifaceInfo.substring(index, ifaceInfo.indexOf(" ", index));
logger.debug("Found on '{}' possible addr '{}' : ADD", interfaceName, possibleAddr);
synchronized (nameSpaceToIifToIp) {
Map<String, String> ifToIp = nameSpaceToIifToIp.get(nameSpace);
//noinspection Java8MapApi
if (ifToIp == null) {
ifToIp = new HashMap<>();
nameSpaceToIifToIp.put(nameSpace, ifToIp);
}
ifToIp.put(interfaceName, possibleAddr);
}
return possibleAddr;
}
}
else {
String possibleAddr = "";
synchronized (nameSpaceToIifToIp) {
Map<String, String> ifToIp = nameSpaceToIifToIp.get(nameSpace);
if (ifToIp != null) {
possibleAddr = ifToIp.remove(interfaceName);
}
}
logger.debug("Found on '{}' possible addr '{}' : REMOVE", interfaceName, possibleAddr);
if (possibleAddr != null) {
return possibleAddr;
}
}
return "";
}
public static
String runWithOutput(String nameSpace, String... args) {
// List<String> newArgs = new ArrayList<>(args.length + 3);
// newArgs.add("netns");
// newArgs.add("exec");
// newArgs.add(nameSpace);
// newArgs.addAll(Arrays.asList(args));
//
// return ShellExecutor.Companion.runWithOutput("/sbin/ip", newArgs.toArray(new String[0]));
throw new RuntimeException("NOT IMPL.");
}
public static
void addLoopback(final String nameSpace) {
run(nameSpace, "/sbin/ip link set dev lo up");
run(nameSpace, "/sbin/ip addr add 127.0.0.1 dev lo");
}
}
public static
class VirtualEth {
public static
void add(String host, String guest) {
// ShellExecutor.Companion.run("/sbin/ip", "link add name " + host + " type veth peer name " + guest);
}
public static
void delete(String host) {
// ShellExecutor.Companion.run("/sbin/ip", "link del " + host);
}
public static
void assignNameSpace(final String nameSpace, final String guest) {
// ShellExecutor.Companion.run("/sbin/ip", "link set " + guest + " netns " + nameSpace);
}
}
public static
class IP {
private static final Map<String, String> ifToIp = new HashMap<>();
public static
void dhcpStart(String nameSpace, String id, String interfaceName) {
dhcpStop(nameSpace, id, interfaceName);
String dhcpPidFile = "/var/run/dhclient-" + id + ".pid";
// ShellExecutor.Companion.run("/sbin/dhclient", "-pf", dhcpPidFile, interfaceName);
}
public static
void dhcpStop(final String nameSpace, final String id, final String interfaceName) {
String dhcpPidFile = "/var/run/dhclient-" + id + ".pid";
// close the dhclient if it was already running (based on pid file), and delete the pid file
// ShellExecutor.Companion.run("/sbin/dhclient", "-r -pf", dhcpPidFile, interfaceName);
// short break
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new File(dhcpPidFile).delete();
}
/**
* On disconnect, it will get the IP address for the interface from the cache, instead of from `ifconfig` (since the interface
* is down at this point)
*/
@SuppressWarnings("Duplicates")
public static
String getIpFromIf(final String interfaceName, boolean isOnClientConnect) {
if (isOnClientConnect) {
// String ifaceInfo = ShellExecutor.Companion.runWithOutput("/sbin/ifconfig", interfaceName);
//
// String str = "inet addr:";
// int index = ifaceInfo.indexOf(str);
// if (index > -1) {
// index += str.length();
// String possibleAddr = ifaceInfo.substring(index, ifaceInfo.indexOf(" ", index));
//
// logger.debug("Found on '{}' possible addr '{}' : ADD", interfaceName, possibleAddr);
// synchronized (ifToIp) {
// ifToIp.put(interfaceName, possibleAddr);
// }
// return possibleAddr;
// }
}
else {
String possibleAddr;
synchronized (ifToIp) {
possibleAddr = ifToIp.remove(interfaceName);
}
logger.debug("Found on '{}' possible addr '{}' : REMOVE", interfaceName, possibleAddr);
if (possibleAddr != null) {
return possibleAddr;
}
}
return "";
}
public static
void down(final String interfaceName) {
// ShellExecutor.Companion.run("/sbin/ip", "link set dev", interfaceName, "down");
}
public static
void up(final String interfaceName) {
// ShellExecutor.Companion.run("/sbin/ip", "link set dev", interfaceName, "up");
}
public static
void setMac(final String interfaceName, String interfaceMac) {
// ShellExecutor.Companion.run("/sbin/ip", "link set dev", interfaceName, "address", interfaceMac);
}
public static
void assignCIDR(final String interfaceName, final int cidr) {
// ShellExecutor.Companion.run("/sbin/ifconfig", interfaceName + " 0.0.0.0/" + cidr + " up");
}
public static
void addLoopback() {
// ShellExecutor.Companion.run("/sbin/ip", "link set dev lo up");
// ShellExecutor.Companion.run("/sbin/ip", "addr add 127.0.0.1 dev lo");
}
public static
int toInt(final byte[] ipBytes) {
int val = 0;
for (int i = 0; i < ipBytes.length; i++) {
val <<= 8;
val |= ipBytes[i] & 0xFF;
}
return val;
}
public static
String toString(final int ipAddress) {
StringBuilder ipString = new StringBuilder(15);
ipString.append(((ipAddress >> 24) & 0x000000FF));
ipString.append('.');
ipString.append(((ipAddress >> 16) & 0x000000FF));
ipString.append('.');
ipString.append(((ipAddress >> 8) & 0x000000FF));
ipString.append('.');
ipString.append((ipAddress & 0x000000FF));
return ipString.toString();
}
public static
void writeString(final int ipAddress, final Writer writer) throws Exception {
writer.write(Integer.toString((ipAddress >> 24) & 0x000000FF));
writer.write('.');
writer.write(Integer.toString((ipAddress >> 16) & 0x000000FF));
writer.write('.');
writer.write(Integer.toString((ipAddress >> 8) & 0x000000FF));
writer.write('.');
writer.write(Integer.toString(ipAddress & 0x000000FF));
}
public static
String toString(final long ipAddress) {
StringBuilder ipString = new StringBuilder(15);
ipString.append(((ipAddress >> 24) & 0x000000FF));
ipString.append('.');
ipString.append(((ipAddress >> 16) & 0x000000FF));
ipString.append('.');
ipString.append(((ipAddress >> 8) & 0x000000FF));
ipString.append('.');
ipString.append((ipAddress & 0x000000FF));
return ipString.toString();
}
public static
byte[] toBytes(int bytes) {
return new byte[] {(byte) ((bytes >>> 24) & 0xFF),
(byte) ((bytes >>> 16) & 0xFF),
(byte) ((bytes >>> 8) & 0xFF),
(byte) ((bytes) & 0xFF)};
}
public static
byte[] toBytes(final String ipAsString) {
byte[] address = new byte[4];
long tmp = 0;
int current = 0;
int len = ipAsString.length();
for (int i = 0; i < len; i++) {
char c = ipAsString.charAt(i);
if (c == '.') {
address[current++] = (byte) (tmp & 0xff);
tmp = 0;
} else {
int digit = Character.digit(c, 10);
tmp *= 10;
tmp += digit;
}
}
return address;
}
public static
int toInt(final String ipAsString) {
int address = 0;
try {
byte[] bytes = toBytes(ipAsString);
address |= bytes[0] << 24;
address |= bytes[1] << 16;
address |= bytes[2] << 8;
address |= bytes[3];
} catch (Exception e) {
logger.error("Error processing IP address '{}'", ipAsString, e);
}
return address;
}
private static final int[] CIDR2MASK = new int[] {0x00000000, 0x80000000, 0xC0000000, 0xE0000000, 0xF0000000, 0xF8000000, 0xFC000000,
0xFE000000, 0xFF000000, 0xFF800000, 0xFFC00000, 0xFFE00000, 0xFFF00000, 0xFFF80000,
0xFFFC0000, 0xFFFE0000, 0xFFFF0000, 0xFFFF8000, 0xFFFFC000, 0xFFFFE000, 0xFFFFF000,
0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00, 0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0, 0xFFFFFFE0,
0xFFFFFFF0, 0xFFFFFFF8, 0xFFFFFFFC, 0xFFFFFFFE, 0xFFFFFFFF};
public static
List<String> range2Cidr(final String startIp, final String endIp) {
long start = NetworkUtil.IP.toInt(startIp);
long end = NetworkUtil.IP.toInt(endIp);
List<String> pairs = new ArrayList<>();
while (end >= start) {
byte maxsize = (byte) 32;
while (maxsize > 0) {
long mask = CIDR2MASK[maxsize - 1];
long maskedBase = start & mask;
if (maskedBase != start) {
break;
}
maxsize--;
}
double x = Math.log(end - start + 1) / Math.log(2);
//noinspection NumericCastThatLosesPrecision
byte maxDiff = (byte) (32 - Math.floor(x));
if (maxsize < maxDiff) {
maxsize = maxDiff;
}
String ip = NetworkUtil.IP.toString(start);
pairs.add(ip + "/" + maxsize);
start += (long) Math.pow(2, 32 - maxsize);
}
return pairs;
}
/*
^ # START OF STRING
(?=\d+\.\d+\.\d+\.\d+(\/d+)?$) # LOOKAHEAD, require this format: number.number.number.number (optional /number) END OF STRING
(?: # Start non-capture group (number 0-255 + optional dot)
(?: # Start non-capture group (number 0-255)
25[0-5] # 250-255
| # OR
2[0-4][0-9] # 200-249
| # OR
1[0-9]{2} # 100-199
| # OR
[1-9][0-9] # 10-99
| # OR
[0-9] # 0-9
) # End non-capture group
\.? # Optional dot (enforced in correct positions by lookahead)
){4} # End non-capture group (number + optional dot), repeat 4 times
(?: # Start OPTIONAL non-capture group (/ + number 0-32)
\/ # /
[0-9] # 0-9
| # OR
1[0-9] # 10-19
| # OR
2[0-9] # 20-29
| # OR
3[0-2] # 30-32
)? # End OPTIONAL non-capture group (/ + number)
$ # END OF STRING
*/
// these MUST stay private. use `isValidIP` for things instead
private static final String IP_ADDRESS_ONLY_PATTERN_STRING =
"^(?=\\d+\\.\\d+\\.\\d+\\.\\d+$)" +
"(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\\.?){4}$";
private static final String IP_ADDRESS_CIDR_PATTERN_STRING =
"^(?=\\d+\\.\\d+\\.\\d+\\.\\d+(\\/\\d+)?$)" +
"(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\\.?){4}" +
"(?:\\/(?:[0-9]|1[0-9]|2[0-9]|3[0-2]))?$";
private static final Pattern IP_ADDRESS_ONLY_PATTERN = Pattern.compile(IP_ADDRESS_ONLY_PATTERN_STRING, Pattern.CASE_INSENSITIVE);
private static final Pattern IP_ADDRESS_CIDR_PATTERN = Pattern.compile(IP_ADDRESS_CIDR_PATTERN_STRING, Pattern.CASE_INSENSITIVE);
/**
* Determine whether a given string is a valid IP address. Accepts 1.2.3.4
*
* @param ipAsString The string that will be checked.
*
* @return return true if the string is a valid IP address, false if it is not.
*/
public static
boolean isValidIp(final String ipAsString) {
if (ipAsString == null || ipAsString.isEmpty()) {
return false;
}
Matcher m = IP_ADDRESS_ONLY_PATTERN.matcher(ipAsString);
return m.matches();
}
/**
* Determine whether a given string is a valid CIDR IP address. Accepts 1.2.3.4 and 1.2.3.4/24
*
* @param ipAsString The string that will be checked.
*
* @return return true if the string is a valid IP address, false if it is not.
*/
public static
boolean isValidCidrIp(final String ipAsString) {
if (ipAsString == null || ipAsString.isEmpty()) {
return false;
}
Matcher m = IP_ADDRESS_CIDR_PATTERN.matcher(ipAsString);
return m.matches();
}
/* Mask to convert unsigned int to a long (i.e. keep 32 bits) */
private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL;
/**
* Check if the IP address is in the range of a specific IP/CIDR
*
* a prefix of 0 will ALWAYS return true
*
* @param address the address to check
* @param networkAddress the network address that will have the other address checked against
* @param networkPrefix 0-32 the network prefix (subnet) to use for the network address
*
* @return true if it is in range
*/
public static boolean isInRange(int address, int networkAddress, int networkPrefix) {
// System.err.println(" ip: " + IP.toString(address));
// System.err.println(" networkAddress: " + IP.toString(networkAddress));
// System.err.println(" networkSubnetPrefix: " + networkPrefix);
if (networkPrefix == 0) {
// a prefix of 0 means it is always true (even though the broadcast address is '-1'). So we short-cut it here
return true;
}
//noinspection PointlessBitwiseExpression
int netmask = ~((1 << (32 - networkPrefix)) - 1);
// Calculate base network address
long network = (networkAddress & netmask) & UNSIGNED_INT_MASK;
// System.err.println(" network " + IP.toString(network));
// Calculate broadcast address
long broadcast = network | ~(netmask) & UNSIGNED_INT_MASK;
// System.err.println(" broadcast " + IP.toString(broadcast));
long addressLong = address & UNSIGNED_INT_MASK;
return network <= addressLong && addressLong <= broadcast;
}
public static void main(String args[]) {
// try {
// String secret = "e8b7b40e031300000000da247441226a";
// String ivString = "987185c4436764b6e27a72f200da2201.e";
// String cipherText = "U2FsdGVkX1+dXqCQz3g/Fltl5Jls5ayO0TynHaGM72PHXSxgBf5F5cwljMJsl4jf";
//
// byte[] cipherData = Base64.decode(cipherText);
// byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
//
// byte[] key = secret.getBytes();
// byte[] iv = ivString.getBytes();
//
// byte[] decryptAes = CryptoAES.decrypt(new GCMBlockCipher(new AESFastEngine()), key, iv, cipherData, logger);
//
// System.out.println(new String(decryptAes, StandardCharsets.UTF_8));
// } catch (Exception e) {
// e.printStackTrace();
// }
// System.out.println(IP.toInt("10.106.6.119"));
System.out.println(MAC.toLong("c8:9c:dc:3d:86:b4"));
// System.err.println("in range true " + IP.isInRange(IP.toInt("10.10.10.5"), IP.toInt("10.10.10.10"), 24));
// System.err.println("in range true " + IP.isInRange(IP.toInt("10.0.0.5"), IP.toInt("10.10.10.10"), 8));
// System.err.println("in range false " + IP.isInRange(IP.toInt("11.0.0.5"), IP.toInt("10.10.10.10"), 8));
// System.err.println("in range true " + IP.isInRange(IP.toInt("11.0.0.5"), IP.toInt("10.10.10.10"), 1));
// System.err.println("in range true " + IP.isInRange(IP.toInt("11.0.0.5"), IP.toInt("10.10.10.10"), 0));
// System.err.println("in range false " + IP.isInRange(IP.toInt("11.0.0.5"), IP.toInt("10.10.10.10"), 32));
// System.err.println("in range true " + IP.isInRange(IP.toInt("10.10.10.10"), IP.toInt("10.10.10.10"), 32));
// System.err.println("in range true " + IP.isInRange(IP.toInt("10.10.10.10"), IP.toInt("10.10.10.10"), 31));
// System.err.println("in range true " + IP.isInRange(IP.toInt("10.10.10.10"), IP.toInt("10.10.10.10"), 30));
// System.err.println("in range true " + IP.isInRange(IP.toInt("192.168.42.14"), IP.toInt("192.168.0.0"), 16));
// System.err.println("in range true " + IP.isInRange(IP.toInt("192.168.0.0"), IP.toInt("192.168.0.0"), 16));
}
}
public static
class ARP {
// Now setup ARP Proxy for this interface (so ARP requests are answered correctly)
public static
void proxyAdd(final String interfaceName) {
File file = new File("/proc/sys/net/ipv4/conf/" + interfaceName + "/proxy_arp");
// String read = FileUtil.INSTANCE.read(file);
// if (read == null || !read.contains("1")) {
// FileUtil.INSTANCE.append(file, "1");
// }
}
public static
void proxyDel(final String interfaceName) {
File file = new File("/proc/sys/net/ipv4/conf/" + interfaceName + "/proxy_arp");
file.delete();
}
public static
void add(final String interfaceName, final String targetIpAddress, final String targetMacAddress) {
// have to make sure that the host interface will answer ARP for the "target" ip (normally, it will not for veth interfaces)
// ShellExecutor.Companion.run("/usr/sbin/arp", "-i", interfaceName, "-s", targetIpAddress, targetMacAddress);
}
}
public static
class Route {
public static
void flush() {
// ShellExecutor.Companion.run("/sbin/ip", "route flush cache");
}
public static
void add(final String targetIpAndCidr, final String hostIP, final String hostInterface) {
// ShellExecutor.Companion.run("/sbin/ip", "route add", targetIpAndCidr + " via " + hostIP + " dev " + hostInterface);
}
}
public static
class IfConfig {
public static
void assignMac(final String interfaceName, final String interfaceMac) {
// ShellExecutor.Companion.run("/sbin/ifconfig", interfaceName + " hw ether " + interfaceMac);
}
public static
void up(final String interfaceName, final String interfaceCIDR) {
up(interfaceName, "0.0.0.0", interfaceCIDR);
}
public static
void up(final String interfaceName, final String interfaceIP, final String interfaceCIDR) {
// ShellExecutor.Companion.run("/sbin/ifconfig", interfaceName + " " + interfaceIP + "/" + interfaceCIDR + " up");
}
}
public static
class IpRoute {
private static final StringBuilder reservedTable = new StringBuilder(2048);
private static final Map<Integer, String> tableNames = new HashMap<>(256);
static {
// reservedTable.append("#").append(FileUtil.INSTANCE.getLINE_SEPARATOR());
// reservedTable.append("# reserved values").append(FileUtil.INSTANCE.getLINE_SEPARATOR());
// reservedTable.append("#").append(FileUtil.INSTANCE.getLINE_SEPARATOR());
// reservedTable.append("255 local").append(FileUtil.INSTANCE.getLINE_SEPARATOR());
// reservedTable.append("254 main").append(FileUtil.INSTANCE.getLINE_SEPARATOR());
// reservedTable.append("253 default").append(FileUtil.INSTANCE.getLINE_SEPARATOR());
// reservedTable.append("0 unspec").append(FileUtil.INSTANCE.getLINE_SEPARATOR());
// reservedTable.append("#").append(FileUtil.INSTANCE.getLINE_SEPARATOR());
}
public static
void addRtTables(Map<Integer, String> tableNames) {
for (Map.Entry<Integer, String> entry : tableNames.entrySet()) {
Integer tableNumber = entry.getKey();
String tableName = entry.getValue();
if (tableNumber == 0 || tableNumber == 253 || tableNumber == 254 || tableNumber == 255) {
logger.error("Trying to add table with same number as reserved value. Skipping.");
continue;
}
if (!IpRoute.tableNames.containsKey(tableNumber)) {
IpRoute.tableNames.put(tableNumber, tableName);
}
else {
if (!IpRoute.tableNames.get(tableNumber).equals(tableName)) {
logger.error("Trying to add table with the same number as another table. Skipping");
}
}
}
final StringBuilder table = new StringBuilder(2048);
for (Map.Entry<Integer, String> entry : IpRoute.tableNames.entrySet()) {
Integer tableNumber = entry.getKey();
String tableName = entry.getValue();
// table.append(tableNumber).append(" ").append(tableName).append(FileUtil.INSTANCE.getLINE_SEPARATOR());
}
File policyRouteFile = new File("/etc/iproute2/rt_tables").getAbsoluteFile();
if (!policyRouteFile.canRead()) {
// SmtpProcessor.INSTANCE.sendErrorEmail("Policy Routing Error", "Unable to initialize policy routing tables. Something is SERIOUSLY wrong, aborting startup!");
throw new RuntimeException("Unable to initialize policy routing tables. Something is SERIOUSLY wrong, aborting startup!");
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(policyRouteFile))) {
writer.write(reservedTable.toString());
writer.write(table.toString());
writer.flush();
} catch (IOException e) {
logger.error("Error saving routing table file: {}", policyRouteFile, e);
}
}
}
public static
class DNS {
public static
void setDNSServers(final String dnsServersString) {
// if (Args.INSTANCE.isLocalMode()) {
// return;
// }
//
// String[] dnsServers = dnsServersString.split(",");
//
// File dnsFile = new File("/etc/resolvconf/resolv.conf.d/head");
//
// if (!dnsFile.canRead()) {
// SmtpProcessor.INSTANCE.sendErrorEmail("DNS File Error", "Unable to initialize dns server file. Some is SERIOUSLY wrong.");
// throw new RuntimeException("Unable to initialize dns server file. Something is SERIOUSLY wrong");
// }
//
// try (BufferedWriter writer = new BufferedWriter(new FileWriter(dnsFile))) {
// writer.write("# File location: /etc/resolvconf/resolv.conf.d/head\n");
// writer.write("# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)\n");
//
// for (String dns : dnsServers) {
// writer.write("nameserver " + dns + '\n');
// }
//
// writer.flush();
// }
// catch (IOException e) {
// logger.error("Error saving dns server file: {}", dnsServers, e);
// }
//
// ShellExecutor.Companion.run("resolvconf", "-u");
}
}
public static
byte[] findFreeSubnet24() {
logger.info("Scanning for available cidr...");
// have to find a free cidr
// start with 10.x.x.x /24 and just march through starting at 0 -> 200 for each, ping to see if there is a gateway (should be)
// and go from there.
// on linux, PING has the setuid bit set - so it runs "as root". isReachable() requires either java to have the setuid bit set
// (ie: sudo setcap cap_net_raw=ep /usr/lib/jvm/jdk/bin/java) or it requires to be run as root. We run as root in production, so it
// works.
byte[] ip = new byte[] {10, 0, 0, 0};
short subnet_24_counter = 0;
while (true) {
ip[3]++;
if (ip[3] > 255) {
ip[3] = 1;
ip[2]++;
subnet_24_counter = 0;
}
if (ip[2] > 255) {
ip[2] = 0;
ip[1]++;
}
if (ip[1] > 255) {
logger.error("Exhausted all ip searches. FATAL ERROR.");
return null;
}
try {
InetAddress address = InetAddress.getByAddress(ip);
boolean reachable = address.isReachable(100);
if (!reachable) {
subnet_24_counter++;
}
if (subnet_24_counter == 250) {
// this means that we tried all /24 IPs, and ALL of them came back an "non-responsive". 100ms timeout is OK, because
// we are on a LAN, that should have MORE than one IP in the cidr, and it should be fairly responsive (ie: <10ms ping)
// we found an empty cidr
ip[3] = 1;
return ip;
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
/**
* Scans for existing IP addresses on the network.
*
* @param startingIp the IP address to start scanning at
* @param numberOfHosts the number of hosts to scan for. A /28 is 14 hosts : 2^(32-28) - 2 = 14
*
* @return true if no hosts were reachable (pingable)
*/
public static
boolean scanHosts(final String startingIp, final int numberOfHosts) {
logger.info("Scanning {} hosts, starting at IP {}.", numberOfHosts, startingIp);
String[] split = startingIp.split("\\.");
byte A = (byte) Integer.parseInt(split[0]);
byte B = (byte) Integer.parseInt(split[1]);
byte C = (byte) Integer.parseInt(split[2]);
byte D = (byte) Integer.parseInt(split[3]);
byte[] ip = new byte[] {A, B, C, D};
int counter = numberOfHosts;
while (counter >= 0) {
counter--;
ip[3]++;
if (ip[3] > 255) {
ip[3] = 1;
ip[2]++;
}
if (ip[2] > 255) {
ip[2] = 0;
ip[1]++;
}
if (ip[1] > 255) {
logger.error("Exhausted all ip searches. FATAL ERROR.");
return false;
}
try {
InetAddress address = InetAddress.getByAddress(ip);
boolean reachable = address.isReachable(100);
if (reachable) {
logger.error("IP address {} is already reachable on the network. Unable to continue.", address.getHostAddress());
return false;
}
} catch (IOException e) {
logger.error("Error pinging the IP address", e);
return false;
}
}
return true;
}
/**
* @param cidr the CIDR notation, ie: 24, 16, etc. That we want to convert into a netmask
*
* @return the netmask (as an int), or if there were errors, the default /24 netmask
*/
public static
int getCidrAsNetmask(final Integer cidr) {
// SubnetUtils.SubnetInfo info = new SubnetUtils("1.1.1.1" + "/" + cidr).getInfo();
// java.lang.reflect.Method method;
// try {
// method = info.getClass()
// .getDeclaredMethod("netmask", (Class<?>[])null);
// method.setAccessible(true);
// // try to load the actual netmask. This is harder than it should be....
// return (int) method.invoke(info);
// } catch (Exception e) {
// logger.error("Error getting netmask info.", e);
// }
return 0xFFFFFF00; // 255.255.255.0
}
public static
int getCidrFromMask(String mask) {
switch (mask) {
case "255.255.255.255": return 32;
case "255.255.255.254": return 31;
case "255.255.255.252": return 30;
case "255.255.255.248": return 29;
case "255.255.255.240": return 28;
case "255.255.255.224": return 27;
case "255.255.255.192": return 26;
case "255.255.255.128": return 25;
case "255.255.255.0": return 24;
case "255.255.254.0": return 23;
case "255.255.252.0": return 22;
case "255.255.248.0": return 21;
case "255.255.240.0": return 20;
case "255.255.224.0": return 19;
case "255.255.192.0": return 18;
case "255.255.128.0": return 17;
case "255.255.0.0": return 16;
case "255.254.0.0": return 15;
case "255.252.0.0": return 14;
case "255.248.0.0": return 13;
case "255.240.0.0": return 12;
case "255.224.0.0": return 11;
case "255.192.0.0": return 10;
case "255.128.0.0": return 9;
case "255.0.0.0": return 8;
case "254.0.0.0": return 7;
case "252.0.0.0": return 6;
case "248.0.0.0": return 5;
case "240.0.0.0": return 4;
case "224.0.0.0": return 3;
case "192.0.0.0": return 2;
case "128.0.0.0": return 1;
default: return 0;
}
}
}