View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.transport.impl;
17  
18  import org.fourthline.cling.model.Constants;
19  import org.fourthline.cling.transport.spi.InitializationException;
20  import org.fourthline.cling.transport.spi.NetworkAddressFactory;
21  import org.fourthline.cling.transport.spi.NoNetworkException;
22  import org.seamless.util.Iterators;
23  
24  import java.net.Inet4Address;
25  import java.net.Inet6Address;
26  import java.net.InetAddress;
27  import java.net.InterfaceAddress;
28  import java.net.NetworkInterface;
29  import java.net.SocketException;
30  import java.net.UnknownHostException;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.Enumeration;
35  import java.util.HashSet;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Set;
39  import java.util.Locale;
40  import java.util.logging.Level;
41  import java.util.logging.Logger;
42  
43  /**
44   * Default implementation of network interface and address configuration/discovery.
45   * <p
46   * This implementation has been tested on Windows XP, Windows Vista, Mac OS X 10.8,
47   * and whatever kernel ships in Ubuntu 9.04. This implementation does not support IPv6.
48   * </p>
49   *
50   * @author Christian Bauer
51   */
52  public class NetworkAddressFactoryImpl implements NetworkAddressFactory {
53  
54      // Ephemeral port is the default
55      public static final int DEFAULT_TCP_HTTP_LISTEN_PORT = 0;
56  
57      private static Logger log = Logger.getLogger(NetworkAddressFactoryImpl.class.getName());
58  
59      final protected Set<String> useInterfaces = new HashSet<>();
60      final protected Set<String> useAddresses = new HashSet<>();
61  
62      final protected List<NetworkInterface> networkInterfaces = new ArrayList<>();
63      final protected List<InetAddress> bindAddresses = new ArrayList<>();
64  
65      protected int streamListenPort;
66  
67      /**
68       * Defaults to an ephemeral port.
69       */
70      public NetworkAddressFactoryImpl() throws InitializationException {
71          this(DEFAULT_TCP_HTTP_LISTEN_PORT);
72      }
73  
74      public NetworkAddressFactoryImpl(int streamListenPort) throws InitializationException {
75      	
76      	System.setProperty("java.net.preferIPv4Stack", "true");
77  
78          String useInterfacesString = System.getProperty(SYSTEM_PROPERTY_NET_IFACES);
79          if (useInterfacesString != null) {
80              String[] userInterfacesStrings = useInterfacesString.split(",");
81              useInterfaces.addAll(Arrays.asList(userInterfacesStrings));
82          }
83  
84          String useAddressesString = System.getProperty(SYSTEM_PROPERTY_NET_ADDRESSES);
85          if (useAddressesString != null) {
86              String[] useAddressesStrings = useAddressesString.split(",");
87              useAddresses.addAll(Arrays.asList(useAddressesStrings));
88          }
89  
90          discoverNetworkInterfaces();
91          discoverBindAddresses();
92  
93          if ((networkInterfaces.size() == 0 || bindAddresses.size() == 0)) {
94              log.warning("No usable network interface or addresses found");
95          	if(requiresNetworkInterface()) {
96          		throw new NoNetworkException(
97                      "Could not discover any usable network interfaces and/or addresses"
98                  );
99          	}
100         }
101 
102         this.streamListenPort = streamListenPort;
103     }
104 
105     /**
106      * @return <code>true</code> (the default) if a <code>MissingNetworkInterfaceException</code> should be thrown
107      */
108     protected boolean requiresNetworkInterface() {
109     	return true;
110     }
111 
112     public void logInterfaceInformation() {
113         synchronized (networkInterfaces) {
114             if(networkInterfaces.isEmpty()) {
115                 log.info("No network interface to display!");
116                 return ;
117             }
118             for(NetworkInterface networkInterface : networkInterfaces) {
119                 try {
120                     logInterfaceInformation(networkInterface);
121                 } catch (SocketException ex) {
122                     log.log(Level.WARNING, "Exception while logging network interface information", ex);
123                 }
124             }
125         }
126     }
127 
128     public InetAddress getMulticastGroup() {
129         try {
130             return InetAddress.getByName(Constants.IPV4_UPNP_MULTICAST_GROUP);
131         } catch (UnknownHostException ex) {
132             throw new RuntimeException(ex);
133         }
134     }
135 
136     public int getMulticastPort() {
137         return Constants.UPNP_MULTICAST_PORT;
138     }
139 
140     public int getStreamListenPort() {
141         return streamListenPort;
142     }
143 
144     public Iterator<NetworkInterface> getNetworkInterfaces() {
145         return new Iterators.Synchronized<NetworkInterface>(networkInterfaces) {
146             @Override
147             protected void synchronizedRemove(int index) {
148                 synchronized (networkInterfaces) {
149                     networkInterfaces.remove(index);
150                 }
151             }
152         };
153     }
154 
155     public Iterator<InetAddress> getBindAddresses() {
156         return new Iterators.Synchronized<InetAddress>(bindAddresses) {
157             @Override
158             protected void synchronizedRemove(int index) {
159                 synchronized (bindAddresses) {
160                     bindAddresses.remove(index);
161                 }
162             }
163         };
164     }
165 
166     public boolean hasUsableNetwork() {
167         return networkInterfaces.size() > 0 && bindAddresses.size() > 0;
168     }
169 
170     public byte[] getHardwareAddress(InetAddress inetAddress) {
171         try {
172             NetworkInterface iface = NetworkInterface.getByInetAddress(inetAddress);
173             return iface != null ? iface.getHardwareAddress() : null;
174         } catch (Throwable ex) {
175             log.log(Level.WARNING, "Cannot get hardware address for: " + inetAddress, ex);
176         	// On Win32: java.lang.Error: IP Helper Library GetIpAddrTable function failed
177 
178             // On Android 4.0.3 NullPointerException with inetAddress != null
179 
180             // On Android "SocketException: No such device or address" when
181             // switching networks (mobile -> WiFi)
182         	return null;
183         }
184     }
185 
186     public InetAddress getBroadcastAddress(InetAddress inetAddress) {
187         synchronized (networkInterfaces) {
188             for (NetworkInterface iface : networkInterfaces) {
189                 for (InterfaceAddress interfaceAddress : getInterfaceAddresses(iface)) {
190                     if (interfaceAddress != null && interfaceAddress.getAddress().equals(inetAddress)) {
191                         return interfaceAddress.getBroadcast();
192                     }
193                 }
194             }
195         }
196         return null;
197     }
198 
199     public Short getAddressNetworkPrefixLength(InetAddress inetAddress) {
200         synchronized (networkInterfaces) {
201             for (NetworkInterface iface : networkInterfaces) {
202                 for (InterfaceAddress interfaceAddress : getInterfaceAddresses(iface)) {
203                     if (interfaceAddress != null && interfaceAddress.getAddress().equals(inetAddress)) {
204                         short prefix = interfaceAddress.getNetworkPrefixLength();
205                         if(prefix > 0 && prefix < 32) return prefix; // some network cards return -1
206                         return null;
207                     }
208                 }
209             }
210         }
211         return null;
212     }
213 
214     public InetAddress getLocalAddress(NetworkInterface networkInterface, boolean isIPv6, InetAddress remoteAddress) {
215 
216         // First try to find a local IP that is in the same subnet as the remote IP
217         InetAddress localIPInSubnet = getBindAddressInSubnetOf(remoteAddress);
218         if (localIPInSubnet != null) return localIPInSubnet;
219 
220         // There are two reasons why we end up here:
221         //
222         // - Windows Vista returns a 64 or 128 CIDR prefix if you ask it for the network prefix length of an IPv4 address!
223         //
224         // - We are dealing with genuine IPv6 addresses
225         //
226         // - Something is really wrong on the LAN and we received a multicast datagram from a source we can't reach via IP
227         log.finer("Could not find local bind address in same subnet as: " + remoteAddress.getHostAddress());
228 
229         // Next, just take the given interface (which is really totally random) and get the first address that we like
230         for (InetAddress interfaceAddress: getInetAddresses(networkInterface)) {
231             if (isIPv6 && interfaceAddress instanceof Inet6Address)
232                 return interfaceAddress;
233             if (!isIPv6 && interfaceAddress instanceof Inet4Address)
234                 return interfaceAddress;
235         }
236         throw new IllegalStateException("Can't find any IPv4 or IPv6 address on interface: " + networkInterface.getDisplayName());
237     }
238 
239     protected List<InterfaceAddress> getInterfaceAddresses(NetworkInterface networkInterface) {
240         return networkInterface.getInterfaceAddresses();
241     }
242 
243     protected List<InetAddress> getInetAddresses(NetworkInterface networkInterface) {
244         return Collections.list(networkInterface.getInetAddresses());
245     }
246 
247     protected InetAddress getBindAddressInSubnetOf(InetAddress inetAddress) {
248         synchronized (networkInterfaces) {
249             for (NetworkInterface iface : networkInterfaces) {
250                 for (InterfaceAddress ifaceAddress : getInterfaceAddresses(iface)) {
251 
252                     synchronized (bindAddresses) {
253                         if (ifaceAddress == null || !bindAddresses.contains(ifaceAddress.getAddress())) {
254                             continue;
255                         }
256                     }
257 
258                     if (isInSubnet(
259                             inetAddress.getAddress(),
260                             ifaceAddress.getAddress().getAddress(),
261                             ifaceAddress.getNetworkPrefixLength())
262                             ) {
263                         return ifaceAddress.getAddress();
264                     }
265                 }
266 
267             }
268         }
269         return null;
270     }
271 
272     protected boolean isInSubnet(byte[] ip, byte[] network, short prefix) {
273         if (ip.length != network.length) {
274             return false;
275         }
276 
277         if (prefix / 8 > ip.length) {
278             return false;
279         }
280 
281         int i = 0;
282         while (prefix >= 8 && i < ip.length) {
283             if (ip[i] != network[i]) {
284                 return false;
285             }
286             i++;
287             prefix -= 8;
288         }
289         if(i == ip.length) return true;
290         final byte mask = (byte) ~((1 << 8 - prefix) - 1);
291 
292         return (ip[i] & mask) == (network[i] & mask);
293     }
294 
295     protected void discoverNetworkInterfaces() throws InitializationException {
296         try {
297 
298             Enumeration<NetworkInterface> interfaceEnumeration = NetworkInterface.getNetworkInterfaces();
299             for (NetworkInterface iface : Collections.list(interfaceEnumeration)) {
300                 //displayInterfaceInformation(iface);
301 
302                 log.finer("Analyzing network interface: " + iface.getDisplayName());
303                 if (isUsableNetworkInterface(iface)) {
304                     log.fine("Discovered usable network interface: " + iface.getDisplayName());
305                     synchronized (networkInterfaces) {
306                         networkInterfaces.add(iface);
307                     }
308                 } else {
309                     log.finer("Ignoring non-usable network interface: " + iface.getDisplayName());
310                 }
311             }
312 
313         } catch (Exception ex) {
314             throw new InitializationException("Could not not analyze local network interfaces: " + ex, ex);
315         }
316     }
317 
318     /**
319      * Validation of every discovered network interface.
320      * <p>
321      * Override this method to customize which network interfaces are used.
322      * </p>
323      * <p>
324      * The given implementation ignores interfaces which are
325      * </p>
326      * <ul>
327      * <li>loopback (yes, we do not bind to lo0)</li>
328      * <li>down</li>
329      * <li>have no bound IP addresses</li>
330      * <li>named "vmnet*" (OS X VMWare does not properly stop interfaces when it quits)</li>
331      * <li>named "vnic*" (OS X Parallels interfaces should be ignored as well)</li>
332      * <li>named "vboxnet*" (OS X Virtual Box interfaces should be ignored as well)</li>
333      * <li>named "*virtual*" (VirtualBox interfaces, for example</li>
334      * <li>named "ppp*"</li>
335      * </ul>
336      *
337      * @param iface The interface to validate.
338      * @return True if the given interface matches all validation criteria.
339      * @throws Exception If any validation test failed with an un-recoverable error.
340      */
341     protected boolean isUsableNetworkInterface(NetworkInterface iface) throws Exception {
342         if (!iface.isUp()) {
343             log.finer("Skipping network interface (down): " + iface.getDisplayName());
344             return false;
345         }
346 
347         if (getInetAddresses(iface).size() == 0) {
348             log.finer("Skipping network interface without bound IP addresses: " + iface.getDisplayName());
349             return false;
350         }
351 
352         if (iface.getName().toLowerCase(Locale.ROOT).startsWith("vmnet") ||
353         		(iface.getDisplayName() != null &&  iface.getDisplayName().toLowerCase(Locale.ROOT).contains("vmnet"))) {
354             log.finer("Skipping network interface (VMWare): " + iface.getDisplayName());
355             return false;
356         }
357 
358         if (iface.getName().toLowerCase(Locale.ROOT).startsWith("vnic")) {
359             log.finer("Skipping network interface (Parallels): " + iface.getDisplayName());
360             return false;
361         }
362 
363         if (iface.getName().toLowerCase(Locale.ROOT).startsWith("vboxnet")) {
364             log.finer("Skipping network interface (Virtual Box): " + iface.getDisplayName());
365             return false;
366         }
367 
368         if (iface.getName().toLowerCase(Locale.ROOT).contains("virtual")) {
369             log.finer("Skipping network interface (named '*virtual*'): " + iface.getDisplayName());
370             return false;
371         }
372 
373         if (iface.getName().toLowerCase(Locale.ROOT).startsWith("ppp")) {
374             log.finer("Skipping network interface (PPP): " + iface.getDisplayName());
375             return false;
376         }
377 
378         if (iface.isLoopback()) {
379             log.finer("Skipping network interface (ignoring loopback): " + iface.getDisplayName());
380             return false;
381         }
382 
383         if (useInterfaces.size() > 0 && !useInterfaces.contains(iface.getName())) {
384             log.finer("Skipping unwanted network interface (-D" + SYSTEM_PROPERTY_NET_IFACES + "): " + iface.getName());
385             return false;
386         }
387 
388         if (!iface.supportsMulticast())
389             log.warning("Network interface may not be multicast capable: "  + iface.getDisplayName());
390 
391         return true;
392     }
393 
394     protected void discoverBindAddresses() throws InitializationException {
395         try {
396 
397             synchronized (networkInterfaces) {
398                 Iterator<NetworkInterface> it = networkInterfaces.iterator();
399                 while (it.hasNext()) {
400                     NetworkInterface networkInterface = it.next();
401 
402                     log.finer("Discovering addresses of interface: " + networkInterface.getDisplayName());
403                     int usableAddresses = 0;
404                     for (InetAddress inetAddress : getInetAddresses(networkInterface)) {
405                         if (inetAddress == null) {
406                             log.warning("Network has a null address: " + networkInterface.getDisplayName());
407                             continue;
408                         }
409 
410                         if (isUsableAddress(networkInterface, inetAddress)) {
411                             log.fine("Discovered usable network interface address: " + inetAddress.getHostAddress());
412                             usableAddresses++;
413                             synchronized (bindAddresses) {
414                                 bindAddresses.add(inetAddress);
415                             }
416                         } else {
417                             log.finer("Ignoring non-usable network interface address: " + inetAddress.getHostAddress());
418                         }
419                     }
420 
421                     if (usableAddresses == 0) {
422                         log.finer("Network interface has no usable addresses, removing: " + networkInterface.getDisplayName());
423                         it.remove();
424                     }
425                 }
426             }
427 
428         } catch (Exception ex) {
429             throw new InitializationException("Could not not analyze local network interfaces: " + ex, ex);
430         }
431     }
432 
433     /**
434      * Validation of every discovered local address.
435      * <p>
436      * Override this method to customize which network addresses are used.
437      * </p>
438      * <p>
439      * The given implementation ignores addresses which are
440      * </p>
441      * <ul>
442      * <li>not IPv4</li>
443      * <li>the local loopback (yes, we ignore 127.0.0.1)</li>
444      * </ul>
445      *
446      * @param networkInterface The interface to validate.
447      * @param address The address of this interface to validate.
448      * @return True if the given address matches all validation criteria.
449      */
450     protected boolean isUsableAddress(NetworkInterface networkInterface, InetAddress address) {
451         if (!(address instanceof Inet4Address)) {
452             log.finer("Skipping unsupported non-IPv4 address: " + address);
453             return false;
454         }
455 
456         if (address.isLoopbackAddress()) {
457             log.finer("Skipping loopback address: " + address);
458             return false;
459         }
460 
461         if (useAddresses.size() > 0 && !useAddresses.contains(address.getHostAddress())) {
462             log.finer("Skipping unwanted address: " + address);
463             return false;
464         }
465 
466         return true;
467     }
468 
469     protected void logInterfaceInformation(NetworkInterface networkInterface) throws SocketException {
470         log.info("---------------------------------------------------------------------------------");
471         log.info(String.format("Interface display name: %s", networkInterface.getDisplayName()));
472         if (networkInterface.getParent() != null)
473             log.info(String.format("Parent Info: %s", networkInterface.getParent()));
474         log.info(String.format("Name: %s", networkInterface.getName()));
475 
476         Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
477 
478         for (InetAddress inetAddress : Collections.list(inetAddresses)) {
479             log.info(String.format("InetAddress: %s", inetAddress));
480         }
481 
482         List<InterfaceAddress> interfaceAddresses = networkInterface.getInterfaceAddresses();
483 
484         for (InterfaceAddress interfaceAddress : interfaceAddresses) {
485             if (interfaceAddress == null) {
486                 log.warning("Skipping null InterfaceAddress!");
487                 continue;
488             }
489             log.info(" Interface Address");
490             log.info("  Address: " + interfaceAddress.getAddress());
491             log.info("  Broadcast: " + interfaceAddress.getBroadcast());
492             log.info("  Prefix length: " + interfaceAddress.getNetworkPrefixLength());
493         }
494 
495         Enumeration<NetworkInterface> subIfs = networkInterface.getSubInterfaces();
496 
497         for (NetworkInterface subIf : Collections.list(subIfs)) {
498             if (subIf == null) {
499                 log.warning("Skipping null NetworkInterface sub-interface");
500                 continue;
501             }
502             log.info(String.format("\tSub Interface Display name: %s", subIf.getDisplayName()));
503             log.info(String.format("\tSub Interface Name: %s", subIf.getName()));
504         }
505         log.info(String.format("Up? %s", networkInterface.isUp()));
506         log.info(String.format("Loopback? %s", networkInterface.isLoopback()));
507         log.info(String.format("PointToPoint? %s", networkInterface.isPointToPoint()));
508         log.info(String.format("Supports multicast? %s", networkInterface.supportsMulticast()));
509         log.info(String.format("Virtual? %s", networkInterface.isVirtual()));
510         log.info(String.format("Hardware address: %s", Arrays.toString(networkInterface.getHardwareAddress())));
511         log.info(String.format("MTU: %s", networkInterface.getMTU()));
512     }
513 }