/* * Copyright 2009 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.rpc; import java.util.ArrayList; import java.util.List; import net.tomp2p.connection.ChannelCreator; import net.tomp2p.connection.ConnectionBean; import net.tomp2p.connection.ConnectionConfiguration; import net.tomp2p.connection.PeerBean; import net.tomp2p.connection.PeerConnection; import net.tomp2p.connection.RequestHandler; import net.tomp2p.connection.Responder; import net.tomp2p.futures.BaseFutureAdapter; import net.tomp2p.futures.FutureChannelCreator; import net.tomp2p.futures.FutureResponse; import net.tomp2p.message.Message; import net.tomp2p.message.Message.Type; import net.tomp2p.message.NeighborSet; import net.tomp2p.p2p.PeerReachable; import net.tomp2p.p2p.PeerReceivedBroadcastPing; import net.tomp2p.peers.Number160; import net.tomp2p.peers.PeerAddress; import net.tomp2p.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The Ping message handler. Also used for NAT detection and other things. * * @author Thomas Bocek * */ public class PingRPC extends DispatchHandler { private static final Logger LOG = LoggerFactory.getLogger(PingRPC.class); public static final int WAIT_TIME = 10 * 1000; private final List<PeerReachable> reachableListeners = new ArrayList<PeerReachable>(1); private final List<PeerReceivedBroadcastPing> receivedBroadcastPingListeners = new ArrayList<PeerReceivedBroadcastPing>( 1); // used for testing and debugging private final boolean enable; private final boolean wait; /** * Creates a new handshake RPC with listeners. * * @param peerBean * The peer bean * @param connectionBean * The connection bean */ public PingRPC(final PeerBean peerBean, final ConnectionBean connectionBean) { this(peerBean, connectionBean, true, true, false); } /** * Constructor that is only called from this class or from testcases. * * @param peerBean * The peer bean * @param connectionBean * The connection bean * @param enable * Used for test cases, set to true in production * @param register * Used for test cases, set to true in production * @param wait * Used for test cases, set to false in production */ PingRPC(final PeerBean peerBean, final ConnectionBean connectionBean, final boolean enable, final boolean register, final boolean wait) { super(peerBean, connectionBean); this.enable = enable; this.wait = wait; if (register) { connectionBean.dispatcher().registerIoHandler(peerBean.serverPeerAddress().peerId(), peerBean.serverPeerAddress().peerId(), this, RPC.Commands.PING.getNr()); } } /** * Ping with UDP or TCP, but do not send yet. * * @param remotePeer * The destination peer * @return the request handler, where we can call sendUDP(), or sendTCP() */ public RequestHandler ping(final PeerAddress remotePeer, final ConnectionConfiguration configuration) { return createHandler(remotePeer, Type.REQUEST_1, configuration); } /** * Ping a UDP peer. * * @param remotePeer * The destination peer * @param channelCreator * The channel creator where we create a UPD channel * @param configuration * * @return The future that will be triggered when we receive an answer or something fails. */ public FutureResponse pingUDP(final PeerAddress remotePeer, final ChannelCreator channelCreator, final ConnectionConfiguration configuration) { LOG.debug("Pinging UDP the remote peer {}.", remotePeer); return ping(remotePeer, configuration).sendUDP(channelCreator); } /** * Ping a UDP peer, but don't expect an answer. * * @param remotePeer * The destination peer * @param channelCreator * The channel creator where we create a UDP channel * @return The future that will be triggered when we receive an answer or something fails. */ public FutureResponse pingTCP(final PeerAddress remotePeer, final ChannelCreator channelCreator, final ConnectionConfiguration configuration) { LOG.debug("ping the remote peer {}", remotePeer); return ping(remotePeer, configuration).sendTCP(channelCreator); } /** * Ping a UDP peer, but don't expect an answer. * * @param remotePeer * The destination peer * @param channelCreator * The channel creator where we create a UPD channel * @return The future that will be triggered when we receive an answer or * something fails. */ public FutureResponse fireUDP(final PeerAddress remotePeer, final ChannelCreator channelCreator, final ConnectionConfiguration configuration) { return createHandler(remotePeer, Type.REQUEST_FF_1, configuration).fireAndForgetUDP(channelCreator); } /** * Ping a TCP peer, but don't expect an answer. * * @param remotePeer * The destination peer * @param channelCreator * The channel creator where we create a TCP channel * @return The future that will be triggered when we receive an answer or something fails. */ public FutureResponse fireTCP(final PeerAddress remotePeer, final ChannelCreator channelCreator, final ConnectionConfiguration configuration) { return createHandler(remotePeer, Type.REQUEST_FF_1, configuration).sendTCP(channelCreator); } /** * Ping a UDP peer and find out how the other peer sees us. * * @param remotePeer * The destination peer * @param channelCreator * The channel creator where we create a UPD channel * @return The future that will be triggered when we receive an answer or something fails. */ public FutureResponse pingUDPDiscover(final PeerAddress remotePeer, final ChannelCreator channelCreator, final ConnectionConfiguration configuration) { final FutureResponse futureResponse = createDiscoverHandler(remotePeer); return new RequestHandler(futureResponse, peerBean(), connectionBean(), configuration) .sendUDP(channelCreator); } /** * Ping a TCP peer and find out how the other peer sees us. * * @param remotePeer * The destination peer * @param channelCreator * The channel creator where we create a TCP channel * @return The future that will be triggered when we receive an answer or something fails. */ public FutureResponse pingTCPDiscover(final PeerAddress remotePeer, final ChannelCreator channelCreator, final ConnectionConfiguration configuration) { final FutureResponse futureResponse = createDiscoverHandler(remotePeer); return new RequestHandler(futureResponse, peerBean(), connectionBean(), configuration) .sendTCP(channelCreator); } /** * Ping a UDP peer and request the other peer to ping us on our public address with a fire and forget * message. * * @param remotePeer * The destination peer * @param channelCreator * The channel creator where we create a UPD channel * @return The future that will be triggered when we receive an answer or something fails. */ public FutureResponse pingUDPProbe(final PeerAddress remotePeer, final ChannelCreator channelCreator, final ConnectionConfiguration configuration) { final Message message = createMessage(remotePeer, RPC.Commands.PING.getNr(), Type.REQUEST_3); FutureResponse futureResponse = new FutureResponse(message); return new RequestHandler(futureResponse, peerBean(), connectionBean(), configuration) .sendUDP(channelCreator); } /** * Ping a TCP peer and request the other peer to ping us on our public address with a fire and forget * message. * * @param remotePeer * The destination peer * @param channelCreator * The channel creator where we create a TCP channel * @return The future that will be triggered when we receive an answer or something fails. */ public FutureResponse pingTCPProbe(final PeerAddress remotePeer, final ChannelCreator channelCreator, final ConnectionConfiguration configuration) { final Message message = createMessage(remotePeer, RPC.Commands.PING.getNr(), Type.REQUEST_3); FutureResponse futureResponse = new FutureResponse(message); return new RequestHandler(futureResponse, peerBean(), connectionBean(), configuration) .sendTCP(channelCreator); } /** * Create a RequestHandler. * * @param remotePeer * The destination peer * @param type * The type of the request * @param configuration * @return The request handler */ private RequestHandler createHandler(final PeerAddress remotePeer, final Type type, final ConnectionConfiguration configuration) { final Message message = createMessage(remotePeer, RPC.Commands.PING.getNr(), type); final FutureResponse futureResponse = new FutureResponse(message); return new RequestHandler(futureResponse, peerBean(), connectionBean(), configuration); } /** * Create a discover handler. * * @param remotePeer * The destination peer * @return The future of this discover handler */ private FutureResponse createDiscoverHandler(final PeerAddress remotePeer) { final Message message = createMessage(remotePeer, RPC.Commands.PING.getNr(), Type.REQUEST_2); message.neighborsSet(createNeighborSet(peerBean().serverPeerAddress())); return new FutureResponse(message); } /** * Create a neighbor set with one peer. We only support sending a neighbor set, so we need this wrapper * class. * * @param self * The peer that should be stored in the neighborset * @return The neighborset with exactly one peer */ private NeighborSet createNeighborSet(final PeerAddress... self) { List<PeerAddress> tmp = new ArrayList<PeerAddress>(self.length); for(int i=0;i<self.length;i++) { tmp.add(self[i]); } return new NeighborSet(-1, tmp); } @Override public void handleResponse(final Message message, PeerConnection peerConnection, final boolean sign, Responder responder) throws Exception { if (!((message.type() == Type.REQUEST_FF_1 || message.type() == Type.REQUEST_1 || message.type() == Type.REQUEST_2 || message.type() == Type.REQUEST_3 || message.type() == Type.REQUEST_4 || message.type() == Type.REQUEST_5) && message.command() == RPC.Commands.PING .getNr())) { throw new IllegalArgumentException("Request message type or command is wrong for this handler."); } final Message responseMessage; // probe if (message.type() == Type.REQUEST_3) { LOG.debug("Respond to probing. Firing message to {}.", message.sender()); if(message.isSendSelf()) { responseMessage = createResponseMessage(message, Type.NOT_FOUND); LOG.warn("Sending probe ping request to yourself? If those are two different peers, messages may be dropped"); } else { responseMessage = createResponseMessage(message, Type.OK); if (message.isUdp()) { connectionBean().reservation().create(1, 0).addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future) throws Exception { if (future.isSuccess()) { LOG.debug("Fire UDP to {}.", message.sender()); FutureResponse futureResponse = fireUDP(message.sender(), future.channelCreator(), connectionBean().channelServer().channelServerConfiguration()); Utils.addReleaseListener(future.channelCreator(), futureResponse); } else { Utils.addReleaseListener(future.channelCreator()); LOG.warn("handleResponse for REQUEST_3 failed (UDP) {}", future.failedReason()); } } }); } else { connectionBean().reservation().create(0, 1).addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future) throws Exception { if (future.isSuccess()) { LOG.debug("Fire TCP to {}.", message.sender()); FutureResponse futureResponse = fireTCP(message.sender(), future.channelCreator(), connectionBean().channelServer().channelServerConfiguration()); Utils.addReleaseListener(future.channelCreator(), futureResponse); } else { Utils.addReleaseListener(future.channelCreator()); LOG.warn("handleResponse for REQUEST_3 failed (TCP) {}", future.failedReason()); } } }); } } } else if (message.type() == Type.REQUEST_2) { // discover LOG.debug("Respond to discovering. Found {}.", message.sender()); if(message.isSendSelf()) { responseMessage = createResponseMessage(message, Type.NOT_FOUND); LOG.warn("Sending discover ping request to yourself? If those are two different peers, messages may be dropped"); } else { responseMessage = createResponseMessage(message, Type.OK); final int port = message.senderSocket().getPort(); responseMessage.neighborsSet(createNeighborSet(message.sender())); responseMessage.intValue(port); } } else if (message.type() == Type.REQUEST_1 || message.type() == Type.REQUEST_4 || message.type() == Type.REQUEST_5) { // regular // ping LOG.debug("Respond to regular ping {}.", message.sender()); // test if this is a broadcast message to ourselves. If it is, do // not // reply. if (message.isUdp() && message.sender().peerId().equals(peerBean().serverPeerAddress().peerId()) && message.recipient().peerId().equals(Number160.ZERO)) { LOG.warn("Don't respond. We are on the same peer, you should make this call."); responder.responseFireAndForget(); } if (enable) { if(message.type() == Type.REQUEST_5) { responseMessage = createResponseMessage(message, Type.PARTIALLY_OK); } else { responseMessage = createResponseMessage(message, Type.OK); } if (wait) { Thread.sleep(WAIT_TIME); } } else { LOG.debug("Don't respond."); // used for debugging if (wait) { Thread.sleep(WAIT_TIME); } return; } if (message.type() == Type.REQUEST_4) { synchronized (receivedBroadcastPingListeners) { for (PeerReceivedBroadcastPing listener : receivedBroadcastPingListeners) { listener.broadcastPingReceived(message.sender()); } } } } else { // fire-and-forget if message.getType() == Type.REQUEST_FF_1 // we received a fire-and forget ping // this means we are reachable from the outside PeerAddress serverAddress = peerBean().serverPeerAddress(); if (message.isUdp()) { // UDP PeerAddress newServerAddress = serverAddress.withReachable4UDP(true); peerBean().serverPeerAddress(newServerAddress); synchronized (reachableListeners) { for (PeerReachable listener : reachableListeners) { listener.peerWellConnected(newServerAddress, message.sender(), false); } } responseMessage = message; } else { // TCP PeerAddress newServerAddress = serverAddress.withReachable4TCP(true); peerBean().serverPeerAddress(newServerAddress); synchronized (reachableListeners) { for (PeerReachable listener : reachableListeners) { listener.peerWellConnected(newServerAddress, message.sender(), true); } } responseMessage = createResponseMessage(message, Type.OK); } } responder.response(responseMessage); } public void addPeerReachableListener(PeerReachable peerReachable) { synchronized (reachableListeners) { reachableListeners.add(peerReachable); } } public void removePeerReachableListener(PeerReachable peerReachable) { synchronized (reachableListeners) { reachableListeners.remove(peerReachable); } } public void addPeerReceivedBroadcastPingListener(PeerReceivedBroadcastPing peerReceivedBroadcastPing) { receivedBroadcastPingListeners.add(peerReceivedBroadcastPing); } public void removePeerReceivedBroadcastPingListener(PeerReceivedBroadcastPing peerReceivedBroadcastPing) { receivedBroadcastPingListeners.remove(peerReceivedBroadcastPing); } }