Implemented Option to choose default network interface #3930
Changes from 1 commit
5875895
b0099bf
0d0bb6b
b841acf
12bb82b
a4067e3
aedbf21
041e897
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
/org.eclipse.smarthome.config.core.internal.i18n.I18nConfigOptionsProvider.xml | ||
/org.eclipse.smarthome.config.core.net.internal.NetworkConfigOptionProvider.xml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/** | ||
* Copyright (c) 2014-2017 by the respective copyright holders. | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
*/ | ||
package org.eclipse.smarthome.config.core.net.internal; | ||
|
||
import java.net.Inet6Address; | ||
import java.net.InetAddress; | ||
import java.net.InterfaceAddress; | ||
import java.net.NetworkInterface; | ||
import java.net.SocketException; | ||
import java.net.URI; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.Enumeration; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
import org.apache.commons.net.util.SubnetUtils; | ||
import org.eclipse.smarthome.config.core.ConfigOptionProvider; | ||
import org.eclipse.smarthome.config.core.ParameterOption; | ||
import org.osgi.service.component.annotations.Component; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* Provides a list of IPv4 addresses of the local machine and shows the user which interface belongs to which IP address | ||
* | ||
* @author Stefan Triller - initial contribution | ||
* | ||
*/ | ||
@Component | ||
public class NetworkConfigOptionProvider implements ConfigOptionProvider { | ||
|
||
static final URI CONFIG_URI = URI.create("system:network"); | ||
static final String PARAM_PRIMARY_ADDRESS = "primaryAddress"; | ||
|
||
private final Logger logger = LoggerFactory.getLogger(NetworkConfigOptionProvider.class); | ||
|
||
@Override | ||
public Collection<ParameterOption> getParameterOptions(URI uri, String param, Locale locale) { | ||
if (!uri.equals(CONFIG_URI)) { | ||
return null; | ||
} | ||
|
||
if (param.equals(PARAM_PRIMARY_ADDRESS)) { | ||
return getIPv4Addresses(); | ||
} | ||
return null; | ||
} | ||
|
||
private List<ParameterOption> getIPv4Addresses() { | ||
ArrayList<ParameterOption> interfaceOptions = new ArrayList<>(); | ||
|
||
HashSet<String> subnets = new HashSet<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change first occurrence to |
||
|
||
try { | ||
final Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); | ||
while (interfaces.hasMoreElements()) { | ||
final NetworkInterface current = interfaces.nextElement(); | ||
if (!current.isUp() || current.isLoopback() || current.isVirtual()) { | ||
continue; | ||
} | ||
|
||
for (InterfaceAddress ifAddr : current.getInterfaceAddresses()) { | ||
InetAddress addr = ifAddr.getAddress(); | ||
|
||
if (addr.isLoopbackAddress() || (addr instanceof Inet6Address)) { | ||
continue; | ||
} | ||
|
||
String ipv4Address = addr.getHostAddress(); | ||
|
||
SubnetUtils su = new SubnetUtils( | ||
ipv4Address + "/" + String.valueOf(ifAddr.getNetworkPrefixLength())); | ||
String subNetString = su.getInfo().getNetworkAddress() + "/" | ||
+ String.valueOf(ifAddr.getNetworkPrefixLength()); | ||
|
||
subnets.add(subNetString); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although you didn't add any annotation to |
||
} | ||
} | ||
} catch (SocketException ex) { | ||
logger.error("Could not retrieve network interface: {}", ex.getMessage(), ex); | ||
return null; | ||
} | ||
|
||
for (String subnet : subnets) { | ||
ParameterOption po = new ParameterOption(subnet, subnet); | ||
interfaceOptions.add(po); | ||
} | ||
|
||
return interfaceOptions; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<config-description:config-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xmlns:config-description="http://eclipse.org/smarthome/schemas/config-description/v1.0.0" | ||
xsi:schemaLocation="http://eclipse.org/smarthome/schemas/config-description/v1.0.0 | ||
http://eclipse.org/smarthome/schemas/config-description-1.0.0.xsd"> | ||
|
||
<config-description uri="system:network"> | ||
<parameter name="primaryAddress" type="text"> | ||
<label>Primary Address</label> | ||
<description><![CDATA[<p>The primary network to be used</p> | ||
<p>Alternative: Enter an IP address (XXX.XXX.XXX.XXX) manually</p>]]></description> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You do not mention the alternative to the alternative :-) Better say:
|
||
<limitToOptions>false</limitToOptions> | ||
</parameter> | ||
</config-description> | ||
|
||
</config-description:config-descriptions> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,7 @@ Ignore-Package: org.eclipse.smarthome.core.internal.items,org.eclipse.smarthome. | |
nal,org.eclipse.smarthome.core.internal.events,org.eclipse.smarthome.core.internal.loggin | ||
g | ||
Bundle-Name: Eclipse SmartHome Core | ||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8 | ||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8 | ||
Bundle-Vendor: Eclipse.org/SmartHome | ||
Bundle-Version: 0.9.0.qualifier | ||
Bundle-ManifestVersion: 2 | ||
|
@@ -39,6 +39,7 @@ Import-Package: com.google.common.base, | |
org.apache.commons.io, | ||
org.apache.commons.lang, | ||
org.eclipse.jdt.annotation;resolution:=optional, | ||
org.apache.commons.net.util, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We imho must not introduce this dependency to the ESH core bundles. |
||
org.eclipse.smarthome.core.auth, | ||
org.eclipse.smarthome.core.binding, | ||
org.eclipse.smarthome.core.binding.dto, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/org.eclipse.smarthome.network.xml |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,10 +13,16 @@ | |
import java.net.InterfaceAddress; | ||
import java.net.NetworkInterface; | ||
import java.net.SocketException; | ||
import java.util.Dictionary; | ||
import java.util.Enumeration; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.apache.commons.net.util.SubnetUtils; | ||
import org.osgi.service.component.ComponentContext; | ||
import org.osgi.service.component.annotations.Component; | ||
import org.osgi.service.component.annotations.Modified; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
|
@@ -25,12 +31,51 @@ | |
* | ||
* @author Markus Rathgeb - Initial contribution and API | ||
* @author Mark Herwege - Added methods to find broadcast address(es) | ||
* @author Stefan Triller - Converted to OSGi service with primary ipv4 conf | ||
*/ | ||
public class NetUtil { | ||
@Component(name = "org.eclipse.smarthome.network", property = { "service.config.description.uri=system:network", | ||
"service.config.label=Network Settings", "service.config.category=system" }) | ||
public class NetUtil implements NetworkAddressProvider { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, just realised your name choice now: "Provider" has a very special meaning in many contexts in ESH. |
||
|
||
private static final String PRIMARY_ADDRESS = "primaryAddress"; | ||
private static final Logger LOGGER = LoggerFactory.getLogger(NetUtil.class); | ||
|
||
private NetUtil() { | ||
private String primaryAddress; | ||
|
||
@SuppressWarnings("unchecked") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why that? |
||
protected void activate(ComponentContext componentContext) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of pulling the properties out of the context, why don't you simply implement |
||
Dictionary<String, Object> props = componentContext.getProperties(); | ||
modified((Map<String, Object>) props); | ||
} | ||
|
||
@Modified | ||
public synchronized void modified(Map<String, Object> config) { | ||
String defaultInterfaceConfig = (String) config.get(PRIMARY_ADDRESS); | ||
if (defaultInterfaceConfig == null || defaultInterfaceConfig.equals("")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better use |
||
// if none is specified we return the default one for backward compatibility | ||
primaryAddress = NetUtil.getLocalIpv4HostAddress(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are calling a deprecated method here. Should be avoided :-) |
||
} else { | ||
String primaryAddressConf = (String) config.get(PRIMARY_ADDRESS); | ||
|
||
String[] addrString = primaryAddressConf.split("/"); | ||
if (addrString.length > 1) { | ||
String ip = getIPv4inSubnet(primaryAddressConf); | ||
if (ip == null) { | ||
// an error has occurred, used first interface like nothing has been configured | ||
LOGGER.warn("Error in IP configuration, will continue to use first interface"); | ||
NetUtil.getLocalIpv4HostAddress(); | ||
} else { | ||
primaryAddress = ip; | ||
} | ||
} else { | ||
primaryAddress = addrString[0]; | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public String getPrimaryIpv4HostAddress() { | ||
return primaryAddress; | ||
} | ||
|
||
/** | ||
|
@@ -104,4 +149,40 @@ public static String getBroadcastAddress() { | |
} | ||
} | ||
|
||
private String getIPv4inSubnet(String subnet) { | ||
try { | ||
final Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); | ||
while (interfaces.hasMoreElements()) { | ||
final NetworkInterface current = interfaces.nextElement(); | ||
if (!current.isUp() || current.isLoopback() || current.isVirtual()) { | ||
continue; | ||
} | ||
|
||
for (InterfaceAddress ifAddr : current.getInterfaceAddresses()) { | ||
InetAddress addr = ifAddr.getAddress(); | ||
|
||
if (addr.isLoopbackAddress() || (addr instanceof Inet6Address)) { | ||
continue; | ||
} | ||
|
||
String ipv4Address = addr.getHostAddress(); | ||
|
||
SubnetUtils su = new SubnetUtils( | ||
ipv4Address + "/" + String.valueOf(ifAddr.getNetworkPrefixLength())); | ||
String subNetString = su.getInfo().getNetworkAddress() + "/" | ||
+ String.valueOf(ifAddr.getNetworkPrefixLength()); | ||
|
||
// use first IP within this subnet | ||
if (subNetString.equals(subnet)) { | ||
return ipv4Address; | ||
} | ||
} | ||
} | ||
} catch (SocketException ex) { | ||
LOGGER.error("Could not retrieve network interface: {}", ex.getMessage(), ex); | ||
return null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this line is superfluous. |
||
} | ||
return null; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/** | ||
* Copyright (c) 2014-2017 by the respective copyright holders. | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Eclipse Public License v1.0 | ||
* which accompanies this distribution, and is available at | ||
* http://www.eclipse.org/legal/epl-v10.html | ||
*/ | ||
package org.eclipse.smarthome.core.net; | ||
|
||
/** | ||
* Interface that provides access to configured network addresses | ||
* | ||
* @author Stefan Triller - initial contribution | ||
* | ||
*/ | ||
public interface NetworkAddressProvider { | ||
|
||
public String getPrimaryIpv4HostAddress(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about adding a NonNull annotation? :-) |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -180,6 +180,27 @@ Furthermore bindings can specify a localized description of the thing status by | |
rate_limit=Device is blocked by remote service for {0} minutes. Maximum limit of {1} configuration changes per {2} has been exceeded. For further info please refer to device vendor. | ||
``` | ||
|
||
## Offering a callback URL | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure whether this is the correct place for the documentation of this feature as the feature is completely independent of bindings. It is often used for the implementation of AudioSinks and potentially other extensions. We should rather have a place for such general system services. |
||
|
||
Some things might require a `callback` URL which should be bound to a certain network interface. A user can configure his default network address via Paper UI under `Configuration -> System -> Network Settings`. To obtain this configured address the `ThingHandlerFactory` needs a `service reference` to the `NetworkInterfaceService` in its `OSGI-INF/MyHandlerFactory.xml`: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a new line for every sentence - this makes diffing and reviews easier. The correct name seems to be |
||
|
||
``` | ||
<reference bind="setNetworkAddressprovider" cardinality="1..1" interface="org.eclipse.smarthome.core.net.NetworkAddressProvider" name="NetworkAddressprovider" policy="static" unbind="unsetNetworkAddressprovider"/> | ||
``` | ||
|
||
Inside `MyHandlerFactory.java` two methods are required: | ||
|
||
```java | ||
protected void setNetworkAddressprovider(NetworkAddressprovider networkAddressprovider) { | ||
this.networkAddressprovider = networkInterfaceService; | ||
} | ||
protected void unsetNetworkAddressprovider(NetworkAddressprovider networkAddressprovider) { | ||
this.networkAddressprovider = null; | ||
} | ||
``` | ||
|
||
Now the `MyHandlerFactory` can obtain the configured IP address via `networkAddressprovider.getPrimaryIpv4HostAddress()`. This IP address can be used in callback URL offered to a device. | ||
|
||
## Channel Links | ||
|
||
Some bindings might want to start specific functionality for a channel only if an item is linked to the channel. The `ThingHandler` has two callback methods `channelLinked(ChannelUID channelUID)` and `channelUnlinked(ChannelUID channelUID)`, which are called for every link that is added or removed to/from a channel. So please be aware of the fact that both methods can be called multiple times. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,7 @@ | |
import org.eclipse.smarthome.core.audio.AudioHTTPServer; | ||
import org.eclipse.smarthome.core.audio.AudioSink; | ||
import org.eclipse.smarthome.core.net.HttpServiceUtil; | ||
import org.eclipse.smarthome.core.net.NetUtil; | ||
import org.eclipse.smarthome.core.net.NetworkAddressProvider; | ||
import org.eclipse.smarthome.core.thing.Thing; | ||
import org.eclipse.smarthome.core.thing.ThingTypeUID; | ||
import org.eclipse.smarthome.core.thing.ThingUID; | ||
|
@@ -46,6 +46,7 @@ public class SonosHandlerFactory extends BaseThingHandlerFactory { | |
private UpnpIOService upnpIOService; | ||
private DiscoveryServiceRegistry discoveryServiceRegistry; | ||
private AudioHTTPServer audioHTTPServer; | ||
private NetworkAddressProvider networkAddressprovider; | ||
|
||
private Map<String, ServiceRegistration<AudioSink>> audioSinkRegistrations = new ConcurrentHashMap<>(); | ||
|
||
|
@@ -110,7 +111,7 @@ private String createCallbackUrl() { | |
if (callbackUrl != null) { | ||
return callbackUrl; | ||
} else { | ||
final String ipAddress = NetUtil.getLocalIpv4HostAddress(); | ||
final String ipAddress = networkAddressprovider.getPrimaryIpv4HostAddress(); | ||
if (ipAddress == null) { | ||
logger.warn("No network interface could be found."); | ||
return null; | ||
|
@@ -171,4 +172,12 @@ protected void unsetAudioHTTPServer(AudioHTTPServer audioHTTPServer) { | |
this.audioHTTPServer = null; | ||
} | ||
|
||
protected void setNetworkAddressProvider(NetworkAddressProvider networkUtil) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change networkUtil to networkAddressProvider |
||
this.networkAddressprovider = networkUtil; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change networkAddressprovider to networkAddressProvider |
||
} | ||
|
||
protected void unsetNetworkAddressProvider(NetworkAddressProvider networkUtil) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dito |
||
this.networkAddressprovider = null; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We imho must not introduce this dependency to the ESH core bundles.