/* * Mojito Distributed Hash Table (Mojito DHT) * Copyright (C) 2006-2007 LimeWire LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.limewire.mojito.routing.impl; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetSocketAddress; import java.net.SocketAddress; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.mojito.KUID; import org.limewire.mojito.routing.Contact; import org.limewire.mojito.routing.Vendor; import org.limewire.mojito.routing.Version; import org.limewire.mojito.settings.NetworkSettings; import org.limewire.mojito.settings.RouteTableSettings; import org.limewire.mojito.util.ContactUtils; /** * The RemoteContact class implements the Contact interface * and encapsulates all requires info of a remote Node. */ public class RemoteContact implements Contact { private static final long serialVersionUID = 833079992601013124L; private static final Log LOG = LogFactory.getLog(RemoteContact.class); /** This Contact's Node ID. */ private final KUID nodeId; /** The Vendor code of this Contact. */ private final Vendor vendor; /** The Version of this Contact. */ private final Version version; /** The instance ID of this Contact. */ private final int instanceId; /** The IP:Port of the Contact as reported in the IP Packet. */ private transient volatile SocketAddress sourceAddress; /** The IP:Port of the Contact as reported in the DHTMessage. */ private volatile SocketAddress contactAddress; /** The Round Trip Time (RTT). */ private transient long rtt = -1L; /** The time of the last successful contact. */ private volatile long timeStamp = 0L; /** The time of the last unsuccessful contact. */ private volatile long lastFailedTime = 0L; /** The number of errors that have occurred. */ private volatile int failures = 0; /** The current state of the Node. */ private transient volatile State state = State.UNKNOWN; /** Whether or not this Node is firewalled. */ private volatile int flags = DEFAULT_FLAG; public RemoteContact(SocketAddress sourceAddress, Vendor vendor, Version version, KUID nodeId, SocketAddress contactAddress, int instanceId, int flags, State state) { if (nodeId == null) { throw new NullPointerException("Node ID is null"); } if (contactAddress == null) { throw new NullPointerException("SocketAddress is null"); } this.sourceAddress = sourceAddress; this.vendor = vendor; this.version = version; this.nodeId = nodeId; this.contactAddress = contactAddress; this.instanceId = instanceId; this.flags = flags; this.state = (state != null ? state : State.UNKNOWN); if (State.ALIVE.equals(state) || (State.UNKNOWN.equals(state) && sourceAddress != null)) { this.timeStamp = System.currentTimeMillis(); fixSourceAndContactAddress(sourceAddress); } checkPortConsistent(); } private void checkPortConsistent() { int port = ((InetSocketAddress)contactAddress).getPort(); if (port == 0) setFirewalled(true); } private void init() { sourceAddress = null; rtt = -1; state = State.UNKNOWN; } /** * This method takes the InetAddress from the sourceAddress and * the Port number from the contactAddress and combines them * to the new contactAddress. When the Port number is 0 it will * set the sourceAddress as contactAddress and marks this Contact * as firewalled. */ public final void fixSourceAndContactAddress(SocketAddress sourceAddress) { if (sourceAddress != null) { this.sourceAddress = sourceAddress; SocketAddress backup = contactAddress; int port = ((InetSocketAddress)contactAddress).getPort(); if (port == 0) { if (!isFirewalled()) { if (LOG.isWarnEnabled()) { LOG.warn(ContactUtils.toString(nodeId, sourceAddress) + " contact address is set to Port 0 but it is not marked as firewalled"); } } contactAddress = sourceAddress; checkPortConsistent(); } else if (!NetworkSettings.ACCEPT_FORCED_ADDRESS.getValue()) { // If the source address is a PRIVATE address then // don't use it because the other Node is on the // same LAN as we are (damn NAT routers!). if (!ContactUtils.isPrivateAddress(sourceAddress)) { contactAddress = new InetSocketAddress( ((InetSocketAddress)sourceAddress).getAddress(), port); } } if (LOG.isInfoEnabled()) { LOG.info("Merged " + sourceAddress + " and " + backup + " to " + contactAddress + ", firewalled=" + isFirewalled()); } } } public void updateWithExistingContact(Contact existing) { if (!nodeId.equals(existing.getNodeID())) { throw new IllegalArgumentException("Node IDs do not match: " + this + " vs. " + existing); } if (rtt < 0L) { rtt = existing.getRoundTripTime(); } if (!isAlive() || (getTimeStamp() < existing.getTimeStamp())) { timeStamp = existing.getTimeStamp(); lastFailedTime = existing.getLastFailedTime(); failures = existing.getFailures(); } } public Vendor getVendor() { return vendor; } public Version getVersion() { return version; } public KUID getNodeID() { return nodeId; } public int getInstanceID() { return instanceId; } public int getFlags() { return flags; } public SocketAddress getContactAddress() { return contactAddress; } public SocketAddress getSourceAddress() { return sourceAddress; } public long getRoundTripTime() { return rtt; } public void setRoundTripTime(long rtt) { this.rtt = rtt; } public void setTimeStamp(long timeStamp) { assert (timeStamp != LOCAL_CONTACT); this.timeStamp = timeStamp; } public long getTimeStamp() { return timeStamp; } public long getLastFailedTime() { return lastFailedTime; } public boolean isFirewalled() { return (flags & FIREWALLED_FLAG) != 0; } private void setFirewalled(boolean firewalled) { if (isFirewalled() != firewalled) { this.flags ^= FIREWALLED_FLAG; } } public long getAdaptativeTimeout() { //for now, based on failures and previous round trip time long timeout = NetworkSettings.DEFAULT_TIMEOUT.getValue(); if (rtt <= 0L || !isAlive()) { return timeout; } else { // Should be NetworkSettings.MIN_TIMEOUT_RTT < t < NetworkSettings.DEFAULT_TIMEOUT long rttFactor = NetworkSettings.MIN_TIMEOUT_RTT_FACTOR.getValue(); long adaptiveTimeout = ((rttFactor * rtt) + failures * rtt); return Math.max(Math.min(timeout, adaptiveTimeout), NetworkSettings.MIN_TIMEOUT_RTT.getValue()); } } public void alive() { state = State.ALIVE; failures = 0; timeStamp = System.currentTimeMillis(); } public boolean isAlive() { return State.ALIVE.equals(state); } public void unknown() { state = State.UNKNOWN; failures = 0; timeStamp = 0L; } public boolean isUnknown() { return State.UNKNOWN.equals(state); } public boolean isDead() { return State.DEAD.equals(state); } public boolean hasBeenRecentlyAlive() { return ((System.currentTimeMillis() - getTimeStamp()) < RouteTableSettings.MIN_RECONNECTION_TIME.getValue()); } public int getFailures() { return failures; } public void handleFailure() { failures++; lastFailedTime = System.currentTimeMillis(); if (!isShutdown()) { // Node has ever been alive? if (getTimeStamp() > 0L) { if (failures >= RouteTableSettings.MAX_ALIVE_NODE_FAILURES.getValue()) { state = State.DEAD; } } else { if (failures >= RouteTableSettings.MAX_UNKNOWN_NODE_FAILURES.getValue()) { state = State.DEAD; } } } } public boolean hasFailed() { return failures > 0; } /** * Returns the state of this Contact. */ public State getState() { return state; } /** * Sets the state of this Contact. */ public void setState(State state) { if (state == null) { state = State.UNKNOWN; } this.state = state; } public boolean isShutdown() { return (flags & SHUTDOWN_FLAG) != 0; } public void shutdown(boolean shutdown) { if (isShutdown() != shutdown) { this.flags ^= SHUTDOWN_FLAG; this.state = State.DEAD; } } @Override public int hashCode() { return nodeId.hashCode(); } @Override public boolean equals(Object o) { if (!(o instanceof Contact) || o instanceof LocalContact) { return false; } Contact c = (Contact)o; return nodeId.equals(c.getNodeID()) && contactAddress.equals(c.getContactAddress()); } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); init(); } @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append(ContactUtils.toString(getNodeID(), getContactAddress())) .append(", rtt=").append(getRoundTripTime()) .append(", failures=").append(getFailures()) .append(", instanceId=").append(getInstanceID()) .append(", state=").append(isShutdown() ? "DOWN" : getState()) .append(", firewalled=").append(isFirewalled()); return buffer.toString(); } }