Skip to content

Commit

Permalink
[network] Add parameter for limiting network interfaces to fix perfor…
Browse files Browse the repository at this point in the history
…mance issues

* Add configuration parameter for limiting network interfaces
* Use last known reachable interface

Signed-off-by: Wouter Born <[email protected]>
  • Loading branch information
wborn committed Dec 29, 2023
1 parent 9906107 commit e9ce357
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.network.internal;

import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

Expand All @@ -29,4 +31,5 @@ public class NetworkHandlerConfiguration {
public Integer retry = 1;
public Integer refreshInterval = 60000;
public Integer timeout = 5000;
public Set<String> networkInterfaceNames = Set.of();
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand Down Expand Up @@ -77,18 +79,19 @@ public class PresenceDetection implements IPRequestReceivedCallback {
/// State variables (cannot be final because of test dependency injections)
ExpiringCacheAsync<PresenceDetectionValue> cache;
private final PresenceDetectionListener updateListener;

private Set<String> networkInterfaceNames = Set.of();
private @Nullable ScheduledFuture<?> refreshJob;
protected @Nullable ExecutorService executorService;
private String dhcpState = "off";
private Integer currentCheck = 0;
int detectionChecks;
private String lastReachableNetworkInterfaceName = "";

public PresenceDetection(final PresenceDetectionListener updateListener, int cacheDeviceStateTimeInMS)
throws IllegalArgumentException {
this.updateListener = updateListener;
cache = new ExpiringCacheAsync<>(cacheDeviceStateTimeInMS, () -> {
performPresenceDetection(false);
});
cache = new ExpiringCacheAsync<>(cacheDeviceStateTimeInMS, () -> performPresenceDetection(false));
}

public @Nullable String getHostname() {
Expand Down Expand Up @@ -137,6 +140,11 @@ public void setHostname(String hostname) {
});
}

public void setNetworkInterfaceNames(Set<String> networkInterfaceNames) {
this.networkInterfaceNames = networkInterfaceNames.stream().filter(Predicate.not(String::isBlank))
.collect(Collectors.toSet());
}

public void setServicePorts(Set<Integer> ports) {
this.tcpPorts = ports;
}
Expand Down Expand Up @@ -293,7 +301,13 @@ public boolean performPresenceDetection(boolean waitForDetectionToFinish) {
detectionChecks += 1;
}
if (arpPingMethod.canProceed) {
interfaceNames = networkUtils.getInterfaceNames();
if (!lastReachableNetworkInterfaceName.isEmpty()) {
interfaceNames = Set.of(lastReachableNetworkInterfaceName);
} else if (!networkInterfaceNames.isEmpty()) {
interfaceNames = networkInterfaceNames;
} else {
interfaceNames = networkUtils.getInterfaceNames();
}
detectionChecks += interfaceNames.size();
}

Expand All @@ -306,7 +320,7 @@ public boolean performPresenceDetection(boolean waitForDetectionToFinish) {

for (Integer tcpPort : tcpPorts) {
executorService.execute(() -> {
Thread.currentThread().setName("presenceDetectionTCP_" + hostname + " " + String.valueOf(tcpPort));
Thread.currentThread().setName("presenceDetectionTCP_" + hostname + " " + tcpPort);
performServicePing(tcpPort);
checkIfFinished();
});
Expand Down Expand Up @@ -493,6 +507,10 @@ protected void performARPping(String interfaceName) {
PresenceDetectionValue v = updateReachableValue(PresenceDetectionType.ARP_PING,
getLatency(o, preferResponseTimeAsLatency));
updateListener.partialDetectionResult(v);
lastReachableNetworkInterfaceName = interfaceName;
} else if (lastReachableNetworkInterfaceName.equals(interfaceName)) {
logger.trace("{} is no longer reachable on network interface: {}", hostname, interfaceName);
lastReachableNetworkInterfaceName = "";
}
});
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand Down Expand Up @@ -55,15 +62,22 @@ public class WakeOnLanPacketSender {

private final @Nullable String hostname;
private final @Nullable Integer port;
private final Set<String> networkInterfaceNames;

private final Consumer<byte[]> magicPacketMacSender;
private final Consumer<byte[]> magicPacketIpSender;

public WakeOnLanPacketSender(String macAddress, @Nullable String hostname, @Nullable Integer port) {
logger.debug("initialized WOL Packet Sender (mac: {}, hostname: {}, port: {}", macAddress, hostname, port);
public WakeOnLanPacketSender(String macAddress, @Nullable String hostname, @Nullable Integer port,
Set<String> networkInterfaceNames) {
if (logger.isDebugEnabled()) {
logger.debug("initialized WOL Packet Sender (mac: {}, hostname: {}, port: {}, networkInterfaceNames: {})",
macAddress, hostname, port, String.join(", ", networkInterfaceNames));
}
this.macAddress = macAddress;
this.hostname = hostname;
this.port = port;
this.networkInterfaceNames = networkInterfaceNames.stream().filter(Predicate.not(String::isBlank))
.collect(Collectors.toSet());
this.magicPacketMacSender = this::sendMagicPacketViaMac;
this.magicPacketIpSender = this::sendMagicPacketViaIp;
}
Expand All @@ -72,10 +86,11 @@ public WakeOnLanPacketSender(String macAddress, @Nullable String hostname, @Null
* Used for testing only.
*/
public WakeOnLanPacketSender(String macAddress) {
logger.debug("initialized WOL Packet Sender (mac: {}", macAddress);
logger.debug("initialized WOL Packet Sender (mac: {})", macAddress);
this.macAddress = macAddress;
this.hostname = null;
this.port = null;
this.networkInterfaceNames = Set.of();
this.magicPacketMacSender = this::sendMagicPacketViaMac;
this.magicPacketIpSender = this::sendMagicPacketViaIp;
}
Expand All @@ -87,6 +102,7 @@ public WakeOnLanPacketSender(String macAddress) {
this.macAddress = macAddress;
this.hostname = null;
this.port = null;
this.networkInterfaceNames = Set.of();
this.magicPacketMacSender = magicPacketSender;
this.magicPacketIpSender = this::sendMagicPacketViaIp;
}
Expand Down Expand Up @@ -172,8 +188,39 @@ private void sendMagicPacketToIp(byte[] magicPacket, DatagramSocket socket, Sock
logger.info("Wake-on-LAN packets sent (IP address: {})", ip);
}

private Set<String> configuredBroadcastAddresses() {
if (networkInterfaceNames.isEmpty()) {
return Set.of();
}

try {
Enumeration<NetworkInterface> niEnum = NetworkInterface.getNetworkInterfaces();
Set<String> broadcastAddresses = new HashSet<>();
while (niEnum.hasMoreElements()) {
NetworkInterface networkInterface = niEnum.nextElement();
if (networkInterfaceNames.contains(networkInterface.getName())) {
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
InetAddress broadcast = interfaceAddress.getBroadcast();
if (broadcast != null) {
broadcastAddresses.add(broadcast.getHostAddress());
}
}
}
}
return broadcastAddresses;
} catch (SocketException e) {
logger.debug("Unable to determine configured broadcast addresses", e);
return Set.of();
}
}

private Stream<InetAddress> broadcastAddressStream() {
Set<String> configuredBroadcastAddresses = configuredBroadcastAddresses();
return NetUtil.getAllBroadcastAddresses().stream().map(address -> {
if (!networkInterfaceNames.isEmpty() && !configuredBroadcastAddresses.contains(address)) {
logger.debug("'{}' is not a broadcast address of the configured network interfaces", address);
return null;
}
try {
return InetAddress.getByName(address);
} catch (UnknownHostException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public static ThingUID createServiceUID(String ip, int tcpPort) {
}

/**
* Submit newly discovered devices. This method is called by the spawned threads in {@link startScan}.
* Submit newly discovered devices. This method is called by the spawned threads in {@link #startScan()}.
*
* @param ip The device IP
* @param tcpPort The TCP port
Expand Down Expand Up @@ -228,7 +228,7 @@ public static ThingUID createPingUID(String ip) {
}

/**
* Submit newly discovered devices. This method is called by the spawned threads in {@link startScan}.
* Submit newly discovered devices. This method is called by the spawned threads in {@link #startScan()}.
*
* @param ip The device IP
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ void initialize(PresenceDetection presenceDetection) {

this.presenceDetection = presenceDetection;
presenceDetection.setHostname(handlerConfiguration.hostname);
presenceDetection.setNetworkInterfaceNames(handlerConfiguration.networkInterfaceNames);
presenceDetection.setPreferResponseTimeAsLatency(configuration.preferResponseTimeAsLatency);

if (isTCPServiceDevice) {
Expand All @@ -199,7 +200,7 @@ void initialize(PresenceDetection presenceDetection) {
presenceDetection.setTimeout(handlerConfiguration.timeout.intValue());

wakeOnLanPacketSender = new WakeOnLanPacketSender(handlerConfiguration.macAddress,
handlerConfiguration.hostname, handlerConfiguration.port);
handlerConfiguration.hostname, handlerConfiguration.port, handlerConfiguration.networkInterfaceNames);

updateStatus(ThingStatus.ONLINE);
presenceDetection.startAutomaticRefresh(scheduler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@
<default>5000</default>
<advanced>true</advanced>
</parameter>

<parameter name="networkInterfaceNames" type="text" required="false" multiple="true">
<label>Network Interface Names</label>
<description>The network interface names used for communicating with the device. Limiting the network interfaces
reduces the load when arping and Wake-on-LAN are used. All interfaces are used when left empty.</description>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Arrays;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand Down Expand Up @@ -117,7 +118,7 @@ private void sendWOLTest(@Nullable String hostname, @Nullable Integer port)
}

try {
WakeOnLanPacketSender sender = new WakeOnLanPacketSender("6f70656e4841", hostname, port);
WakeOnLanPacketSender sender = new WakeOnLanPacketSender("6f70656e4841", hostname, port, Set.of());
sender.sendWakeOnLanPacketViaIp();

// This Test is only applicable for IP Requests
Expand Down

0 comments on commit e9ce357

Please sign in to comment.