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

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

Merged
merged 3 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 13 additions & 0 deletions bundles/org.openhab.binding.network/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
- **usePing:** When set to true if the presence detection is allowed to use ping.
jlaur marked this conversation as resolved.
Show resolved Hide resolved
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).
Expand Down Expand Up @@ -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"]
```

Expand All @@ -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"}
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 usePing = true;
public Set<String> networkInterfaceNames = Set.of();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 usePing;
private Set<Integer> tcpPorts = new HashSet<>();

private Duration refreshInterval = Duration.ofMinutes(1);
Expand Down Expand Up @@ -188,7 +190,7 @@ public void setPreferResponseTimeAsLatency(boolean preferResponseTimeAsLatency)
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;
Expand Down Expand Up @@ -220,12 +222,17 @@ private void setUseArpPing(boolean enable, @Nullable InetAddress destinationAddr
* 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() {
Expand Down Expand Up @@ -256,6 +263,36 @@ public void setIOSDevice(boolean value) {
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 ping.
*/
public boolean isUsePing() {
return usePing;
}

/**
* Set to <code>true</code> if the device presence detection should also be performed using ping.
*/
public void setUsePing(boolean usePing) {
this.usePing = usePing;
}

/**
* Return the last seen value as an {@link Instant} or <code>null</code> if not yet seen.
*/
Expand Down Expand Up @@ -329,7 +366,7 @@ public CompletableFuture<PresenceDetectionValue> performPresenceDetection() {
Set<String> interfaceNames = null;

detectionChecks = tcpPorts.size();
if (pingMethod != null) {
if (pingMethod != IpPingMethodEnum.DISABLED) {
detectionChecks += 1;
}
if (arpPingMethod.canProceed) {
Expand Down Expand Up @@ -385,7 +422,7 @@ public CompletableFuture<PresenceDetectionValue> performPresenceDetection() {
}

// ICMP ping
if (pingMethod != null) {
if (pingMethod != IpPingMethodEnum.DISABLED) {
addAsyncDetection(completableFutures, () -> {
Thread.currentThread().setName("presenceDetectionICMP_" + hostname);
if (pingMethod == IpPingMethodEnum.JAVA_PING) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,9 @@ void initialize(PresenceDetection presenceDetection) {
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.usePing ? configuration.allowSystemPings : null);
presenceDetection.setUseArpPing(handlerConfiguration.useArpPing, configuration.arpPingToolPath,
configuration.arpPingUtilMethod);
}

this.retries = handlerConfiguration.retry.intValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,7 +31,7 @@
@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:
Expand All @@ -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 apring response on Linux:
jlaur marked this conversation as resolved.
Show resolved Hide resolved
// 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(",", ".")));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@
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.
Expand All @@ -84,6 +85,17 @@ public static Duration millisToDuration(double millis) {
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();
Expand Down Expand Up @@ -286,6 +298,7 @@ public ArpPingUtilEnum determineNativeArpPingMethod(String arpToolPath) {
}

public enum IpPingMethodEnum {
DISABLED,
JAVA_PING,
WINDOWS_PING,
IPUTILS_LINUX_PING,
Expand Down Expand Up @@ -414,7 +427,25 @@ public enum ArpPingUtilEnum {

// 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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.usePing.label = Use ICMP Ping
thing-type.config.network.pingdevice.usePing.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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="usePing" type="boolean" required="true">
jlaur marked this conversation as resolved.
Show resolved Hide resolved
<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>

Expand Down