diff --git a/bom/openhab-core/pom.xml b/bom/openhab-core/pom.xml index 8346e8dd549..0b7cd97eea3 100644 --- a/bom/openhab-core/pom.xml +++ b/bom/openhab-core/pom.xml @@ -310,6 +310,12 @@ ${project.version} compile + + org.openhab.core.bundles + org.openhab.core.config.discovery.addon.ip + ${project.version} + compile + org.openhab.core.bundles org.openhab.core.config.discovery.addon.mdns diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/.classpath b/bundles/org.openhab.core.config.discovery.addon.ip/.classpath new file mode 100644 index 00000000000..d3d6b3c11b6 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.ip/.classpath @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/.project b/bundles/org.openhab.core.config.discovery.addon.ip/.project new file mode 100644 index 00000000000..f2cee8bcbb9 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.ip/.project @@ -0,0 +1,23 @@ + + + org.openhab.core.config.discovery.addon.ip + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature + + diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/NOTICE b/bundles/org.openhab.core.config.discovery.addon.ip/NOTICE new file mode 100644 index 00000000000..6c17d0d8a45 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.ip/NOTICE @@ -0,0 +1,14 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-core + diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/pom.xml b/bundles/org.openhab.core.config.discovery.addon.ip/pom.xml new file mode 100644 index 00000000000..d70ff9728c8 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.ip/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + + org.openhab.core.bundles + org.openhab.core.reactor.bundles + 4.1.0-SNAPSHOT + + + org.openhab.core.config.discovery.addon.ip + + openHAB Core :: Bundles :: IP-based Suggested Add-on Finder + + + + org.openhab.core.bundles + org.openhab.core.config.discovery.addon + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.addon + ${project.version} + + + diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/src/main/java/org/openhab/core/config/discovery/addon/ip/IpAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.ip/src/main/java/org/openhab/core/config/discovery/addon/ip/IpAddonFinder.java new file mode 100644 index 00000000000..ab6175074c9 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.ip/src/main/java/org/openhab/core/config/discovery/addon/ip/IpAddonFinder.java @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.config.discovery.addon.ip; + +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; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.StandardProtocolFamily; +import java.net.StandardSocketOptions; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.HashSet; +import java.util.HexFormat; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.config.discovery.addon.AddonFinder; +import org.openhab.core.config.discovery.addon.BaseAddonFinder; +import org.openhab.core.net.NetUtil; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is a {@link IpAddonFinder} for finding suggested add-ons by sending IP packets to the + * network and collecting responses. + * + * @author Holger Friedrich - Initial contribution + */ +@NonNullByDefault +@Component(service = AddonFinder.class, name = IpAddonFinder.SERVICE_NAME) +public class IpAddonFinder extends BaseAddonFinder { + + public static final String SERVICE_TYPE = SERVICE_TYPE_IP; + public static final String SERVICE_NAME = SERVICE_NAME_IP; + + private final Logger logger = LoggerFactory.getLogger(IpAddonFinder.class); + + @Activate + public IpAddonFinder() { + logger.warn("IpAddonFinder::IpAddonFinder"); + } + + Set scan() { + Set result = new HashSet<>(); + for (AddonInfo candidate : addonCandidates) { + for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods().stream() + .filter(method -> SERVICE_TYPE.equals(method.getServiceType())).toList()) { + + Map matchProperties = method.getMatchProperties().stream() + .collect(Collectors.toMap(property -> property.getName(), property -> property.getRegex())); + + String type = matchProperties.get("type"); + String request = matchProperties.get("request"); + int timeoutMs = Integer.parseInt(Objects.toString(matchProperties.get("timeout_ms"))); + @Nullable + InetAddress destIp = null; + try { + destIp = InetAddress.getByName(matchProperties.get("dest_ip")); + } catch (UnknownHostException e) { + // TODO Auto-generated catch block + + } + int destPort = Integer.parseInt(Objects.toString(matchProperties.get("dest_port"))); + + if ("ip_multicast".equals(type)) { + + List ipAddresses = NetUtil.getAllInterfaceAddresses().stream() + .filter(a -> a.getAddress() instanceof Inet4Address) + .map(a -> a.getAddress().getHostAddress()).toList(); + + for (String localIp : ipAddresses) { + try { + + DatagramChannel channel = (DatagramChannel) DatagramChannel + .open(StandardProtocolFamily.INET) + .setOption(StandardSocketOptions.SO_REUSEADDR, true) + .bind(new InetSocketAddress(localIp, 0)) + .setOption(StandardSocketOptions.IP_MULTICAST_TTL, 64).configureBlocking(false); + InetSocketAddress sock = (InetSocketAddress) channel.getLocalAddress(); + + ByteArrayOutputStream requestFrame = new ByteArrayOutputStream(); + StringTokenizer parts = new StringTokenizer(request); + + while (parts.hasMoreTokens()) { + String token = parts.nextToken(); + if (token.startsWith("$")) { + switch (token) { + case "$src_ip": + byte[] adr = sock.getAddress().getAddress(); + requestFrame.write(adr); + break; + case "$src_port": + int dPort = sock.getPort(); + requestFrame.write((byte) ((dPort >> 8) & 0xff)); + requestFrame.write((byte) (dPort & 0xff)); + break; + default: + logger.warn("unknown token"); + } + } else { + int i = Integer.decode(token); + requestFrame.write((byte) i); + } + } + logger.info("{}", HexFormat.of().withDelimiter(" ").formatHex(requestFrame.toByteArray())); + + channel.send(ByteBuffer.wrap(requestFrame.toByteArray()), + new InetSocketAddress(destIp, destPort)); + + // listen to responses + Selector selector = Selector.open(); + ByteBuffer buffer = ByteBuffer.wrap(new byte[50]); + channel.register(selector, SelectionKey.OP_READ); + selector.select(timeoutMs); + Iterator it = selector.selectedKeys().iterator(); + if (it.hasNext()) { + final SocketAddress source = ((DatagramChannel) it.next().channel()).receive(buffer); + logger.debug("Received return frame from {}", + ((InetSocketAddress) source).getAddress().getHostAddress()); + result.add(candidate); + } else { + logger.debug("no response"); + } + + } catch (IOException e) { + logger.trace("KNXnet/IP discovery failed on {}", localIp, e); + } + } + } + } + } + return result; + } + + @Override + public Set getSuggestedAddons() { + logger.trace("IpAddonFinder::getSuggestedAddons"); + Set result = new HashSet<>(); + + result = scan(); + + return result; + } + + @Override + public String getServiceName() { + return SERVICE_NAME; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java index 596498898d5..ffb15a5577d 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java @@ -28,6 +28,11 @@ public class AddonFinderConstants { private static final String ADDON_SUGGESTION_FINDER = "-addon-suggestion-finder"; private static final String ADDON_SUGGESTION_FINDER_FEATURE = "openhab-core-config-discovery-addon-"; + public static final String SERVICE_TYPE_IP = "ip"; + public static final String CFG_FINDER_IP = "suggestionFinderIp"; + public static final String SERVICE_NAME_IP = SERVICE_TYPE_IP + ADDON_SUGGESTION_FINDER; + public static final String FEATURE_IP = ADDON_SUGGESTION_FINDER_FEATURE + SERVICE_TYPE_IP; + public static final String SERVICE_TYPE_MDNS = "mdns"; public static final String CFG_FINDER_MDNS = "suggestionFinderMdns"; public static final String SERVICE_NAME_MDNS = SERVICE_TYPE_MDNS + ADDON_SUGGESTION_FINDER; @@ -38,9 +43,10 @@ public class AddonFinderConstants { public static final String SERVICE_NAME_UPNP = SERVICE_TYPE_UPNP + ADDON_SUGGESTION_FINDER; public static final String FEATURE_UPNP = ADDON_SUGGESTION_FINDER_FEATURE + SERVICE_TYPE_UPNP; - public static final List SUGGESTION_FINDERS = List.of(SERVICE_NAME_MDNS, SERVICE_NAME_UPNP); - public static final Map SUGGESTION_FINDER_CONFIGS = Map.of(SERVICE_NAME_MDNS, CFG_FINDER_MDNS, - SERVICE_NAME_UPNP, CFG_FINDER_UPNP); - public static final Map SUGGESTION_FINDER_FEATURES = Map.of(SERVICE_NAME_MDNS, FEATURE_MDNS, - SERVICE_NAME_UPNP, FEATURE_UPNP); + public static final List SUGGESTION_FINDERS = List.of(SERVICE_NAME_IP, SERVICE_NAME_MDNS, + SERVICE_NAME_UPNP); + public static final Map SUGGESTION_FINDER_CONFIGS = Map.of(SERVICE_NAME_IP, CFG_FINDER_IP, + SERVICE_NAME_MDNS, CFG_FINDER_MDNS, SERVICE_NAME_UPNP, CFG_FINDER_UPNP); + public static final Map SUGGESTION_FINDER_FEATURES = Map.of(SERVICE_NAME_IP, FEATURE_IP, + SERVICE_NAME_MDNS, FEATURE_MDNS, SERVICE_NAME_UPNP, FEATURE_UPNP); } diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml index 8859ef7814f..f175329fd41 100644 --- a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml @@ -29,6 +29,12 @@ Use mDNS network scan to suggest add-ons. Enabling/disabling may take up to 1 minute. true + + true + + Use IP network scan to suggest add-ons. Enabling/disabling may take up to 1 minute. + true + diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons.properties b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons.properties index 46f8519531d..f4fde0f03d5 100644 --- a/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons.properties +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/i18n/addons.properties @@ -2,6 +2,8 @@ system.config.addons.includeIncompatible.label = Include (Potentially) Incompati system.config.addons.includeIncompatible.description = Some add-on services may provide add-ons where compatibility with the currently running system is not expected. Enabling this option will include these entries in the list of available add-ons. system.config.addons.remote.label = Access Remote Repository system.config.addons.remote.description = Defines whether openHAB should access the remote repository for add-on installation. +system.config.addons.suggestionFinderIp.label = IP-based Suggestion Finder +system.config.addons.suggestionFinderIp.description = Use IP network scan to suggest add-ons. Enabling/disabling may take up to 1 minute. system.config.addons.suggestionFinderMdns.label = mDNS Suggestion Finder system.config.addons.suggestionFinderMdns.description = Use mDNS network scan to suggest add-ons. Enabling/disabling may take up to 1 minute. system.config.addons.suggestionFinderUpnp.label = UPnP Suggestion Finder diff --git a/bundles/pom.xml b/bundles/pom.xml index 01799d77195..5110022de5a 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -31,6 +31,7 @@ org.openhab.core.config.core org.openhab.core.config.discovery org.openhab.core.config.discovery.addon + org.openhab.core.config.discovery.addon.ip org.openhab.core.config.discovery.addon.mdns org.openhab.core.config.discovery.addon.upnp org.openhab.core.config.discovery.mdns diff --git a/features/karaf/openhab-core/src/main/feature/feature.xml b/features/karaf/openhab-core/src/main/feature/feature.xml index 5062c2ead67..336e0e6e854 100644 --- a/features/karaf/openhab-core/src/main/feature/feature.xml +++ b/features/karaf/openhab-core/src/main/feature/feature.xml @@ -83,6 +83,12 @@ openhab.tp-jmdns + + openhab-core-base + openhab-core-config-discovery-addon + mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon.ip/${project.version} + + openhab-core-base openhab-core-config-discovery-addon