[network] Make icmp ping and arp ping optional by presence thing (#18083)

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
pull/18090/head
Mark Herwege 2025-01-11 11:28:56 +01:00 committed by GitHub
parent 9f9bb7b5e2
commit 3c90a0dcf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 136 additions and 19 deletions

View File

@ -54,6 +54,12 @@ Use the following options for a **network:pingdevice**:
- **timeout:** How long the ping will wait for an answer, in milliseconds. Default: `5000` (5 seconds).
- **refreshInterval:** How often the device will be checked, in milliseconds. Default: `60000` (one minute).
- **useIOSWakeUp:** When set to true, an additional port knock is performed before a ping. Default: `true`.
- **useArpPing:** When set to true if the presence detection is allowed to use arp ping.
This can speed up presence detection, but may lead to inaccurate ping latency measurements.
Switch off if you want to use this for ping latency monitoring. Default: `true`.
- **useIcmpPing:** When set to true if the presence detection is allowed to use icmp ping.
When also using arp ping, the latency measurements will not be comparable.
Switch off if you rather want to use arp ping latency monitoring. Default: `true`.
- **networkInterfaceNames:** 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.
Use comma separated values when using textual config. Default: empty (all network interfaces).
@ -190,6 +196,7 @@ demo.things:
```java
Thing network:pingdevice:devicename [ hostname="192.168.0.42", macAddress="6f:70:65:6e:48:41", useIOSWakeUp="false" ]
Thing network:pingdevice:router [ hostname="192.168.0.1", useArpPing="false" ]
Thing network:speedtest:local "SpeedTest 50Mo" @ "Internet" [url="https://bouygues.testdebit.info/", fileName="50M.iso"]
```
@ -199,6 +206,8 @@ demo.items:
Switch MyDevice { channel="network:pingdevice:devicename:online" }
Number:Time MyDeviceResponseTime { channel="network:pingdevice:devicename:latency" }
Number:Time MyRouterResponseTime { channel="network:pingdevice:router:latency" }
String Speedtest_Running "Test running ... [%s]" {channel="network:speedtest:local:isRunning"}
Number:Dimensionless Speedtest_Progress "Test progress [%d %unit%]" {channel="network:speedtest:local:progress"}
Number:DataTransferRate Speedtest_ResultDown "Downlink [%.2f %unit%]" {channel="network:speedtest:local:rateDown"}
@ -218,6 +227,10 @@ sitemap demo label="Main Menu"
Text item=MyDeviceResponseTime label="Device Response Time [%s]"
}
Frame {
Text item=MyRouterResponseTime label="Router Response Time [%s]"
}
Frame label="SpeedTest" {
Text item=Speedtest_Start
Switch item=Speedtest_Running

View File

@ -32,5 +32,7 @@ public class NetworkHandlerConfiguration {
public Integer refreshInterval = 60000;
public Integer timeout = 5000;
public boolean useIOSWakeUp = true;
public boolean useArpPing = true;
public boolean useIcmpPing = true;
public Set<String> networkInterfaceNames = Set.of();
}

View File

@ -71,8 +71,10 @@ public class PresenceDetection implements IPRequestReceivedCallback {
private String ipPingState = "Disabled";
protected String arpPingUtilPath = "";
private ArpPingUtilEnum arpPingMethod = ArpPingUtilEnum.DISABLED;
protected @Nullable IpPingMethodEnum pingMethod = null;
protected @Nullable IpPingMethodEnum pingMethod = IpPingMethodEnum.DISABLED;
private boolean iosDevice;
private boolean useArpPing;
private boolean useIcmpPing;
private Set<Integer> tcpPorts = new HashSet<>();
private Duration refreshInterval = Duration.ofMinutes(1);
@ -188,7 +190,7 @@ public class PresenceDetection implements IPRequestReceivedCallback {
public void setUseIcmpPing(@Nullable Boolean useSystemPing) {
if (useSystemPing == null) {
ipPingState = "Disabled";
pingMethod = null;
pingMethod = IpPingMethodEnum.DISABLED;
} else if (useSystemPing) {
final IpPingMethodEnum pingMethod = networkUtils.determinePingMethod();
this.pingMethod = pingMethod;
@ -220,12 +222,17 @@ public class PresenceDetection implements IPRequestReceivedCallback {
* Sets the path to ARP ping.
*
* @param enable enable or disable ARP ping
* @param arpPingUtilPath enableDHCPListen(useDHCPsniffing);
* @param arpPingUtilPath path to Arping tool
* @param arpPingUtilMethod Arping tool method
*/
public void setUseArpPing(boolean enable, String arpPingUtilPath, ArpPingUtilEnum arpPingUtilMethod) {
setUseArpPing(enable, destination.getValue());
this.arpPingUtilPath = arpPingUtilPath;
this.arpPingMethod = arpPingUtilMethod;
if (!enable) {
arpPingMethod = ArpPingUtilEnum.DISABLED;
} else {
setUseArpPing(enable, destination.getValue());
this.arpPingUtilPath = arpPingUtilPath;
this.arpPingMethod = arpPingUtilMethod;
}
}
public String getArpPingState() {
@ -256,6 +263,36 @@ public class PresenceDetection implements IPRequestReceivedCallback {
iosDevice = value;
}
/**
* Return <code>true</code> if the device presence detection is also performed using arp ping. This gives
* less accurate ping latency results when used for an IPv4 destination host.
*/
public boolean isUseArpPing() {
return useArpPing;
}
/**
* Set to <code>true</code> if the device presence detection should also be performed using arp ping. This gives
* less accurate ping latency results when used for an IPv4 destination host.
*/
public void setUseArpPing(boolean useArpPing) {
this.useArpPing = useArpPing;
}
/**
* Return <code>true</code> if the device presence detection is also performed using icmp ping.
*/
public boolean isUseIcmpPing() {
return useIcmpPing;
}
/**
* Set to <code>true</code> if the device presence detection should also be performed using icmp ping.
*/
public void setUseIcmPing(boolean useIcmpPing) {
this.useIcmpPing = useIcmpPing;
}
/**
* Return the last seen value as an {@link Instant} or <code>null</code> if not yet seen.
*/
@ -329,7 +366,7 @@ public class PresenceDetection implements IPRequestReceivedCallback {
Set<String> interfaceNames = null;
detectionChecks = tcpPorts.size();
if (pingMethod != null) {
if (pingMethod != IpPingMethodEnum.DISABLED) {
detectionChecks += 1;
}
if (arpPingMethod.canProceed) {
@ -385,7 +422,7 @@ public class PresenceDetection implements IPRequestReceivedCallback {
}
// ICMP ping
if (pingMethod != null) {
if (pingMethod != IpPingMethodEnum.DISABLED) {
addAsyncDetection(completableFutures, () -> {
Thread.currentThread().setName("presenceDetectionICMP_" + hostname);
if (pingMethod == IpPingMethodEnum.JAVA_PING) {

View File

@ -183,8 +183,9 @@ public class NetworkHandler extends BaseThingHandler
presenceDetection.setIOSDevice(handlerConfiguration.useIOSWakeUp);
// Hand over binding configurations to the network service
presenceDetection.setUseDhcpSniffing(configuration.allowDHCPlisten);
presenceDetection.setUseIcmpPing(configuration.allowSystemPings);
presenceDetection.setUseArpPing(true, configuration.arpPingToolPath, configuration.arpPingUtilMethod);
presenceDetection.setUseIcmpPing(handlerConfiguration.useIcmpPing ? configuration.allowSystemPings : null);
presenceDetection.setUseArpPing(handlerConfiguration.useArpPing, configuration.arpPingToolPath,
configuration.arpPingUtilMethod);
}
this.retries = handlerConfiguration.retry.intValue();

View File

@ -12,7 +12,7 @@
*/
package org.openhab.binding.network.internal.utils;
import static org.openhab.binding.network.internal.utils.NetworkUtils.millisToDuration;
import static org.openhab.binding.network.internal.utils.NetworkUtils.*;
import java.time.Duration;
import java.util.regex.Matcher;
@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault
public class LatencyParser {
private static final Pattern LATENCY_PATTERN = Pattern.compile(".*time=(.*) ?ms");
private static final Pattern LATENCY_PATTERN = Pattern.compile(".*time=(.*) ?(u|m)s.*");
private final Logger logger = LoggerFactory.getLogger(LatencyParser.class);
// This is how the input looks like on Mac and Linux:
@ -43,18 +43,30 @@ public class LatencyParser {
// 1 packets transmitted, 1 packets received, 0.0% packet loss
// round-trip min/avg/max/stddev = 1.225/1.225/1.225/0.000 ms
// This is an example of an arping response on Linux:
// arping -c 1 -i eth0 192.168.0.1
// ARPING 192.168.0.1
// 60 bytes from xx:xx:xx:xx:xx:xx (192.168.0.1): index=0 time=792.847 usec
//
// --- 192.168.0.1 statistics ---
// 1 packets transmitted, 1 packets received, 0% unanswered (0 extra)
// rtt min/avg/max/std-dev = 0.793/0.793/0.793/0.000 ms
/**
* Examine a single ping command output line and try to extract the latency value if it is contained.
* Examine a single ping or arping command output line and try to extract the latency value if it is contained.
*
* @param inputLine Single output line of the ping command.
* @return Latency value provided by the ping command. <code>null</code> if the provided line did not contain a
* latency value which matches the known patterns.
* @param inputLine Single output line of the ping or arping command.
* @return Latency value provided by the ping or arping command. <code>null</code> if the provided line did not
* contain a latency value which matches the known patterns.
*/
public @Nullable Duration parseLatency(String inputLine) {
logger.debug("Parsing latency from input {}", inputLine);
Matcher m = LATENCY_PATTERN.matcher(inputLine);
if (m.find() && m.groupCount() == 1) {
if (m.find() && m.groupCount() >= 2) {
if ("u".equals(m.group(2))) {
return microsToDuration(Double.parseDouble(m.group(1).replace(",", ".")));
}
return millisToDuration(Double.parseDouble(m.group(1).replace(",", ".")));
}

View File

@ -55,9 +55,10 @@ import org.slf4j.LoggerFactory;
public class NetworkUtils {
/**
* Nanos per millisecond.
* Nanos per millisecond and microsecond.
*/
private static final long NANOS_PER_MILLI = 1000_000L;
private static final long NANOS_PER_MICRO = 1000L;
/**
* Converts a {@link Duration} to milliseconds.
@ -84,6 +85,17 @@ public class NetworkUtils {
return Duration.ofNanos((long) (millis * NANOS_PER_MILLI));
}
/**
* Converts a double representing microseconds to a {@link Duration} instance.
* <p>
*
* @param micros the microseconds to be converted
* @return a {@link Duration} instance representing the given microseconds
*/
public static Duration microsToDuration(double micros) {
return Duration.ofNanos((long) (micros * NANOS_PER_MICRO));
}
private final Logger logger = LoggerFactory.getLogger(NetworkUtils.class);
private LatencyParser latencyParser = new LatencyParser();
@ -286,6 +298,7 @@ public class NetworkUtils {
}
public enum IpPingMethodEnum {
DISABLED,
JAVA_PING,
WINDOWS_PING,
IPUTILS_LINUX_PING,
@ -414,7 +427,25 @@ public class NetworkUtils {
// The return code is 0 for a successful ping. 1 if device didn't respond and 2 if there is another error like
// network interface not ready.
return new PingResult(proc.waitFor() == 0, Duration.between(execStartTime, Instant.now()));
int result = proc.waitFor();
if (result != 0) {
return new PingResult(false, Duration.between(execStartTime, Instant.now()));
}
PingResult pingResult = new PingResult(true, Duration.between(execStartTime, Instant.now()));
try (BufferedReader r = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line = r.readLine();
while (line != null) {
Duration responseTime = latencyParser.parseLatency(line);
if (responseTime != null) {
pingResult.setResponseTime(responseTime);
return pingResult;
}
line = r.readLine();
}
}
return pingResult;
}
/**

View File

@ -41,8 +41,12 @@ thing-type.config.network.pingdevice.retry.label = Retry
thing-type.config.network.pingdevice.retry.description = How many refresh interval cycles should a presence detection should take place, before the device is stated as offline
thing-type.config.network.pingdevice.timeout.label = Timeout
thing-type.config.network.pingdevice.timeout.description = States how long to wait for a response (in ms), before if a device is stated as offline
thing-type.config.network.pingdevice.useArpPing.label = Use ARP Ping
thing-type.config.network.pingdevice.useArpPing.description = Set to true if the presence detection is allowed to use arp ping. This can speed up presence detection, but may lead to inaccurate ping latency measurements. Switch off if you want to use this for ping latency monitoring.
thing-type.config.network.pingdevice.useIOSWakeUp.label = Use iOS Wake Up
thing-type.config.network.pingdevice.useIOSWakeUp.description = Set to true if the device presence detection should be performed for an iOS device like iPhone or iPads. An additional port knock is performed before a ping.
thing-type.config.network.pingdevice.useIcmpPing.label = Use ICMP Ping
thing-type.config.network.pingdevice.useIcmpPing.description = Set to true if the presence detection is allowed to use icmp ping. If you are monitoring network latency using arping, you should switch this off to prevent mixing results with arp ping results.
thing-type.config.network.servicedevice.hostname.label = Hostname or IP
thing-type.config.network.servicedevice.hostname.description = Hostname or IP of the device
thing-type.config.network.servicedevice.macAddress.label = MAC Address

View File

@ -70,6 +70,23 @@
<advanced>true</advanced>
</parameter>
<parameter name="useArpPing" type="boolean" required="true">
<label>Use ARP Ping</label>
<default>true</default>
<description>Set to true if the presence detection is allowed to use arp ping. This can speed up presence detection,
but may lead to inaccurate ping latency measurements. Switch off if you want to use this for ping latency
monitoring.</description>
<advanced>true</advanced>
</parameter>
<parameter name="useIcmpPing" type="boolean" required="true">
<label>Use ICMP Ping</label>
<default>true</default>
<description>Set to true if the presence detection is allowed to use icmp ping. If you are monitoring network
latency using arping, you should switch this off to prevent mixing results with arp ping results.</description>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>