Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend NetUtils for network range scanning #4375

Merged
merged 8 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand All @@ -55,6 +56,7 @@
* @author Stefan Triller - Converted to OSGi service with primary ipv4 conf
* @author Gary Tse - Network address change listener
* @author Tim Roberts - Added primary address change to network address change listener
* @author Leo Siepel - Added methods to improve support for network scanning
*/
@Component(configurationPid = "org.openhab.network", property = { "service.pid=org.openhab.network",
"service.config.description.uri=system:network", "service.config.label=Network Settings",
Expand Down Expand Up @@ -595,4 +597,116 @@ private boolean getConfigParameter(Map<String, Object> parameters, String parame
return defaultValue;
}
}

/**
* For all network interfaces (except loopback) all Ipv4 addresses are returned.
holgerfriedrich marked this conversation as resolved.
Show resolved Hide resolved
* This list can for example, be used to scan the network for available devices.
*
* @return A full list of IP {@link InetAddress} (except network and broadcast)
*/
public static List<InetAddress> getFullRangeOfAddressesToScan() {
List<InetAddress> addressesToScan = List.of();
List<CidrAddress> ipV4InterfaceAddresses = NetUtil.getAllInterfaceAddresses().stream()
.filter(a -> a.getAddress() instanceof Inet4Address).collect(Collectors.toList());

for (CidrAddress i : ipV4InterfaceAddresses) {
addressesToScan.addAll(getAddressesRangeByCidrAddress(i, i.getPrefix()));
}
return addressesToScan;
}

/**
* For the given {@link CidrAddress} all Ipv4 addresses are returned.
* This list can for example, be used to scan the network for available devices.
holgerfriedrich marked this conversation as resolved.
Show resolved Hide resolved
*
* @param iFaceAddress The {@link CidrAddress} of the network interface
* @param maxPrefixLength Control the prefix length of the network (e.g. 24 for class C)
holgerfriedrich marked this conversation as resolved.
Show resolved Hide resolved
* @return A full list of IP {@link InetAddress} (except network and broadcast)
*/
public static List<InetAddress> getAddressesRangeByCidrAddress(CidrAddress iFaceAddress, int maxPrefixLength) {
if (!(iFaceAddress.getAddress() instanceof Inet4Address) || iFaceAddress.getPrefix() < maxPrefixLength) {
return List.of();
}

List<byte[]> addresses = getAddressesInSubnet(iFaceAddress.getAddress().getAddress(), iFaceAddress.getPrefix(),
true);
if (addresses.size() > 2) {
addresses.remove(0); // remove network address
addresses.remove(addresses.size() - 1); // remove broadcast address
}

return addresses.stream().map(m -> {
try {
return InetAddress.getByAddress(m);
} catch (UnknownHostException e) {
return null;
}
}).filter(f -> f != null).sorted((a, b) -> {
byte[] aOct = a.getAddress();
byte[] bOct = b.getAddress();
int r = 0;
for (int i = 0; i < aOct.length && i < bOct.length; i++) {
r = Integer.compare(aOct[i] & 0xff, bOct[i] & 0xff);
if (r != 0) {
return r;
}
}
return r;
}).collect(Collectors.toList());
}

/**
* Recursively calculate each IP address within a subnet
*
* @param address IP address in byte array form (i.e. 127.0.0.1 = 01111111 00000000 00000000 00000001)
* @param maskLength Network mask length (i.e. the number after the forward-slash, '/', in CIDR notation)
* @param scrub Whether or not to scrub the unmasked bits of the address
* @return All possible IP addresses
*
* @author: https://gist.github.com/ssttevee/b0e2b431f4b23d289537
holgerfriedrich marked this conversation as resolved.
Show resolved Hide resolved
*/
private static List<byte[]> getAddressesInSubnet(byte[] address, int maskLength, boolean scrub) {
if (scrub) {
scrubAddress(address, maskLength);
}

if (maskLength >= address.length * 8) {
return Collections.singletonList(address);
}

List<byte[]> addresses = new ArrayList<byte[]>();

int set = maskLength / 8;
int pos = maskLength % 8;

byte[] addressLeft = address.clone();
addressLeft[set] |= 1 << pos;
addresses.addAll(getAddressesInSubnet(addressLeft, maskLength + 1, false));

byte[] addressRight = address.clone();
addressRight[set] &= ~(1 << pos);
addresses.addAll(getAddressesInSubnet(addressRight, maskLength + 1, false));

return addresses;
}

/**
* Set the unmasked bits of the IP address to 0
*
* @param address IP address in byte array form (i.e. 127.0.0.1 = 01111111 00000000 00000000 00000001)
* @param maskLength Network mask length (i.e. the number after the forward-slash, '/', in CIDR notation)
* @return The scrubbed IP address
*
* @author: https://gist.github.com/ssttevee/b0e2b431f4b23d289537
*/
private static byte[] scrubAddress(byte[] address, int maskLength) {
for (int i = 0; i < address.length * 8; i++) {
if (i < maskLength)
continue;

address[i / 8] &= ~(1 << (i % 8));
}

return address;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -116,4 +121,35 @@ public void testBroadcastAddress() {
assertThat(iae.getMessage(), is("IP 'SOME_TEXT' is not a valid IPv4 address"));
}
}

@Test
public void checkValidRangeCount() throws UnknownHostException {
InetAddress testableAddress = InetAddress.getByName("192.168.1.0");
List<InetAddress> addresses = NetUtil
.getAddressesRangeByCidrAddress(new CidrAddress(testableAddress, (short) 24), 24);

assertEquals(254, addresses.size());
assertEquals("192.168.1.1", addresses.get(0).getHostAddress());
assertEquals("192.168.1.254", addresses.get(253).getHostAddress());
}

@Test
public void checkValidLargeRangeCount() throws UnknownHostException {
InetAddress testableAddress = InetAddress.getByName("127.0.0.0");
List<InetAddress> addresses = NetUtil
.getAddressesRangeByCidrAddress(new CidrAddress(testableAddress, (short) 16), 16);

assertEquals(65534, addresses.size());
assertEquals("127.0.0.1", addresses.get(0).getHostAddress());
assertEquals("127.0.255.254", addresses.get(65533).getHostAddress());
}

@Test
public void checkInvalidPrefixLength() throws UnknownHostException {
InetAddress testableAddress = InetAddress.getByName("192.168.1.0");
List<InetAddress> addresses = NetUtil
.getAddressesRangeByCidrAddress(new CidrAddress(testableAddress, (short) 16), 24);

assertEquals(0, addresses.size());
}
}