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

IP addon finder: add support for sending local mac address #4477

Merged
merged 4 commits into from
Dec 8, 2024
Merged
Changes from all 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 @@ -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);
andrewfg marked this conversation as resolved.
Show resolved Hide resolved
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));
holgerfriedrich marked this conversation as resolved.
Show resolved Hide resolved
return (isDelimited ? result.substring(0, result.length() - 1) : result).toString();
}
}