Skip to content

Commit

Permalink
IP addon finder: add support for sending local mac address (#4477)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Fiddian-Green <[email protected]>
  • Loading branch information
andrewfg authored Dec 8, 2024
1 parent 76daeef commit 57cf6a5
Showing 1 changed file with 69 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
*/
package org.openhab.core.config.discovery.addon.ip;

import static org.openhab.core.config.discovery.addon.AddonFinderConstants.*;
import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_NAME_IP;
import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_TYPE_IP;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
Expand All @@ -21,7 +22,9 @@
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
Expand All @@ -34,6 +37,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.HexFormat;
import java.util.IllegalFormatException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -139,6 +143,11 @@
* <td>timeout to wait for a answers</td>
* <td></td>
* </tr>
* <tr>
* <td>{@code fmtMac}</td>
* <td>format specifier string for mac address</td>
* <td>e.g. '%02X', '%02X:', '%02x-'</td>
* </tr>
* </table>
* <p>
* <table border="1">
Expand All @@ -154,6 +163,11 @@
* <td>{@code $srcPort}</td>
* <td>source port</td>
* </tr>
* <tr>
* <td>{@code $srcMac}</td>
* <td>source mac address</td>
* </tr>
* <tr>
* <td>{@code $uuid}</td>
* <td>String returned by {@code java.util.UUID.randomUUID()}</td>
* </tr>
Expand Down Expand Up @@ -201,6 +215,8 @@ public class IpAddonFinder extends BaseAddonFinder implements NetworkAddressChan
private static final String PARAMETER_REQUEST_PLAIN = "requestPlain";
private static final String PARAMETER_SRC_IP = "srcIp";
private static final String PARAMETER_SRC_PORT = "srcPort";
private static final String PARAMETER_SRC_MAC = "srcMac";
private static final String PARAMETER_MAC_FORMAT = "fmtMac";
private static final String PARAMETER_TIMEOUT_MS = "timeoutMs";
private static final String REPLACEMENT_UUID = "uuid";

Expand Down Expand Up @@ -346,16 +362,22 @@ private void scan() {
continue;
}
}
String macFormat = parameters.getOrDefault(PARAMETER_MAC_FORMAT, "%02X:");
try {
String.format(macFormat, 123);
} catch (IllegalFormatException e) {
logger.warn("{}: discovery-parameter '{}' invalid format specifier", candidate.getUID(), macFormat);
}

// handle known types
try {
switch (Objects.toString(type)) {
case TYPE_IP_BROADCAST:
scanBroadcast(candidate, request, requestPlain, response, timeoutMs, destPort);
scanBroadcast(candidate, request, requestPlain, response, timeoutMs, destPort, macFormat);
break;
case TYPE_IP_MULTICAST:
scanMulticast(candidate, request, requestPlain, response, timeoutMs, listenPort, destIp,
destPort);
destPort, macFormat);
break;
default:
logger.warn("{}: discovery-parameter type \"{}\" is unknown", candidate.getUID(), type);
Expand All @@ -369,7 +391,7 @@ private void scan() {
}

private void scanBroadcast(AddonInfo candidate, String request, String requestPlain, String response, int timeoutMs,
int destPort) throws ParseException {
int destPort, String macFormat) throws ParseException {
if (request.isEmpty() && requestPlain.isEmpty()) {
logger.warn("{}: match-property request and requestPlain \"{}\" is unknown", candidate.getUID(),
TYPE_IP_BROADCAST);
Expand All @@ -391,7 +413,7 @@ private void scanBroadcast(AddonInfo candidate, String request, String requestPl
socket.setBroadcast(true);
socket.setSoTimeout(timeoutMs);
byte[] sendBuffer = requestPlain.isEmpty() ? buildRequestArray(socket.getLocalSocketAddress(), request)
: buildRequestArrayPlain(socket.getLocalSocketAddress(), requestPlain);
: buildRequestArrayPlain(socket.getLocalSocketAddress(), requestPlain, macFormat);
DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length,
InetAddress.getByName(broadcastAddress), destPort);
socket.send(sendPacket);
Expand Down Expand Up @@ -431,7 +453,7 @@ private byte[] buildByteArray(String input) {
}

private void scanMulticast(AddonInfo candidate, String request, String requestPlain, String response, int timeoutMs,
int listenPort, @Nullable InetAddress destIp, int destPort) throws ParseException {
int listenPort, @Nullable InetAddress destIp, int destPort, String macFormat) throws ParseException {
List<String> ipAddresses = NetUtil.getAllInterfaceAddresses().stream()
.filter(a -> a.getAddress() instanceof Inet4Address).map(a -> a.getAddress().getHostAddress()).toList();

Expand All @@ -443,7 +465,7 @@ private void scanMulticast(AddonInfo candidate, String request, String requestPl
Selector selector = Selector.open()) {
byte[] requestArray = "".equals(requestPlain)
? buildRequestArray(channel.getLocalAddress(), Objects.toString(request))
: buildRequestArrayPlain(channel.getLocalAddress(), Objects.toString(requestPlain));
: buildRequestArrayPlain(channel.getLocalAddress(), Objects.toString(requestPlain), macFormat);
if (logger.isTraceEnabled()) {
InetSocketAddress sock = (InetSocketAddress) channel.getLocalAddress();
String id = candidate.getUID();
Expand Down Expand Up @@ -489,7 +511,7 @@ private void scanMulticast(AddonInfo candidate, String request, String requestPl
}

// build from plaintext string
private byte[] buildRequestArrayPlain(SocketAddress address, String request)
private byte[] buildRequestArrayPlain(SocketAddress address, String request, String macFormat)
throws java.io.IOException, ParseException {
InetSocketAddress sock = (InetSocketAddress) address;

Expand All @@ -502,6 +524,9 @@ private byte[] buildRequestArrayPlain(SocketAddress address, String request)
while ((p = req.indexOf("$" + PARAMETER_SRC_PORT)) != -1) {
req.replace(p, p + PARAMETER_SRC_PORT.length() + 1, "" + sock.getPort());
}
while ((p = req.indexOf("$" + PARAMETER_SRC_MAC)) != -1) {
req.replace(p, p + PARAMETER_SRC_MAC.length() + 1, macFormat(macFormat, macBytesFrom(sock)));
}
while ((p = req.indexOf("$" + REPLACEMENT_UUID)) != -1) {
req.replace(p, p + REPLACEMENT_UUID.length() + 1, UUID.randomUUID().toString());
}
Expand Down Expand Up @@ -531,6 +556,10 @@ private byte[] buildRequestArray(SocketAddress address, String request) throws j
requestFrame.write((byte) ((dPort >> 8) & 0xff));
requestFrame.write((byte) (dPort & 0xff));
break;
case "$" + PARAMETER_SRC_MAC:
byte[] mac = macBytesFrom(sock);
requestFrame.write(mac);
break;
case "$" + REPLACEMENT_UUID:
String uuid = UUID.randomUUID().toString();
requestFrame.write(uuid.getBytes());
Expand Down Expand Up @@ -567,4 +596,36 @@ private boolean isAddonInstalled(String addonId) {
}
return false;
}

/**
* Get mac address bytes associated with the given Internet socket address
*
* @param inetSocketAddress the Internet address
* @return the mac address as an array of bytes
* @throws SocketException if address is not on this PC, or no mac address is associated
*/
private byte[] macBytesFrom(InetSocketAddress inetSocketAddress) throws SocketException {
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetSocketAddress.getAddress());
if (networkInterface == null) {
throw new SocketException("No network interface");
}
return networkInterface.getHardwareAddress();
}

/**
* Use the given format specifier to format an array of mac address bytes
*
* @param format a standard format specifier; optionally ends with a delimiter e.g. "%02x:" or "%02X"
* @param bytes the mac address as an array of bytes
*
* @return e.g. '01:02:03:04:A5:B6:C7:D8', '01-02-03-04:a5:b6:c7:d8', or '01020304A5B6C7D8'
*/
private String macFormat(String format, byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte byt : bytes) {
result.append(String.format(format, byt));
}
boolean isDelimited = Set.of(':', '-', '.').contains(format.charAt(format.length() - 1));
return (isDelimited ? result.substring(0, result.length() - 1) : result).toString();
}
}

0 comments on commit 57cf6a5

Please sign in to comment.