Package net.tomp2p.nat

Source Code of net.tomp2p.nat.PeerNAT

package net.tomp2p.nat;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import net.tomp2p.connection.ConnectionConfiguration;
import net.tomp2p.connection.PeerConnection;
import net.tomp2p.connection.Ports;
import net.tomp2p.futures.BaseFuture;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureBootstrap;
import net.tomp2p.futures.FutureDiscover;
import net.tomp2p.futures.FutureDone;
import net.tomp2p.futures.FuturePeerConnection;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Message;
import net.tomp2p.message.Message.Type;
import net.tomp2p.natpmp.NatPmpException;
import net.tomp2p.p2p.Peer;
import net.tomp2p.p2p.Shutdown;
import net.tomp2p.p2p.builder.BootstrapBuilder;
import net.tomp2p.p2p.builder.DiscoverBuilder;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.relay.BaseRelayConnection;
import net.tomp2p.relay.DistributedRelay;
import net.tomp2p.relay.FutureRelay;
import net.tomp2p.relay.PeerMapUpdateTask;
import net.tomp2p.relay.RelayListener;
import net.tomp2p.relay.RelayRPC;
import net.tomp2p.relay.RelayType;
import net.tomp2p.relay.RelayUtils;
import net.tomp2p.relay.android.GCMServerCredentials;
import net.tomp2p.rpc.RPC;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PeerNAT {

  private static final Logger LOG = LoggerFactory.getLogger(PeerNAT.class);

  private final Peer peer;
  private final NATUtils natUtils;
  private final RelayRPC relayRPC;
  private final int failedRelayWaitTime;
  private final int maxFail;
  private final boolean manualPorts;
  private final ConnectionConfiguration config;
 
  // relaying
  private final int peerMapUpdateInterval;
  private final Collection<PeerAddress> manualRelays;
  private final DistributedRelay distributedRelay;
  private final RelayType relayType;
 
  private static final int MESSAGE_VERSION = 1;


  public PeerNAT(Peer peer, NATUtils natUtils, RelayRPC relayRPC, Collection<PeerAddress> manualRelays,
      int failedRelayWaitTime, int maxFail, int peerMapUpdateInterval, boolean manualPorts, RelayType relayType,
      GCMServerCredentials gcmServerCredentials, ConnectionConfiguration config) {
    this.peer = peer;
    this.natUtils = natUtils;
    this.relayRPC = relayRPC;
    this.manualRelays = manualRelays;
    this.failedRelayWaitTime = failedRelayWaitTime;
    this.maxFail = maxFail;
    this.peerMapUpdateInterval = peerMapUpdateInterval;
    this.manualPorts = manualPorts;
    this.relayType = relayType;
    this.config = config;
    this.distributedRelay = new DistributedRelay(peer, relayRPC, failedRelayWaitTime, relayType, gcmServerCredentials, peerMapUpdateInterval, config);
  }

  public Peer peer() {
    return peer;
  }

  public NATUtils natUtils() {
    return natUtils;
  }

  public RelayRPC relayRPC() {
    return relayRPC;
  }

  /**
   * @return the peer map update interval in seconds
   */
  public int peerMapUpdateInterval() {
    return peerMapUpdateInterval;
  }

  /**
   * @return How many seconds to wait at least until asking a relay that
   *         denied a relay request or a relay that failed to act as a relay
   *         again
   */
  public int failedRelayWaitTime() {
    return failedRelayWaitTime;
  }

  public int maxFail() {
    return maxFail;
  }

  public void addRelay(PeerAddress relay) {
    synchronized (manualRelays) {
      manualRelays.add(relay);
    }
  }

  public Collection<PeerAddress> manualRelays() {
    return manualRelays;
  }

  public boolean isManualPorts() {
    return manualPorts;
  }
 
  public Collection<BaseRelayConnection> currentRelays() {
    return distributedRelay.relays();
  }

  /**
   * Setup UPNP or NATPMP port forwarding.
   *
   * @param futureDiscover
   *            The result of the discovery process. This information from the
   *            discovery process is important to setup UPNP or NATPMP. If
   *            this fails, then this future will also fail, and other means
   *            to connect to the network needs to be found.
   * @return The future object that tells you if you are reachable (success),
   *         if UPNP or NATPMP could be setup and then you are reachable
   *         (success), or if it failed.
   */
  public FutureNAT startSetupPortforwarding(final FutureDiscover futureDiscover) {
    final FutureNAT futureNAT = new FutureNAT();
    futureDiscover.addListener(new BaseFutureAdapter<FutureDiscover>() {

      @Override
      public void operationComplete(FutureDiscover future) throws Exception {

        // set the peer that we contacted
        if (future.reporter() != null) {
          futureNAT.reporter(future.reporter());
        }

        if (future.isFailed() && future.isNat() && !isManualPorts()) {
          Ports externalPorts = setupPortforwarding(future.internalAddress().getHostAddress(), peer
              .connectionBean().channelServer().channelServerConfiguration().portsForwarding());
          if (externalPorts != null) {
            final PeerAddress serverAddressOrig = peer.peerBean().serverPeerAddress();
            final PeerAddress serverAddress = serverAddressOrig
                .changePorts(externalPorts.tcpPort(), externalPorts.udpPort())
                .changeAddress(future.externalAddress());
           
            //set the new address regardless wheter it will succeed or not.
            // The discover will eventually check wheter the announced ip matches the one that it sees.
            peer.peerBean().serverPeerAddress(serverAddress);
           
            // test with discover again
            DiscoverBuilder builder = new DiscoverBuilder(peer).peerAddress(futureNAT.reporter());
            builder.start().addListener(new BaseFutureAdapter<FutureDiscover>() {
              @Override
              public void operationComplete(FutureDiscover future) throws Exception {
                if (future.isSuccess()) {
                  //UPNP or NAT-PMP was successful, set flag
                  peer.peerBean().serverPeerAddress(serverAddress.changePortForwarding(true));
                  peer.peerBean().serverPeerAddress().internalPeerSocketAddress(serverAddressOrig.peerSocketAddress());
                  futureNAT.done(future.peerAddress(), future.reporter());
                } else {
                  // indicate relay
                  PeerAddress pa = peer.peerBean().serverPeerAddress().changeFirewalledTCP(true)
                      .changeFirewalledUDP(true);
                  peer.peerBean().serverPeerAddress(pa);
                  futureNAT.failed(future);
                }
              }
            });
          } else {
            // indicate relay
            PeerAddress pa = peer.peerBean().serverPeerAddress().changeFirewalledTCP(true)
                .changeFirewalledUDP(true);
            peer.peerBean().serverPeerAddress(pa);
            futureNAT.failed("could not setup NAT");
          }
        } else if (future.isSuccess()) {
          LOG.info("nothing to do, you are reachable from outside");
          futureNAT.done(futureDiscover.peerAddress(), futureDiscover.reporter());
        } else {
          LOG.info("not reachable, maybe your setup is wrong");
          futureNAT.failed("could discover anything", future);
        }
      }
    });
    return futureNAT;
  }

  /**
   * The Dynamic and/or Private Ports are those from 49152 through 65535
   * (http://www.iana.org/assignments/port-numbers).
   *
   * @param internalHost
   *            The IP of the internal host
   * @return The new external ports if port forwarding seemed to be
   *         successful, otherwise null
   */
  public Ports setupPortforwarding(final String internalHost, Ports ports) {
    // new random ports
    boolean success;

    try {
      success = natUtils.mapUPNP(internalHost, peer.peerAddress().tcpPort(), peer.peerAddress().udpPort(),
          ports.udpPort(), ports.tcpPort());
    } catch (Exception e) {
      success = false;
    }

    if (!success) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("cannot find UPNP devices");
      }
      try {
        success = natUtils.mapPMP(peer.peerAddress().tcpPort(), peer.peerAddress().udpPort(), ports.udpPort(),
            ports.tcpPort());
        if (!success) {
          if (LOG.isWarnEnabled()) {
            LOG.warn("cannot find NAT-PMP devices");
          }
        }
      } catch (NatPmpException e1) {
        if (LOG.isWarnEnabled()) {
          LOG.warn("cannot find NAT-PMP devices ", e1);
        }
      }
    }
    if (success) {
      return ports;
    }
    return null;
  }

  private DistributedRelay startSetupRelay(FutureRelay futureRelay) {
    // close the relay connection when the peer is shutdown
    peer.addShutdownListener(new Shutdown() {
      @Override
      public BaseFuture shutdown() {
        return distributedRelay.shutdown();
      }
    });

    // open new relay when one failed
    distributedRelay.addRelayListener(new RelayListener() {
      @Override
      public void relayFailed(PeerAddress relayAddress) {
        // one failed, add new one
        final FutureRelay futureRelay2 = new FutureRelay();
        distributedRelay.setupRelays(futureRelay2, manualRelays, maxFail);
        peer.notifyAutomaticFutures(futureRelay2);
      }
    });

    distributedRelay.setupRelays(futureRelay, manualRelays, maxFail);
    return distributedRelay;
  }

  private Shutdown startRelayMaintenance(final FutureRelay futureRelay, BootstrapBuilder bootstrapBuilder,
      DistributedRelay distributedRelay) {
    final PeerMapUpdateTask peerMapUpdateTask = new PeerMapUpdateTask(relayRPC, bootstrapBuilder, distributedRelay,
        manualRelays, maxFail);
    peer.connectionBean().timer().scheduleAtFixedRate(peerMapUpdateTask, 0, peerMapUpdateInterval(), TimeUnit.SECONDS);

    final Shutdown shutdown = new Shutdown() {
      @Override
      public BaseFuture shutdown() {
        peerMapUpdateTask.cancel();
        return new FutureDone<Void>().done();
      }
    };
    peer.addShutdownListener(shutdown);

    return new Shutdown() {
      @Override
      public BaseFuture shutdown() {
        peerMapUpdateTask.cancel();
        peer.removeShutdownListener(shutdown);
        return new FutureDone<Void>().done();
      }
    };
  }

  public FutureRelayNAT startRelay(final PeerAddress peerAddress) {
    final BootstrapBuilder bootstrapBuilder = peer.bootstrap().peerAddress(peerAddress);
    return startRelay(bootstrapBuilder);
  }

  public FutureRelayNAT startRelay(BootstrapBuilder bootstrapBuilder) {
    final FutureRelayNAT futureBootstrapNAT = new FutureRelayNAT();
    return startRelay(futureBootstrapNAT, bootstrapBuilder);
  }

  public FutureRelayNAT startRelay(final FutureDiscover futureDiscover) {
    return startRelay(futureDiscover, null);
  }

  public FutureRelayNAT startRelay(final FutureDiscover futureDiscover, final FutureNAT futureNAT) {
    final FutureRelayNAT futureRelayNAT = new FutureRelayNAT();
    futureDiscover.addListener(new BaseFutureAdapter<FutureDiscover>() {
      @Override
      public void operationComplete(FutureDiscover future) throws Exception {
        if (future.isFailed()) {
          if (futureNAT != null) {
            handleFutureNat(futureDiscover.reporter(), futureNAT, futureRelayNAT);
          } else {
            BootstrapBuilder bootstrapBuilder = peer.bootstrap().peerAddress(futureDiscover.reporter());
            startRelay(futureRelayNAT, bootstrapBuilder);
          }
        } else {
          futureRelayNAT.done();
        }
      }
    });
    return futureRelayNAT;
  }

  private void handleFutureNat(final PeerAddress peerAddress, final FutureNAT futureNAT,
      final FutureRelayNAT futureRelayNAT) {
    futureNAT.addListener(new BaseFutureAdapter<FutureNAT>() {
      @Override
      public void operationComplete(FutureNAT future) throws Exception {
        if (future.isSuccess()) {
          // setup UPNP or NATPMP worked
          futureRelayNAT.done();
        } else {
          BootstrapBuilder bootstrapBuilder = peer.bootstrap().peerAddress(peerAddress);
          startRelay(futureRelayNAT, bootstrapBuilder);
        }
      }
    });
  }

  private FutureRelayNAT startRelay(final FutureRelayNAT futureBootstrapNAT, final BootstrapBuilder bootstrapBuilder) {
    PeerAddress upa = peer.peerBean().serverPeerAddress();
    upa = upa.changeFirewalledTCP(true).changeFirewalledUDP(true).changeSlow(relayType.isSlow());
    peer.peerBean().serverPeerAddress(upa);
    // find neighbors

    FutureBootstrap futureBootstrap = bootstrapBuilder.start();
    futureBootstrapNAT.futureBootstrap0(futureBootstrap);

    futureBootstrap.addListener(new BaseFutureAdapter<FutureBootstrap>() {
      @Override
      public void operationComplete(FutureBootstrap future) throws Exception {
        if (future.isSuccess()) {
          // setup relay
          LOG.debug("bootstrap completed");
          final FutureRelay futureRelay = new FutureRelay();
          final DistributedRelay distributedRelay = startSetupRelay(futureRelay);
          futureBootstrapNAT.futureRelay(futureRelay);
          futureRelay.addListener(new BaseFutureAdapter<FutureRelay>() {

            @Override
            public void operationComplete(FutureRelay future) throws Exception {
              // find neighbors again
              if (future.isSuccess()) {
                FutureBootstrap futureBootstrap = bootstrapBuilder.start();
                futureBootstrapNAT.futureBootstrap1(futureBootstrap);
                futureBootstrap.addListener(new BaseFutureAdapter<FutureBootstrap>() {
                  @Override
                  public void operationComplete(FutureBootstrap future) throws Exception {
                    if (future.isSuccess()) {
                      Shutdown shutdown = startRelayMaintenance(futureRelay, bootstrapBuilder, distributedRelay);
                      futureBootstrapNAT.done(shutdown);
                    } else {
                      futureBootstrapNAT.failed("2nd FutureBootstrap failed", future);
                    }
                  }
                });
              } else {
                futureBootstrapNAT.failed("FutureRelay failed", future);
              }
            }
          });
        } else {
          futureBootstrapNAT.failed("FutureBootstrap failed", future);
        }
      }
    });
    return futureBootstrapNAT;
  }

  /**
   * This Method creates a {@link PeerConnection} to an unreachable (behind a NAT) peer using an active
   * relay of the unreachable peer. The connection will be kept open until close() is called.
   *
   * @param relayPeerAddress
   * @param unreachablePeerAddress
   * @param timeoutSeconds
   * @return {@link FutureDone}
   * @throws TimeoutException
   */
  public FutureDone<PeerConnection> startSetupRcon(final PeerAddress relayPeerAddress, final PeerAddress unreachablePeerAddress) {
    checkRconPreconditions(relayPeerAddress, unreachablePeerAddress);

    final FutureDone<PeerConnection> futureDone = new FutureDone<PeerConnection>();
    final FuturePeerConnection fpc = peer.createPeerConnection(relayPeerAddress);
    fpc.addListener(new BaseFutureAdapter<FuturePeerConnection>() {
      // wait for the connection to the relay Peer
      @Override
      public void operationComplete(FuturePeerConnection future) throws Exception {
        if (fpc.isSuccess()) {
          final PeerConnection peerConnection = fpc.peerConnection();
          if (peerConnection != null) {
            // create the necessary messages
            final Message setUpMessage = createSetupMessage(relayPeerAddress, unreachablePeerAddress);

            // send the message to the relay so it forwards it to the unreachable peer
            FutureResponse futureResponse = RelayUtils.send(peerConnection, peer.peerBean(),
                peer.connectionBean(), config, setUpMessage);

            // wait for the unreachable peer to answer
            futureResponse.addListener(new BaseFutureAdapter<FutureResponse>() {
              @Override
              public void operationComplete(FutureResponse future) throws Exception {
                // get the PeerConnection which is cached in the PeerBean object
                final PeerConnection openPeerConnection = peer.peerBean().peerConnection(unreachablePeerAddress.peerId());
                if (openPeerConnection != null && openPeerConnection.isOpen()) {
                  futureDone.done(openPeerConnection);
                } else {
                  LOG.error("The reverse connection to the unreachable peer failed.");
                  handleFail(futureDone, "No reverse connection could be established");
                }

                // can close the connection to the relay peer
                peerConnection.close();
              }
            });
          } else {
            handleFail(futureDone, "The PeerConnection was null!");
          }
        } else {
          handleFail(futureDone, "no channel could be established");
        }
      }

      private void handleFail(final FutureDone<PeerConnection> futureDone, final String failMessage) {
        LOG.error(failMessage);
        futureDone.failed(failMessage);
      }

      // this message is sent to the relay peer to initiate the rcon setup
      private Message createSetupMessage(final PeerAddress relayPeerAddress, final PeerAddress unreachablePeerAddress) {
        Message setUpMessage = new Message();
        setUpMessage.version(MESSAGE_VERSION);
        setUpMessage.sender(peer.peerAddress());
        setUpMessage.recipient(relayPeerAddress.changePeerId(unreachablePeerAddress.peerId()));
        setUpMessage.command(RPC.Commands.RCON.getNr());
        setUpMessage.type(Type.REQUEST_1);
        // setUpMessage.keepAlive(true);
        return setUpMessage;
      }
    });
    return futureDone;
  }

  /**
   * This method checks if a reverse connection setup with startSetupRcon() is
   * possible.
   *
   * @param relayPeerAddress
   * @param unreachablePeerAddress
   * @param timeoutSeconds
   */
  private void checkRconPreconditions(final PeerAddress relayPeerAddress, final PeerAddress unreachablePeerAddress) {
    if (relayPeerAddress == null || unreachablePeerAddress == null) {
      throw new IllegalArgumentException("either the relay PeerAddress or the unreachablePeerAddress or both was/were null!");
    }
   
    // If we are already a relay of the unreachable peer, we shouldn't use a
    // reverse connection setup. It just doesn't make sense!
    if (peer.peerAddress().peerId().equals(relayPeerAddress.peerId())) {
      throw new IllegalStateException(
          "We are alredy a relay for the target peer. We shouldn't use a reverse connection to connect to the targeted peer!");
    }
  }
}
TOP

Related Classes of net.tomp2p.nat.PeerNAT

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.