/*
* Copyright 2011 Thomas Bocek
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package net.tomp2p.connection;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import net.tomp2p.futures.FutureDone;
/**
* A class to search for addresses to bind the sockets to. The user first
* creates a {@link Bindings} instance, provides all the necessary information
* and then calls {@link #discoverInterfaces(Bindings)}. The results are stored
* in the {@link Bindings} instance as well.
*
* @author Thomas Bocek
*/
public final class DiscoverNetworks {
private final Collection<DiscoverNetworkListener> listeners = new ArrayList<DiscoverNetworkListener>(1);
private final int checkIntervalMillis;
private final Bindings bindings;
private final ScheduledExecutorService timer;
private ScheduledFuture<?> future;
private boolean stopped = false;
private volatile DiscoverResults discoverResults = null;
public DiscoverNetworks(final int checkIntervalMillis, Bindings bindings, ScheduledExecutorService timer) {
this.checkIntervalMillis = checkIntervalMillis;
this.bindings = bindings;
this.timer = timer;
}
public FutureDone<Void> start() {
final FutureDone<Void> futureDone = new FutureDone<Void>();
// the thread could be executed faster than the initialization of the future variable, which would result in an NPE
final CountDownLatch futureExistsLatch = new CountDownLatch(1);
future = timer.scheduleWithFixedDelay(new Runnable() {
private boolean firstRun = true;
@Override
public void run() {
try {
discoverResults = discoverInterfaces(bindings, discoverResults);
if(discoverResults.isListenAny()) {
futureExistsLatch.await();
future.cancel(false);
notifyDiscoverNetwork(discoverResults);
} else if(!discoverResults.isEmpty()) {
notifyDiscoverNetwork(discoverResults);
}
if(firstRun) {
futureDone.done();
firstRun = false;
}
} catch (Throwable t) {
futureDone.done();
notifyFailure(t);
}
}
}, 0, checkIntervalMillis, TimeUnit.MILLISECONDS);
futureExistsLatch.countDown();
return futureDone;
}
public DiscoverResults currentDiscoverResults() {
return discoverResults;
}
public void stop () {
synchronized (this) {
stopped = true;
if(future!=null) {
future.cancel(false);
}
}
}
public DiscoverNetworks addDiscoverNetworkListener(DiscoverNetworkListener listener) {
listeners.add(listener);
return this;
}
public DiscoverNetworks removeDiscoverNetworkListener(DiscoverNetworkListener listener) {
listeners.remove(listener);
return this;
}
private void notifyDiscoverNetwork(DiscoverResults discoverResults) {
synchronized (this) {
if(stopped) {
return;
}
for (DiscoverNetworkListener listener : listeners) {
listener.discoverNetwork(discoverResults);
}
}
}
private void notifyFailure(Throwable throwable) {
synchronized (this) {
if(stopped) {
return;
}
for (DiscoverNetworkListener listener : listeners) {
listener.exception(throwable);
}
}
}
public static DiscoverResults discoverInterfaces(final Bindings bindings) throws IOException {
return discoverInterfaces(bindings, null);
}
/**
* Search for local interfaces. Hints how to search for those interfaces are
* provided by the user through the {@link Bindings} class. The results of
* that search (InetAddress) are stored in {@link Bindings} as well.
*
* @param bindings
* The hints for the search and also the results are stored there
* @param oldDiscoverResults
*
* @return The status of the search
* @throws IOException
* If anything goes wrong, such as reflection.
*/
public static DiscoverResults discoverInterfaces(final Bindings bindings, DiscoverResults oldDiscoverResults) throws IOException {
final Collection<InetAddress> existingAddresses = new ArrayList<InetAddress>();
final Collection<InetAddress> existingBroadcastAddresses= new ArrayList<InetAddress>();
final Collection<InetAddress> existingAddressesOld = new ArrayList<InetAddress>();
final Collection<InetAddress> existingBroadcastAddressesOld= new ArrayList<InetAddress>();
if(oldDiscoverResults!=null) {
existingAddresses.addAll(oldDiscoverResults.existingAddresses());
existingBroadcastAddresses.addAll(oldDiscoverResults.existingBroadcastAddresses());
existingAddressesOld.addAll(oldDiscoverResults.existingAddresses());
existingBroadcastAddressesOld.addAll(oldDiscoverResults.existingBroadcastAddresses());
}
StringBuilder sb = new StringBuilder("Discover status: ");
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
NetworkInterface networkInterface = e.nextElement();
if (bindings.anyInterfaces()) {
sb.append(" ++").append(networkInterface.getName());
DiscoverResults discoverResults = discoverNetwork(networkInterface, existingAddressesOld,
existingBroadcastAddressesOld, bindings.isIPv4(), bindings.isIPv6(), bindings.addresses());
sb.append(discoverResults.status()).append(",");
addIfNotPresent(existingAddresses, discoverResults.existingAddresses());
addIfNotPresent(existingBroadcastAddresses, discoverResults.existingBroadcastAddresses());
} else {
if (bindings.containsInterface(networkInterface.getName())) {
sb.append(" +").append(networkInterface.getName());
DiscoverResults discoverResults = discoverNetwork(networkInterface, existingAddressesOld,
existingBroadcastAddressesOld, bindings.isIPv4(), bindings.isIPv6(), bindings.addresses());
sb.append(discoverResults.status()).append(",");
addIfNotPresent(existingAddresses, discoverResults.existingAddresses());
addIfNotPresent(existingBroadcastAddresses, discoverResults.existingBroadcastAddresses());
} else {
sb.append(" -").append(networkInterface.getName()).append(",");
}
}
}
// remove the last comma or space
sb.deleteCharAt(sb.length() - 1);
sb.append(".");
//find new results left-intersect
final Collection<InetAddress> newAddresses = new ArrayList<InetAddress>();
for(InetAddress existingAddress:existingAddresses) {
if(!existingAddressesOld.contains(existingAddress)) {
newAddresses.add(existingAddress);
}
}
final Collection<InetAddress> newBroadcastAddresses= new ArrayList<InetAddress>();
for(InetAddress existingBroadcastAddress:existingBroadcastAddresses) {
if(!existingBroadcastAddressesOld.contains(existingBroadcastAddress)) {
newBroadcastAddresses.add(existingBroadcastAddress);
}
}
//find removed results right-intersect
final Collection<InetAddress> removedFoundAddresses= new ArrayList<InetAddress>();
for(InetAddress existingAddressOld:existingAddressesOld) {
if(!existingAddresses.contains(existingAddressOld)) {
removedFoundAddresses.add(existingAddressOld);
}
}
final Collection<InetAddress> removedFoundBroadcastAddresses= new ArrayList<InetAddress>();
for(InetAddress existingBroadcastAddressOld:existingBroadcastAddressesOld) {
if(!existingBroadcastAddresses.contains(existingBroadcastAddressOld)) {
removedFoundBroadcastAddresses.add(existingBroadcastAddressOld);
}
}
DiscoverResults discoverResults = new DiscoverResults(newAddresses, newBroadcastAddresses,
removedFoundAddresses, removedFoundBroadcastAddresses, existingAddresses, existingBroadcastAddresses, bindings.isListenAny(), sb.toString());
return discoverResults;
}
private static void addIfNotPresent(Collection<InetAddress> existingAddresses,
Collection<InetAddress> existingAddresses2) {
for(InetAddress inet:existingAddresses2) {
if(!existingAddresses.contains(inet)) {
existingAddresses.add(inet);
}
}
}
/**
* Discovers network interfaces and addresses.
*
* @param networkInterface
* The networkInterface to search for addresses to listen to
* @param foundAddresses
*
* @param foundBroadcastAddresses
*
* @param isIPv4
*
* @param isIPv6
*
* @return The status of the discovery
*/
public static DiscoverResults discoverNetwork(final NetworkInterface networkInterface,
final Collection<InetAddress> foundAddresses, Collection<InetAddress> foundBroadcastAddresses,
boolean isIPv4, boolean isIPv6, List<InetAddress> requestedInetAddress) {
final Collection<InetAddress> foundAddresses2 = new ArrayList<InetAddress>(foundAddresses);
final Collection<InetAddress> foundBroadcastAddresses2 = new ArrayList<InetAddress>(
foundBroadcastAddresses);
//final Collection<InetAddress> newAddresses = new ArrayList<InetAddress>();
//final Collection<InetAddress> newBroadcastAddresses = new ArrayList<InetAddress>();
StringBuilder sb = new StringBuilder("( ");
for (InterfaceAddress iface : networkInterface.getInterfaceAddresses()) {
// reported by Vasiliy:
// iface == null happens when connecting to the Internet through
// my mobile operator. In this case additional dial-up connection
// is created in Network Connections (on Windows)
if (iface == null) {
continue;
}
InetAddress inet = iface.getAddress();
InetAddress broadcast = iface.getBroadcast();
// could be a bug, but on travis-ci I get an any address as a broadcast address, maybe reletade to this:
// http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7158636
if (broadcast != null && !broadcast.isAnyLocalAddress()) {
if(!foundBroadcastAddresses2.contains(broadcast)) {
foundBroadcastAddresses2.add(broadcast);
}
}
if (inet instanceof Inet4Address && isIPv4) {
sb.append(inet).append(",");
if(!foundAddresses2.contains(inet)) {
if(requestedInetAddress.isEmpty() || requestedInetAddress.contains(inet)) {
foundAddresses2.add(inet);
}
}
} else if (inet instanceof Inet6Address && isIPv6) {
sb.append(inet).append(",");
if(!foundAddresses2.contains(inet)) {
if(requestedInetAddress.isEmpty() || requestedInetAddress.contains(inet)) {
foundAddresses2.add(inet);
}
}
}
}
sb.deleteCharAt(sb.length() - 1);
sb.append(")");
DiscoverResults discoverResults = new DiscoverResults(null, null,
null, null, foundAddresses2, foundBroadcastAddresses2, false, sb.toString());
return discoverResults;
}
}