/*
* Copyright 2012 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.p2p.builder;
import java.net.InetAddress;
import java.util.Collection;
import net.tomp2p.connection.Bindings;
import net.tomp2p.connection.ChannelCreator;
import net.tomp2p.connection.ConnectionConfiguration;
import net.tomp2p.connection.DefaultConnectionConfiguration;
import net.tomp2p.connection.DiscoverNetworks;
import net.tomp2p.connection.DiscoverResults;
import net.tomp2p.connection.Ports;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureChannelCreator;
import net.tomp2p.futures.FutureDiscover;
import net.tomp2p.futures.FutureDone;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.futures.Futures;
import net.tomp2p.message.Message.Type;
import net.tomp2p.p2p.Peer;
import net.tomp2p.p2p.PeerReachable;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerSocketAddress.PeerSocket4Address;
import net.tomp2p.peers.IP.IPv4;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DiscoverBuilder {
final private static Logger LOG = LoggerFactory.getLogger(DiscoverBuilder.class);
final private static FutureDiscover FUTURE_DISCOVER_SHUTDOWN = new FutureDiscover()
.failed("Peer is shutting down.");
final private Peer peer;
private InetAddress inetAddress;
private int portUDP = Ports.DEFAULT_PORT;
private int portTCP = Ports.DEFAULT_PORT;
//private int portUDT = Ports.DEFAULT_PORT + 1;
private PeerAddress peerAddress;
private int discoverTimeoutSec = 5;
private ConnectionConfiguration configuration;
private FutureDiscover futureDiscover;
private boolean expectManualForwarding;
public DiscoverBuilder(Peer peer) {
this.peer = peer;
}
public InetAddress inetAddress() {
return inetAddress;
}
public DiscoverBuilder inetAddress(InetAddress inetAddress) {
this.inetAddress = inetAddress;
return this;
}
public DiscoverBuilder inetSocketAddress(InetAddress inetAddress, int port) {
this.inetAddress = inetAddress;
this.portTCP = port;
this.portUDP = port;
return this;
}
public DiscoverBuilder inetSocketAddress(InetAddress inetAddress, int portTCP, int portUDP) {
this.inetAddress = inetAddress;
this.portTCP = portTCP;
this.portUDP = portUDP;
return this;
}
public DiscoverBuilder peerSocketAddress(PeerSocket4Address peerSocketAddress) {
this.inetAddress = peerSocketAddress.ipv4().toInetAddress();
this.portTCP = peerSocketAddress.tcpPort();
this.portUDP = peerSocketAddress.udpPort();
return this;
}
public int portUDP() {
return portUDP;
}
public DiscoverBuilder portUDP(int portUDP) {
this.portUDP = portUDP;
return this;
}
public int portTCP() {
return portTCP;
}
public DiscoverBuilder portTCP(int portTCP) {
this.portTCP = portTCP;
return this;
}
public DiscoverBuilder ports(int port) {
this.portTCP = port;
this.portUDP = port;
return this;
}
public PeerAddress peerAddress() {
return peerAddress;
}
public DiscoverBuilder peerAddress(PeerAddress peerAddress) {
this.peerAddress = peerAddress;
return this;
}
public int discoverTimeoutSec() {
return discoverTimeoutSec;
}
public DiscoverBuilder discoverTimeoutSec(int discoverTimeoutSec) {
this.discoverTimeoutSec = discoverTimeoutSec;
return this;
}
public FutureDiscover futureDiscover() {
return futureDiscover;
}
public DiscoverBuilder futureDiscover(FutureDiscover futureDiscover) {
this.futureDiscover = futureDiscover;
return this;
}
public boolean isExpectManualForwarding() {
return expectManualForwarding;
}
public DiscoverBuilder expectManualForwarding() {
return setExpectManualForwarding(true);
}
public DiscoverBuilder setExpectManualForwarding(boolean expectManualForwarding) {
this.expectManualForwarding = expectManualForwarding;
return this;
}
public FutureDiscover start() {
if (peer.isShutdown()) {
return FUTURE_DISCOVER_SHUTDOWN;
}
if (peerAddress == null && inetAddress != null) {
PeerSocket4Address psa = PeerSocket4Address.builder().ipv4(IPv4.fromInet4Address(inetAddress)).tcpPort(portTCP).udpPort(portUDP).build();
peerAddress = PeerAddress.builder().ipv4Socket(psa).peerId(Number160.ZERO).build();
}
if (peerAddress == null) {
throw new IllegalArgumentException("Peer address or inet address required.");
}
if (configuration == null) {
configuration = new DefaultConnectionConfiguration();
}
if (futureDiscover == null) {
futureDiscover = new FutureDiscover();
}
return discover(peerAddress, configuration, futureDiscover);
}
/**
* Discover attempts to find the external IP address of this peer. This is done by first trying to set UPNP with
* port forwarding (gives us the external address), query UPNP for the external address, and pinging a well known
* peer. The fallback is NAT-PMP.
*
* @param peerAddress
* The peer address. Since pings are used the peer ID can be Number160.ZERO
* @return The future discover. This future holds also the real ID of the peer we send the discover request
*/
private FutureDiscover discover(final PeerAddress peerAddress, final ConnectionConfiguration configuration,
final FutureDiscover futureDiscover) {
FutureChannelCreator fcc = peer.connectionBean().reservation().create(1, 2);
Utils.addReleaseListener(fcc, futureDiscover);
fcc.addListener(new BaseFutureAdapter<FutureChannelCreator>() {
@Override
public void operationComplete(final FutureChannelCreator future) throws Exception {
if (future.isSuccess()) {
discover(futureDiscover, peerAddress, future.channelCreator(), configuration);
} else {
futureDiscover.failed(future);
}
}
});
return futureDiscover;
}
/**
* Needs 3 connections. Cleans up ChannelCreator, which means they will be released.
*
* @param peerAddress
* @param cc
* @return
*/
private void discover(final FutureDiscover futureDiscover, final PeerAddress peerAddress,
final ChannelCreator cc, final ConnectionConfiguration configuration) {
LOG.debug("starting discover to {}",peerAddress);
final FutureDone<Void> pingDone = new FutureDone<Void>();
peer.pingRPC().addPeerReachableListener(new PeerReachable() {
private volatile boolean changedUDP = false;
private volatile boolean changedTCP = false;
@Override
public void peerWellConnected(final PeerAddress peerAddress, final PeerAddress reporter, final boolean tcp) {
pingDone.addListener(new BaseFutureAdapter<FutureDone<Void>>() {
@Override
public void operationComplete(FutureDone<Void> future) throws Exception {
if (tcp) {
futureDiscover.discoveredTCP();
changedTCP = true;
LOG.debug("TCP discovered");
} else {
futureDiscover.discoveredUDP();
changedUDP = true;
LOG.debug("UDP discovered");
}
if (changedTCP && changedUDP) {
futureDiscover.done(peerAddress, reporter);
}
}
});
}
});
final FutureResponse futureResponseTCP = peer.pingRPC().pingTCPDiscover(peerAddress, cc,
configuration);
futureResponseTCP.addListener(new BaseFutureAdapter<FutureResponse>() {
@Override
public void operationComplete(FutureResponse future) throws Exception {
PeerAddress serverAddress = peer.peerBean().serverPeerAddress();
if (futureResponseTCP.isSuccess() && futureResponseTCP.responseMessage().type() == Type.NOT_FOUND) {
//this was a ping to myself. This is pointless
futureDiscover.failed("FutureDiscover to yourself",
futureResponseTCP);
return;
}
else if (futureResponseTCP.isSuccess()) {
//now we know our internal address, set it as it could be a wrong one, e.g. 127.0.0.1
serverAddress = serverAddress.withIpv4Socket(futureResponseTCP.responseMessage().recipient().ipv4Socket());
Collection<PeerAddress> tmp = futureResponseTCP.responseMessage().neighborsSet(0)
.neighbors();
futureDiscover.reporter(futureResponseTCP.responseMessage().sender());
if (tmp.size() == 1) {
PeerAddress seenAs = tmp.iterator().next();
LOG.info("This peer is seen as {} by peer {}. This peer sees itself as {}.",
seenAs, peerAddress, peer.peerAddress());
if (!peer.peerAddress().ipv4Socket().equalsWithoutPorts(seenAs.ipv4Socket())) {
// check if we have this interface in that we can
// listen to
Bindings bindings2 = new Bindings().addAddress(seenAs.ipv4Socket().ipv4().toInetAddress());
DiscoverResults discoverResults = DiscoverNetworks.discoverInterfaces(bindings2);
String status = discoverResults.status();
LOG.info("2nd interface discovery: {}", status);
if (discoverResults.newAddresses().size() > 0
&& discoverResults.newAddresses().contains(seenAs.ipv4Socket().ipv4().toInetAddress())) {
serverAddress = serverAddress.withIpv4Socket(seenAs.ipv4Socket());
peer.peerBean().serverPeerAddress(serverAddress);
LOG.info("This peer had the wrong interface. Changed it to {}.", serverAddress);
} else {
// now we know our internal IP, where we receive
// packets
final Ports ports = peer.connectionBean().channelServer().channelServerConfiguration().portsForwarding();
if (ports.isManualPort()) {
final PeerAddress serverAddressOrig = serverAddress;
PeerSocket4Address serverSocket = serverAddress.ipv4Socket();
serverSocket = serverSocket.withTcpPort(ports.tcpPort()).withUdpPort(ports.udpPort());
serverSocket = serverSocket.withIpv4(seenAs.ipv4Socket().ipv4());
//manual port forwarding detected, set flag
peer.peerBean().serverPeerAddress(serverAddress.withIpv4Socket(serverSocket).withIpInternalSocket(serverAddressOrig.ipv4Socket()));
LOG.info("manual ports, change it to: {}", serverAddress);
} else if(expectManualForwarding) {
final PeerAddress serverAddressOrig = serverAddress;
PeerSocket4Address serverSocket = serverAddress.ipv4Socket();
serverSocket = serverSocket.withIpv4(seenAs.ipv4Socket().ipv4());
peer.peerBean().serverPeerAddress(serverAddress.withIpv4Socket(serverSocket).withIpInternalSocket(serverAddressOrig.ipv4Socket()));
LOG.info("we were manually forwarding, change it to: {}", serverAddress);
}
else {
// we need to find a relay, because there is a NAT in the way.
// we cannot use futureResponseTCP.responseMessage().recipient() as this may return also IPv6 addresses
LOG.info("We are most likely behind NAT, try to UPNP, NATPMP or relay. PeerAddress: {}, ServerAddress: {}, Seen as: {}" + peerAddress, serverAddress, seenAs);
futureDiscover.externalHost("We are most likely behind NAT, try to UPNP, NATPMP or relay. Using peerAddress " + peerAddress, serverAddress.ipv4Socket(), seenAs.ipv4Socket());
return;
}
}
}
// else -> we announce exactly how the other peer sees
// us
FutureResponse fr1 = peer.pingRPC().pingTCPProbe(peerAddress, cc,
configuration);
fr1.addListener(new BaseFutureAdapter<FutureResponse>() {
@Override
public void operationComplete(FutureResponse future) throws Exception {
if(future.isFailed() ) {
LOG.warn("FutureDiscover (2): We need at least the TCP connection {} - {}", future, futureDiscover.failedReason());
futureDiscover.failed("FutureDiscover (2): We need at least the TCP connection", future);
}
}
});
FutureResponse fr2 = peer.pingRPC().pingUDPProbe(peerAddress, cc,
configuration);
fr2.addListener(new BaseFutureAdapter<FutureResponse>() {
@Override
public void operationComplete(FutureResponse future) throws Exception {
if(future.isFailed() ) {
LOG.warn("FutureDiscover (2): UDP failed connection {} - {}", future, futureDiscover.failedReason());
}
}
});
Futures.whenAll(fr1, fr2).addListener(new BaseFutureAdapter<FutureDone<FutureResponse[]>>() {
@Override
public void operationComplete(FutureDone<FutureResponse[]> future) throws Exception {
pingDone.done();
}
});
// from here we probe, set the timeout here
futureDiscover.timeout(serverAddress, peer.connectionBean().timer(), discoverTimeoutSec);
return;
} else {
futureDiscover.failed("Peer " + peerAddress + " did not report our IP address.");
return;
}
} else {
futureDiscover.failed("FutureDiscover (1): We need at least the TCP connection",
futureResponseTCP);
return;
}
}
});
}
}