Implemented Option to choose default network interface #3930
Changes from 4 commits
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,96 @@ | ||
/** | ||
* 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.eclipse.smarthome.config.core.ConfigOptionProvider; | ||
import org.eclipse.smarthome.config.core.ParameterOption; | ||
import org.eclipse.smarthome.core.net.NetUtil; | ||
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<>(); | ||
|
||
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(); | ||
String subNetString = NetUtil.getIpv4NetAddress(ipv4Address, ifAddr.getNetworkPrefixLength()) + "/" | ||
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. What do you do if this now throws the newly introduced |
||
+ 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,15 @@ | ||
<?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>A subnet (e.g. 192.168.1.0/24) <!-- or an IP address (e.g. 192.168.1.5) --></description> | ||
<limitToOptions>true</limitToOptions> | ||
</parameter> | ||
</config-description> | ||
|
||
</config-description:config-descriptions> |
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 java.util.regex.Pattern; | ||
|
||
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,17 +31,63 @@ | |
* | ||
* @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 static final Pattern IPV4_PATTERN = Pattern | ||
.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"); | ||
|
||
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 primaryAddressConf = (String) config.get(PRIMARY_ADDRESS); | ||
if (primaryAddressConf == null || primaryAddressConf.isEmpty() || !isValidIPConfig(primaryAddressConf)) { | ||
// if none is specified we return the default one for backward compatibility | ||
LOGGER.warn("Non valid IP configuration found, will continue to use first interface"); | ||
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. As >90% of users will have exactly one interface, there is no need to set this configuration parameter. |
||
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 { | ||
primaryAddress = primaryAddressConf; | ||
} | ||
} | ||
|
||
@Override | ||
public String getPrimaryIpv4HostAddress() { | ||
String primaryIP; | ||
|
||
String[] addrString = primaryAddress.split("/"); | ||
if (addrString.length > 1) { | ||
String ip = getIPv4inSubnet(primaryAddress); | ||
if (ip == null) { | ||
// an error has occurred, using first interface like nothing has been configured | ||
LOGGER.warn("Error in IP configuration, will continue to use first interface"); | ||
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 to:
|
||
primaryIP = NetUtil.getLocalIpv4HostAddress(); | ||
} else { | ||
primaryIP = ip; | ||
} | ||
} else { | ||
primaryIP = addrString[0]; | ||
} | ||
|
||
return primaryIP; | ||
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 did you convince your IDE that primaryIP is definitely not null here? |
||
} | ||
|
||
/** | ||
* Get the first candidate for a local IPv4 host address (non loopback, non localhost). | ||
*/ | ||
@Deprecated | ||
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 message about what people should now use instead. |
||
public static String getLocalIpv4HostAddress() { | ||
try { | ||
String hostAddress = null; | ||
|
@@ -104,4 +156,108 @@ public static String getBroadcastAddress() { | |
} | ||
} | ||
|
||
/** | ||
* Converts a netmask in bits into a string representation | ||
* i.e. 24 bits -> 255.255.255.0 | ||
* | ||
* @param prefixLength bits of the netmask | ||
* @return string representation of netmask (i.e. 255.255.255.0) | ||
*/ | ||
public static String networkPrefixLengthToNetmask(int prefixLength) { | ||
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. where are the null annotation? |
||
if (prefixLength > 31 || prefixLength < 1) { | ||
throw new IllegalArgumentException("Network prefix length is not within bounds"); | ||
} | ||
|
||
int ipv4Netmask = 0xFFFFFFFF; | ||
ipv4Netmask <<= (32 - prefixLength); | ||
|
||
byte[] octets = new byte[] { (byte) (ipv4Netmask >>> 24), (byte) (ipv4Netmask >>> 16), | ||
(byte) (ipv4Netmask >>> 8), (byte) ipv4Netmask }; | ||
|
||
String result = ""; | ||
for (int i = 0; i < 4; i++) { | ||
result += octets[i] & 0xff; | ||
if (i < 3) { | ||
result += "."; | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
/** | ||
* Get the network address a specific ip address is in | ||
* | ||
* @param ipAddressString ipv4 address of the device (i.e. 192.168.5.1) | ||
* @param netMask netmask in bits (i.e. 24) | ||
* @return network a device is in (i.e. 192.168.5.0) | ||
*/ | ||
public static String getIpv4NetAddress(String ipAddressString, short netMask) { | ||
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. where are the null annotation? |
||
String subnetMaskString = networkPrefixLengthToNetmask(netMask); | ||
|
||
String[] netMaskOctets = subnetMaskString.split("\\."); | ||
String[] ipv4AddressOctets = ipAddressString.split("\\."); | ||
String netAddress = ""; | ||
for (int i = 0; i < 4; i++) { | ||
netAddress += Integer.parseInt(ipv4AddressOctets[i]) & Integer.parseInt(netMaskOctets[i]); | ||
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'll need to catch NumberFormatException - this should not bubble up in the call stack. |
||
if (i < 3) { | ||
netAddress += "."; | ||
} | ||
} | ||
return netAddress; | ||
} | ||
|
||
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(); | ||
String subNetString = getIpv4NetAddress(ipv4Address, ifAddr.getNetworkPrefixLength()) + "/" | ||
+ 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; | ||
} | ||
|
||
private boolean isValidIPConfig(String ipConfig) { | ||
|
||
if (ipConfig.contains("/")) { | ||
String parts[] = ipConfig.split("/"); | ||
boolean ipMatches = IPV4_PATTERN.matcher(parts[0]).matches(); | ||
|
||
int netMask = Integer.parseInt(parts[1]); | ||
boolean netMaskMatches = false; | ||
if (netMask > 0 || netMask < 32) { | ||
netMaskMatches = true; | ||
} | ||
|
||
if (ipMatches && netMaskMatches) { | ||
return true; | ||
} | ||
} else { | ||
return IPV4_PATTERN.matcher(ipConfig).matches(); | ||
} | ||
return false; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/** | ||
* 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; | ||
|
||
import org.eclipse.jdt.annotation.NonNull; | ||
|
||
/** | ||
* Interface that provides access to configured network addresses | ||
* | ||
* @author Stefan Triller - initial contribution | ||
* | ||
*/ | ||
public interface NetworkAddressProvider { | ||
|
||
@NonNull | ||
String getPrimaryIpv4HostAddress(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
<li><a href="{{docu}}/features/rest.html">REST API</a></li> | ||
<li><a href="{{docu}}/features/dsl.html">Textual Configuration</a></li> | ||
<li><a href="{{docu}}/features/internationalization.html">Internationalization</a></li> | ||
<li><a href="{{docu}}/features/providedServices.html">Provided Services</a></li> | ||
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. "Provided Services" sounds weird. 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. The chosen "Framework Utilities" is okay for me |
||
<li><a href="{{docu}}/features/rules.html">Rules</a></li> | ||
<li><a href="{{docu}}/features/bindings/hue/readme.html">Bindings</a> | ||
<ul> | ||
|
@@ -69,4 +70,4 @@ | |
<li><a href="{{docu}}/community/contributing.html">Contributing</a></li> | ||
<li><a href="{{docu}}/community/downloads.html">Downloads</a></li> | ||
</ul></li> | ||
</ul> | ||
</ul> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
--- | ||
layout: documentation | ||
--- | ||
|
||
# Provided Services | ||
|
||
In this chapter useful services of the Eclipse SmartHome project are described. | ||
|
||
## Obtaining the default IP address | ||
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. Use the service name for the header: "Network Address Service" or something like that. |
||
|
||
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 `MyHandlerFactory.java`: | ||
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. Every sentence in a new line please. 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 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. Do not talk about "things" and "handlers", we are not in the binding docs anymore. Rather describe your service in general and merely mention in a side note that it might be helpful for callback functionality in ThingHandlers and AudioSinks. |
||
|
||
```java | ||
@Reference | ||
protected void setNetworkAddressprovider(NetworkAddressprovider networkAddressProvider) { | ||
this.networkAddressProvider = networkAddressProvider; | ||
} | ||
protected void unsetNetworkAddressprovider(NetworkAddressprovider networkAddressProvider) { | ||
this.networkAddressProvider = null; | ||
} | ||
``` | ||
|
||
Now the `MyHandlerFactory` can obtain the configured IP address via `networkAddressProvider.getPrimaryIpv4HostAddress()`. This IP address can for example be used in callback URLs offered to a device. |
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.
Change first occurrence to
Set