package net.i2p.router.transport.udp;
import java.io.IOException;
import java.io.Writer;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import net.i2p.crypto.SigType;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterIdentity;
import net.i2p.data.router.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.CommSystemFacade.Status;
import net.i2p.router.OutNetMessage;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.Transport;
import static net.i2p.router.transport.Transport.AddressSource.*;
import net.i2p.router.transport.TransportBid;
import net.i2p.router.transport.TransportImpl;
import net.i2p.router.transport.TransportUtil;
import static net.i2p.router.transport.TransportUtil.IPv6Config.*;
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
import static net.i2p.router.transport.udp.PeerTestState.Role.*;
import static net.i2p.router.transport.udp.Sorters.*;
import net.i2p.router.util.EventLog;
import net.i2p.router.util.RandomIterator;
import net.i2p.util.Addresses;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SimpleTimer;
import net.i2p.util.SimpleTimer2;
import net.i2p.util.VersionComparator;
/**
* The SSU transport
*/
public class UDPTransport extends TransportImpl implements TimedWeightedPriorityMessageQueue.FailedListener {
private final Log _log;
private final List<UDPEndpoint> _endpoints;
private final Object _addDropLock = new Object();
/** Peer (Hash) to PeerState */
private final Map<Hash, PeerState> _peersByIdent;
/** RemoteHostId to PeerState */
private final Map<RemoteHostId, PeerState> _peersByRemoteHost;
private PacketHandler _handler;
private EstablishmentManager _establisher;
private final MessageQueue _outboundMessages;
private final OutboundMessageFragments _fragments;
private final OutboundMessageFragments.ActiveThrottle _activeThrottle;
private OutboundRefiller _refiller;
private volatile PacketPusher _pusher;
private final InboundMessageFragments _inboundFragments;
//private UDPFlooder _flooder;
private final PeerTestManager _testManager;
private final IntroductionManager _introManager;
private final ExpirePeerEvent _expireEvent;
private final PeerTestEvent _testEvent;
private final PacketBuilder _destroyBuilder;
private Status _reachabilityStatus;
private Status _reachabilityStatusPending;
// only for logging, to be removed
private long _reachabilityStatusLastUpdated;
private int _reachabilityStatusUnchanged;
private long _introducersSelectedOn;
private long _lastInboundReceivedOn;
private final DHSessionKeyBuilder.Factory _dhFactory;
private int _mtu;
private int _mtu_ipv6;
private boolean _mismatchLogged;
private final int _networkID;
/**
* Do we have a public IPv6 address?
* TODO periodically update via CSFI.NetMonitor?
*/
private volatile boolean _haveIPv6Address;
private long _lastInboundIPv6;
private final int _min_peers;
private final int _min_v6_peers;
/** do we need to rebuild our external router address asap? */
private boolean _needsRebuild;
private final Object _rebuildLock = new Object();
/** introduction key */
private SessionKey _introKey;
/**
* List of RemoteHostId for peers whose packets we want to drop outright
* This is only for old network IDs (pre-0.6.1.10), so it isn't really used now.
*/
private final Set<RemoteHostId> _dropList;
private volatile long _expireTimeout;
/** last report from a peer of our IP */
private Hash _lastFrom;
private byte[] _lastOurIP;
private int _lastOurPort;
/** since we don't publish our IP/port if introduced anymore, we need
to store it somewhere. */
private RouterAddress _currentOurV4Address;
private RouterAddress _currentOurV6Address;
private static final int DROPLIST_PERIOD = 10*60*1000;
public static final String STYLE = "SSU";
public static final String PROP_INTERNAL_PORT = "i2np.udp.internalPort";
/** now unused, we pick a random port
* @deprecated unused
*/
@Deprecated
public static final int DEFAULT_INTERNAL_PORT = 8887;
/** define this to explicitly set an external IP address */
public static final String PROP_EXTERNAL_HOST = "i2np.udp.host";
/** define this to explicitly set an external port */
public static final String PROP_EXTERNAL_PORT = "i2np.udp.port";
/**
* If i2np.udp.preferred is set to "always", the UDP bids will always be under
* the bid from the TCP transport - even if a TCP connection already
* exists. If it is set to "true",
* it will prefer UDP unless no UDP session exists and a TCP connection
* already exists.
* If it is set to "false" (the default),
* it will prefer TCP unless no TCP session exists and a UDP connection
* already exists.
*/
public static final String PROP_PREFER_UDP = "i2np.udp.preferred";
private static final String DEFAULT_PREFER_UDP = "false";
/** Override whether we will change our advertised port no matter what our peers tell us
* See getIsPortFixed() for default behaviour.
*/
private static final String PROP_FIXED_PORT = "i2np.udp.fixedPort";
/** allowed sources of address updates */
public static final String PROP_SOURCES = "i2np.udp.addressSources";
public static final String DEFAULT_SOURCES = SOURCE_INTERFACE.toConfigString() + ',' +
SOURCE_UPNP.toConfigString() + ',' +
SOURCE_SSU.toConfigString();
/** remember IP changes */
public static final String PROP_IP= "i2np.lastIP";
public static final String PROP_IP_CHANGE = "i2np.lastIPChange";
public static final String PROP_LAPTOP_MODE = "i2np.laptopMode";
/** do we require introducers, regardless of our status? */
public static final String PROP_FORCE_INTRODUCERS = "i2np.udp.forceIntroducers";
/** do we allow direct SSU connections, sans introducers? */
public static final String PROP_ALLOW_DIRECT = "i2np.udp.allowDirect";
/** this is rarely if ever used, default is to bind to wildcard address */
public static final String PROP_BIND_INTERFACE = "i2np.udp.bindInterface";
/** override the "large" (max) MTU, default is PeerState.LARGE_MTU */
private static final String PROP_DEFAULT_MTU = "i2np.udp.mtu";
private static final String PROP_ADVANCED = "routerconsole.advanced";
private static final String CAP_TESTING = "" + UDPAddress.CAPACITY_TESTING;
private static final String CAP_TESTING_INTRO = "" + UDPAddress.CAPACITY_TESTING + UDPAddress.CAPACITY_INTRODUCER;
/** how many relays offered to us will we use at a time? */
public static final int PUBLIC_RELAY_COUNT = 3;
private static final boolean USE_PRIORITY = false;
/** configure the priority queue with the given split points */
private static final int PRIORITY_LIMITS[] = new int[] { 100, 200, 300, 400, 500, 1000 };
/** configure the priority queue with the given weighting per priority group */
private static final int PRIORITY_WEIGHT[] = new int[] { 1, 1, 1, 1, 1, 2 };
/** should we flood all UDP peers with the configured rate? This is for testing only! */
//private static final boolean SHOULD_FLOOD_PEERS = false;
private static final int MAX_CONSECUTIVE_FAILED = 5;
public static final int DEFAULT_COST = 5;
static final long[] RATES = { 10*60*1000 };
/** minimum active peers to maintain IP detection, etc. */
private static final int MIN_PEERS = 5;
private static final int MIN_PEERS_IF_HAVE_V6 = 30;
/** minimum peers volunteering to be introducers if we need that */
private static final int MIN_INTRODUCER_POOL = 5;
static final long INTRODUCER_EXPIRATION_MARGIN = 20*60*1000L;
private static final int[] BID_VALUES = { 15, 20, 50, 65, 80, 95, 100, 115, TransportBid.TRANSIENT_FAIL };
private static final int FAST_PREFERRED_BID = 0;
private static final int SLOW_PREFERRED_BID = 1;
private static final int FAST_BID = 2;
private static final int SLOW_BID = 3;
private static final int SLOWEST_BID = 4;
private static final int SLOWEST_COST_BID = 5;
private static final int NEAR_CAPACITY_BID = 6;
private static final int NEAR_CAPACITY_COST_BID = 7;
private static final int TRANSIENT_FAIL_BID = 8;
private final TransportBid[] _cachedBid;
// Opera doesn't have the char, TODO check UA
//private static final String THINSP = " / ";
private static final String THINSP = " / ";
/**
* RI sigtypes supported in 0.9.16, but due to a bug in InboundEstablishState
* fixed in 0.9.17, we cannot connect out to routers before that version.
*/
private static final String MIN_SIGTYPE_VERSION = "0.9.17";
/**
* IPv6 Peer Testing supported
*/
private static final String MIN_V6_PEER_TEST_VERSION = "0.9.27";
public UDPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh) {
super(ctx);
_networkID = ctx.router().getNetworkID();
_dhFactory = dh;
_log = ctx.logManager().getLog(UDPTransport.class);
_peersByIdent = new ConcurrentHashMap<Hash, PeerState>(128);
_peersByRemoteHost = new ConcurrentHashMap<RemoteHostId, PeerState>(128);
_dropList = new ConcurrentHashSet<RemoteHostId>(2);
_endpoints = new CopyOnWriteArrayList<UDPEndpoint>();
// See comments in DummyThrottle.java
if (USE_PRIORITY) {
TimedWeightedPriorityMessageQueue mq = new TimedWeightedPriorityMessageQueue(ctx, PRIORITY_LIMITS, PRIORITY_WEIGHT, this);
_outboundMessages = mq;
_activeThrottle = mq;
} else {
DummyThrottle mq = new DummyThrottle();
_outboundMessages = null;
_activeThrottle = mq;
}
_cachedBid = new SharedBid[BID_VALUES.length];
for (int i = 0; i < BID_VALUES.length; i++) {
_cachedBid[i] = new SharedBid(BID_VALUES[i]);
}
_destroyBuilder = new PacketBuilder(_context, this);
_fragments = new OutboundMessageFragments(_context, this, _activeThrottle);
_inboundFragments = new InboundMessageFragments(_context, _fragments, this);
//if (SHOULD_FLOOD_PEERS)
// _flooder = new UDPFlooder(_context, this);
_expireTimeout = EXPIRE_TIMEOUT;
_expireEvent = new ExpirePeerEvent();
_testManager = new PeerTestManager(_context, this);
_testEvent = new PeerTestEvent(_context, this, _testManager);
_reachabilityStatus = Status.UNKNOWN;
_reachabilityStatusPending = Status.OK;
_introManager = new IntroductionManager(_context, this);
_introducersSelectedOn = -1;
_lastInboundReceivedOn = -1;
_mtu = PeerState.LARGE_MTU;
_mtu_ipv6 = PeerState.MIN_IPV6_MTU;
setupPort();
_needsRebuild = true;
_min_peers = _context.getProperty("i2np.udp.minpeers", MIN_PEERS);
_min_v6_peers = _context.getProperty("i2np.udp.minv6peers", MIN_PEERS_IF_HAVE_V6);
_context.statManager().createRateStat("udp.alreadyConnected", "What is the lifetime of a reestablished session", "udp", RATES);
_context.statManager().createRateStat("udp.droppedPeer", "How long ago did we receive from a dropped peer (duration == session lifetime", "udp", RATES);
_context.statManager().createRateStat("udp.droppedPeerInactive", "How long ago did we receive from a dropped peer (duration == session lifetime)", "udp", RATES);
//_context.statManager().createRateStat("udp.statusOK", "How many times the peer test returned OK", "udp", RATES);
//_context.statManager().createRateStat("udp.statusDifferent", "How many times the peer test returned different IP/ports", "udp", RATES);
//_context.statManager().createRateStat("udp.statusReject", "How many times the peer test returned reject unsolicited", "udp", RATES);
//_context.statManager().createRateStat("udp.statusUnknown", "How many times the peer test returned an unknown result", "udp", RATES);
_context.statManager().createRateStat("udp.addressTestInsteadOfUpdate", "How many times we fire off a peer test of ourselves instead of adjusting our own reachable address?", "udp", RATES);
_context.statManager().createRateStat("udp.addressUpdated", "How many times we adjust our own reachable IP address", "udp", RATES);
_context.statManager().createRateStat("udp.proactiveReestablish", "How long a session was idle for when we proactively reestablished it", "udp", RATES);
_context.statManager().createRateStat("udp.dropPeerDroplist", "How many peers currently have their packets dropped outright when a new peer is added to the list?", "udp", RATES);
_context.statManager().createRateStat("udp.dropPeerConsecutiveFailures", "How many consecutive failed sends to a peer did we attempt before giving up and reestablishing a new session (lifetime is inactivity perood)", "udp", RATES);
_context.statManager().createRateStat("udp.inboundIPv4Conn", "Inbound IPv4 UDP Connection", "udp", RATES);
_context.statManager().createRateStat("udp.inboundIPv6Conn", "Inbound IPv4 UDP Connection", "udp", RATES);
// following are for PacketBuider
//_context.statManager().createRateStat("udp.packetAuthTime", "How long it takes to encrypt and MAC a packet for sending", "udp", RATES);
//_context.statManager().createRateStat("udp.packetAuthTimeSlow", "How long it takes to encrypt and MAC a packet for sending (when its slow)", "udp", RATES);
_context.simpleTimer2().addPeriodicEvent(new PingIntroducers(), MIN_EXPIRE_TIMEOUT * 3 / 4);
}
/**
* Pick a port if not previously configured, so that TransportManager may
* call getRequestedPort() before we've started to get a best-guess of what our
* port is going to be, and pass that to NTCP
*
* @since IPv6
*/
private void setupPort() {
int port = getRequestedPort();
if (port < 0) {
port = UDPEndpoint.selectRandomPort(_context);
Map<String, String> changes = new HashMap<String, String>();
changes.put(PROP_INTERNAL_PORT, Integer.toString(port));
changes.put(PROP_EXTERNAL_PORT, Integer.toString(port));
_context.router().saveConfig(changes, null);
_log.logAlways(Log.INFO, "UDP selected random port " + port);
}
}
private synchronized void startup() {
_fragments.shutdown();
if (_pusher != null)
_pusher.shutdown();
if (_handler != null)
_handler.shutdown();
for (UDPEndpoint endpoint : _endpoints) {
endpoint.shutdown();
// should we remove?
_endpoints.remove(endpoint);
}
if (_establisher != null)
_establisher.shutdown();
if (_refiller != null)
_refiller.shutdown();
_inboundFragments.shutdown();
//if (_flooder != null)
// _flooder.shutdown();
_introManager.reset();
UDPPacket.clearCache();
if (_log.shouldLog(Log.WARN)) _log.warn("Starting SSU transport listening");
_introKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
System.arraycopy(_context.routerHash().getData(), 0, _introKey.getData(), 0, SessionKey.KEYSIZE_BYTES);
// bind host
// This is not exposed in the UI and in practice is always null.
// We use PROP_EXTERNAL_HOST instead. See below.
String bindTo = _context.getProperty(PROP_BIND_INTERFACE);
if (bindTo == null) {
// If we are configured with a fixed IP address,
// AND it's one of our local interfaces,
// bind only to that.
String fixedHost = _context.getProperty(PROP_EXTERNAL_HOST);
if (fixedHost != null && fixedHost.length() > 0) {
// Generate a comma-separated list of valid IP addresses
// that we can bind to,
// from the comma-separated PROP_EXTERNAL_HOST config.
// The config may contain IPs or hostnames; expand each
// hostname to one or more (v4 or v6) IPs.
TransportUtil.IPv6Config cfg = getIPv6Config();
Set<String> myAddrs;
if (cfg == IPV6_DISABLED)
myAddrs = Addresses.getAddresses(false, false);
else
myAddrs = Addresses.getAddresses(false, true);
StringBuilder buf = new StringBuilder();
String[] bta = DataHelper.split(fixedHost, "[,; \r\n\t]");
for (int i = 0; i < bta.length; i++) {
String bt = bta[i];
if (bt.length() <= 0)
continue;
try {
InetAddress[] all = InetAddress.getAllByName(bt);
for (int j = 0; j < all.length; j++) {
InetAddress ia = all[j];
if (cfg == IPV6_ONLY && (ia instanceof Inet4Address)) {
if (_log.shouldWarn())
_log.warn("Configured for IPv6 only, not binding to configured IPv4 host " + bt);
continue;
}
String testAddr = ia.getHostAddress();
if (myAddrs.contains(testAddr)) {
if (buf.length() > 0)
buf.append(',');
buf.append(testAddr);
} else {
if (_log.shouldWarn())
_log.warn("Not a local address, not binding to configured IP " + testAddr);
}
}
} catch (UnknownHostException uhe) {
if (_log.shouldWarn())
_log.warn("Not binding to configured host " + bt + " - " + uhe);
}
}
if (buf.length() > 0) {
bindTo = buf.toString();
if (_log.shouldWarn() && !fixedHost.equals(bindTo))
_log.warn("Expanded external host config \"" + fixedHost + "\" to \"" + bindTo + '"');
}
}
}
// construct a set of addresses
Set<InetAddress> bindToAddrs = new HashSet<InetAddress>(4);
if (bindTo != null) {
// Generate a set IP addresses
// that we can bind to,
// from the comma-separated PROP_BIND_INTERFACE config,
// or as generated from the PROP_EXTERNAL_HOST config.
// In theory, the config may contain IPs and/or hostnames.
// However, in practice, it's only IPs, because any hostnames
// in PROP_EXTERNAL_HOST were expanded above to one or more (v4 or v6) IPs.
// PROP_BIND_INTERFACE is not exposed in the UI and is never set.
String[] bta = DataHelper.split(bindTo, "[,; \r\n\t]");
for (int i = 0; i < bta.length; i++) {
String bt = bta[i];
if (bt.length() <= 0)
continue;
try {
bindToAddrs.add(InetAddress.getByName(bt));
} catch (UnknownHostException uhe) {
_log.error("Invalid SSU bind interface specified [" + bt + "]", uhe);
//setReachabilityStatus(CommSystemFacade.STATUS_HOSED);
//return;
// fall thru...
}
}
}
// Requested bind port
// This may be -1 or may not be honored if busy,
// Priority: Configured internal, then already used, then configured external
// we will check below after starting up the endpoint.
int port;
int oldIPort = _context.getProperty(PROP_INTERNAL_PORT, -1);
int oldBindPort = getListenPort(false);
int oldEPort = _context.getProperty(PROP_EXTERNAL_PORT, -1);
if (oldIPort > 0)
port = oldIPort;
else if (oldBindPort > 0)
port = oldBindPort;
else
port = oldEPort;
if (!bindToAddrs.isEmpty() && _log.shouldLog(Log.WARN))
_log.warn("Binding only to " + bindToAddrs);
if (_log.shouldLog(Log.INFO))
_log.info("Binding to the port: " + port);
if (_endpoints.isEmpty()) {
// _endpoints will always be empty since we removed them above
if (bindToAddrs.isEmpty()) {
UDPEndpoint endpoint = new UDPEndpoint(_context, this, port, null);
_endpoints.add(endpoint);
setMTU(null);
} else {
for (InetAddress bindToAddr : bindToAddrs) {
UDPEndpoint endpoint = new UDPEndpoint(_context, this, port, bindToAddr);
_endpoints.add(endpoint);
setMTU(bindToAddr);
}
}
} else {
// unused for now
for (UDPEndpoint endpoint : _endpoints) {
if (endpoint.isIPv4()) {
// hack, first IPv4 endpoint, FIXME
// todo, set bind address too
endpoint.setListenPort(port);
break;
}
}
}
if (_establisher == null)
_establisher = new EstablishmentManager(_context, this);
if (_handler == null)
_handler = new PacketHandler(_context, this, _establisher, _inboundFragments, _testManager, _introManager);
// See comments in DummyThrottle.java
if (USE_PRIORITY && _refiller == null)
_refiller = new OutboundRefiller(_context, _fragments, _outboundMessages);
//if (SHOULD_FLOOD_PEERS && _flooder == null)
// _flooder = new UDPFlooder(_context, this);
// Startup the endpoint with the requested port, check the actual port, and
// take action if it failed or was different than requested or it needs to be saved
int newPort = -1;
for (UDPEndpoint endpoint : _endpoints) {
try {
endpoint.startup();
// hack, first IPv4 endpoint, FIXME
if (newPort < 0 && endpoint.isIPv4()) {
newPort = endpoint.getListenPort();
}
if (_log.shouldLog(Log.WARN))
_log.warn("Started " + endpoint);
} catch (SocketException se) {
_endpoints.remove(endpoint);
_log.error("Failed to start " + endpoint, se);
}
}
if (_endpoints.isEmpty()) {
_log.log(Log.CRIT, "Unable to open UDP port");
setReachabilityStatus(Status.HOSED);
return;
}
if (newPort > 0 &&
(newPort != port || newPort != oldIPort || newPort != oldEPort)) {
// attempt to use it as our external port - this will be overridden by
// externalAddressReceived(...)
Map<String, String> changes = new HashMap<String, String>();
changes.put(PROP_INTERNAL_PORT, Integer.toString(newPort));
changes.put(PROP_EXTERNAL_PORT, Integer.toString(newPort));
_context.router().saveConfig(changes, null);
}
_handler.startup();
_fragments.startup();
_inboundFragments.startup();
_pusher = new PacketPusher(_context, _fragments, _endpoints);
_pusher.startup();
// must be after pusher
_establisher.startup();
if (USE_PRIORITY)
_refiller.startup();
//if (SHOULD_FLOOD_PEERS)
// _flooder.startup();
_expireEvent.setIsAlive(true);
_reachabilityStatus = Status.UNKNOWN;
_testEvent.setIsAlive(true); // this queues it for 3-6 minutes in the future...
_testEvent.reschedule(10*1000); // lets requeue it for Real Soon
// set up external addresses
// REA param is false;
// TransportManager.startListening() calls router.rebuildRouterInfo()
if (newPort > 0 && bindToAddrs.isEmpty()) {
for (InetAddress ia : getSavedLocalAddresses()) {
// Discovered or configured addresses are presumed good at the start.
// when externalAddressReceived() was called with SOURCE_INTERFACE,
// isAlive() was false, so setReachabilityStatus() was not called
// TODO should we set both to unknown and wait for an inbound v6 conn,
// since there's no v6 testing?
if (ia.getAddress().length == 16) {
// FIXME we need to check and time out after an hour of no inbound ipv6,
// change to firewalled maybe? but we don't have any test to restore
// a v6 address after it's removed.
_lastInboundIPv6 = _context.clock().now();
if (!isIPv6Firewalled())
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK, true);
} else {
if (!isIPv4Firewalled())
setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
}
rebuildExternalAddress(ia.getHostAddress(), newPort, false);
}
} else if (newPort > 0 && !bindToAddrs.isEmpty()) {
for (InetAddress ia : bindToAddrs) {
if (ia.getAddress().length == 16) {
_lastInboundIPv6 = _context.clock().now();
if (!isIPv6Firewalled())
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK, true);
} else {
if (!isIPv4Firewalled())
setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
}
rebuildExternalAddress(ia.getHostAddress(), newPort, false);
}
// TODO
// If we are bound only to v4 addresses,
// force _haveIPv6Address to false, or else we get 'no endpoint' errors
// If we are bound only to v6 addresses,
// override getIPv6Config() ?
}
if (isIPv4Firewalled()) {
if (_lastInboundIPv6 > 0)
setReachabilityStatus(Status.IPV4_FIREWALLED_IPV6_UNKNOWN);
else
setReachabilityStatus(Status.REJECT_UNSOLICITED);
}
rebuildExternalAddress(false);
}
public synchronized void shutdown() {
destroyAll();
for (UDPEndpoint endpoint : _endpoints) {
endpoint.shutdown();
// should we remove?
_endpoints.remove(endpoint);
}
//if (_flooder != null)
// _flooder.shutdown();
if (_refiller != null)
_refiller.shutdown();
if (_handler != null)
_handler.shutdown();
if (_pusher != null)
_pusher.shutdown();
_fragments.shutdown();
if (_establisher != null)
_establisher.shutdown();
_inboundFragments.shutdown();
_expireEvent.setIsAlive(false);
_testEvent.setIsAlive(false);
_peersByRemoteHost.clear();
_peersByIdent.clear();
_dropList.clear();
_introManager.reset();
UDPPacket.clearCache();
UDPAddress.clearCache();
_lastInboundIPv6 = 0;
}
/**
* The endpoint has failed. Remove it.
*
* @since 0.9.16
*/
public void fail(UDPEndpoint endpoint) {
if (_endpoints.remove(endpoint)) {
_log.log(Log.CRIT, "UDP port failure: " + endpoint);
if (_endpoints.isEmpty()) {
_log.log(Log.CRIT, "No more UDP sockets open");
setReachabilityStatus(Status.HOSED);
// TODO restart?
}
rebuildExternalAddress();
}
}
/** @since IPv6 */
private boolean isAlive() {
return _inboundFragments.isAlive();
}
/**
* Introduction key that people should use to contact us
*
*/
SessionKey getIntroKey() { return _introKey; }
int getExternalPort(boolean ipv6) {
RouterAddress addr = getCurrentAddress(ipv6);
if (addr != null) {
int rv = addr.getPort();
if (rv > 0)
return rv;
}
return getRequestedPort(ipv6);
}
/**
* IPv4 only
* @return IP or null
* @since 0.9.2
*/
byte[] getExternalIP() {
RouterAddress addr = getCurrentAddress(false);
if (addr != null)
return addr.getIP();
return null;
}
/**
* For PeerTestManager
* @since 0.9.30
*/
boolean hasIPv6Address() {
return _haveIPv6Address;
}
/**
* Is this IP too close to ours to trust it for
* things like relaying?
* @param ip IPv4 or IPv6
* @since IPv6
*/
boolean isTooClose(byte[] ip) {
if (allowLocal())
return false;
for (RouterAddress addr : getCurrentAddresses()) {
byte[] myip = addr.getIP();
if (myip == null || ip.length != myip.length)
continue;
if (ip.length == 4) {
if (DataHelper.eq(ip, 0, myip, 0, 2))
return true;
} else if (ip.length == 16) {
if (DataHelper.eq(ip, 0, myip, 0, 8))
return true;
}
}
return false;
}
/**
* The current port of the first matching endpoint.
* To be enhanced to handle multiple endpoints of the same type.
* @return port or -1
* @since IPv6
*/
private int getListenPort(boolean ipv6) {
for (UDPEndpoint endpoint : _endpoints) {
if (((!ipv6) && endpoint.isIPv4()) ||
(ipv6 && endpoint.isIPv6()))
return endpoint.getListenPort();
}
return -1;
}
/**
* The current or configured internal IPv4 port.
* UDPEndpoint should always be instantiated (and a random port picked if not configured)
* before this is called, so the returned value should be > 0
* unless the endpoint failed to bind.
*/
@Override
public int getRequestedPort() {
return getRequestedPort(false);
}
/**
* The current or configured internal port.
* UDPEndpoint should always be instantiated (and a random port picked if not configured)
* before this is called, so the returned value should be > 0
* unless the endpoint failed to bind.
*/
private int getRequestedPort(boolean ipv6) {
int rv = getListenPort(ipv6);
if (rv > 0)
return rv;
// fallbacks
rv = _context.getProperty(PROP_INTERNAL_PORT, -1);
if (rv > 0)
return rv;
return _context.getProperty(PROP_EXTERNAL_PORT, -1);
}
/**
* Set the MTU for the socket interface at addr.
* @param addr null ok
* @return the mtu
* @since 0.9.2
*/
private int setMTU(InetAddress addr) {
String p = _context.getProperty(PROP_DEFAULT_MTU);
if (p != null) {
try {
int pmtu = Integer.parseInt(p);
_mtu = MTU.rectify(false, pmtu);
_mtu_ipv6 = MTU.rectify(true, pmtu);
return _mtu;
} catch (NumberFormatException nfe) {}
}
int mtu = MTU.getMTU(addr);
if (addr != null && addr.getAddress().length == 16) {
if (mtu <= 0)
mtu = PeerState.MIN_IPV6_MTU;
_mtu_ipv6 = mtu;
} else {
if (mtu <= 0)
mtu = PeerState.LARGE_MTU;
_mtu = mtu;
}
return mtu;
}
/**
* The MTU for the socket interface.
* To be used as the "large" MTU.
* @return limited to range PeerState.MIN_MTU to PeerState.LARGE_MTU.
* @since 0.9.2
*/
int getMTU(boolean ipv6) {
// TODO multiple interfaces of each type
return ipv6 ? _mtu_ipv6 : _mtu;
}
/**
* If we have received an inbound connection in the last 2 minutes, don't allow
* our IP to change.
*/
private static final int ALLOW_IP_CHANGE_INTERVAL = 2*60*1000;
void inboundConnectionReceived(boolean isIPv6) {
if (isIPv6) {
_lastInboundIPv6 = _context.clock().now();
_context.statManager().addRateData("udp.inboundIPv6Conn", 1);
// former workaround for lack of IPv6 peer testing
//if (_currentOurV6Address != null)
// setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK, true);
} else {
// Introduced connections are still inbound, this is not evidence
// that we are not firewalled.
// use OS clock since its an ordering thing, not a time thing
_lastInboundReceivedOn = System.currentTimeMillis();
_context.statManager().addRateData("udp.inboundIPv4Conn", 1);
}
}
// temp prevent multiples
private boolean gotIPv4Addr = false;
private boolean gotIPv6Addr = false;
/**
* From config, UPnP, local i/f, ...
* Not for info received from peers - see externalAddressReceived(Hash, ip, port)
*
* @param source as defined in Transport.SOURCE_xxx
* @param ip publicly routable IPv4 or IPv6, null ok
* @param port 0 if unknown
*/
@Override
public void externalAddressReceived(Transport.AddressSource source, byte[] ip, int port) {
if (_log.shouldLog(Log.WARN))
_log.warn("Received address: " + Addresses.toString(ip, port) + " from: " + source);
if (ip == null)
return;
// this is essentially isValid(ip), but we can't use that because
// _haveIPv6Address is not set yet
if (!(isPubliclyRoutable(ip) || allowLocal())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid address: " + Addresses.toString(ip, port) + " from: " + source);
return;
}
if (source == SOURCE_INTERFACE && ip.length == 16) {
// NOW we can set it, it's a valid v6 address
// (we don't want to set this for Teredo, 6to4, ...)
_haveIPv6Address = true;
}
if (explicitAddressSpecified())
return;
String sources = _context.getProperty(PROP_SOURCES, DEFAULT_SOURCES);
if (!sources.contains(source.toConfigString()))
return;
if (!isAlive()) {
if (source == SOURCE_INTERFACE || source == SOURCE_UPNP) {
try {
InetAddress ia = InetAddress.getByAddress(ip);
saveLocalAddress(ia);
} catch (UnknownHostException uhe) {}
}
return;
}
if (source == SOURCE_INTERFACE) {
// temp prevent multiples
if (ip.length == 4) {
if (gotIPv4Addr)
return;
else
gotIPv4Addr = true;
} else if (ip.length == 16) {
if (gotIPv6Addr)
return;
else
gotIPv6Addr = true;
}
}
boolean changed = changeAddress(ip, port);
// Assume if we have an interface with a public IP that we aren't firewalled.
// If this is wrong, the peer test will figure it out and change the status.
if (changed && source == SOURCE_INTERFACE) {
if (ip.length == 4) {
if (!isIPv4Firewalled())
setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
} else if (ip.length == 16) {
// TODO should we set both to unknown and wait for an inbound v6 conn,
// since there's no v6 testing?
if (!isIPv6Firewalled())
setReachabilityStatus(Status.IPV4_UNKNOWN_IPV6_OK, true);
}
}
}
/**
* Callback from UPnP.
* If we we have an IP address and UPnP claims success, believe it.
* If this is wrong, the peer test will figure it out and change the status.
* Don't do anything if UPnP claims failure.
*/
@Override
public void forwardPortStatus(byte[] ip, int port, int externalPort, boolean success, String reason) {
if (_log.shouldLog(Log.WARN)) {
if (success)
_log.warn("UPnP has opened the SSU port: " + port + " via " + Addresses.toString(ip, externalPort));
else
_log.warn("UPnP has failed to open the SSU port: " + port + " reason: " + reason);
}
if (success && ip != null && getExternalIP() != null) {
if (!isIPv4Firewalled())
setReachabilityStatus(Status.IPV4_OK_IPV6_UNKNOWN);
}
}
/**
* Someone we tried to contact gave us what they think our IP address is.
* Right now, we just blindly trust them, changing our IP and port on a
* whim. this is not good ;)
*
* Slight enhancement - require two different peers in a row to agree
*
* Todo:
* - Much better tracking of troublemakers
* - Disable if we have good local address or UPnP
* - This gets harder if and when we publish multiple addresses, or IPv6
*
* @param from Hash of inbound destination
* @param ourIP publicly routable IPv4 only
* @param ourPort >= 1024
*/
void externalAddressReceived(Hash from, byte ourIP[], int ourPort) {
boolean isValid = isValid(ourIP) &&
TransportUtil.isValidPort(ourPort);
boolean explicitSpecified = explicitAddressSpecified();
boolean inboundRecent = _lastInboundReceivedOn + ALLOW_IP_CHANGE_INTERVAL > System.currentTimeMillis();
if (_log.shouldLog(Log.INFO))
_log.info("External address received: " + Addresses.toString(ourIP, ourPort) + " from "
+ from + ", isValid? " + isValid + ", explicitSpecified? " + explicitSpecified
+ ", receivedInboundRecent? " + inboundRecent + " status " + _reachabilityStatus);
if (ourIP.length != 4) {
return;
}
if (explicitSpecified)
return;
String sources = _context.getProperty(PROP_SOURCES, DEFAULT_SOURCES);
if (!sources.contains("ssu"))
return;
if (!isValid) {
// ignore them
if (_log.shouldLog(Log.ERROR))
_log.error("The router " + from + " told us we have an invalid IP - "
+ Addresses.toString(ourIP, ourPort) + ". Lets throw tomatoes at them");
markUnreachable(from);
//_context.banlist().banlistRouter(from, "They said we had an invalid IP", STYLE);
return;
}
RouterAddress addr = getCurrentExternalAddress(false);
if (inboundRecent && addr != null && addr.getPort() > 0 && addr.getHost() != null) {
// use OS clock since its an ordering thing, not a time thing
// Note that this fails us if we switch from one IP to a second, then back to the first,
// as some routers still have the first IP and will successfully connect,
// leaving us thinking the second IP is still good.
if (_log.shouldLog(Log.INFO))
_log.info("Ignoring IP address suggestion, since we have received an inbound con recently");
} else {
// New IP
boolean changeIt = false;
synchronized(this) {
if (from.equals(_lastFrom) || !eq(_lastOurIP, _lastOurPort, ourIP, ourPort)) {
_lastFrom = from;
_lastOurIP = ourIP;
_lastOurPort = ourPort;
if (_log.shouldLog(Log.INFO))
_log.info("The router " + from + " told us we have a new IP - "
+ Addresses.toString(ourIP, ourPort) + ". Wait until somebody else tells us the same thing.");
} else {
_lastFrom = from;
_lastOurIP = ourIP;
_lastOurPort = ourPort;
changeIt = true;
}
}
if (changeIt) {
if (_log.shouldLog(Log.INFO))
_log.info(from + " and " + _lastFrom + " agree we have a new IP - "
+ Addresses.toString(ourIP, ourPort) + ". Changing address.");
changeAddress(ourIP, ourPort);
}
}
}
/**
* Possibly change our external address to the IP/port.
* IP/port are already validated, but not yet compared to current IP/port.
* We compare here.
*
* @param ourIP MUST have been previously validated with isValid()
* IPv4 or IPv6 OK
* @param ourPort >= 1024 or 0 for no change
*/
private boolean changeAddress(byte ourIP[], int ourPort) {
// this defaults to true when we are firewalled and false otherwise.
boolean fixedPort = getIsPortFixed();
boolean updated = false;
boolean fireTest = false;
boolean isIPv6 = ourIP.length == 16;
RouterAddress current = getCurrentExternalAddress(isIPv6);
byte[] externalListenHost = current != null ? current.getIP() : null;
int externalListenPort = current != null ? current.getPort() : getRequestedPort(isIPv6);
if (_log.shouldLog(Log.INFO))
_log.info("Change address? status = " + _reachabilityStatus +
" diff = " + (_context.clock().now() - _reachabilityStatusLastUpdated) +
" old = " + Addresses.toString(externalListenHost, externalListenPort) +
" new = " + Addresses.toString(ourIP, ourPort));
if ((fixedPort && externalListenPort > 0) || ourPort <= 0)
ourPort = externalListenPort;
synchronized (this) {
if (ourPort > 0 &&
!eq(externalListenHost, externalListenPort, ourIP, ourPort)) {
// This prevents us from changing our IP when we are not firewalled
//if ( (_reachabilityStatus != CommSystemFacade.STATUS_OK) ||
// (_externalListenHost == null) || (_externalListenPort <= 0) ||
// (_context.clock().now() - _reachabilityStatusLastUpdated > 2*TEST_FREQUENCY) ) {
// they told us something different and our tests are either old or failing
if (_log.shouldLog(Log.WARN))
_log.warn("Trying to change our external address to " +
Addresses.toString(ourIP, ourPort));
RouterAddress newAddr = rebuildExternalAddress(ourIP, ourPort, true);
updated = newAddr != null;
//} else {
// // they told us something different, but our tests are recent and positive,
// // so lets test again
// fireTest = true;
// if (_log.shouldLog(Log.WARN))
// _log.warn("Different address, but we're fine.. (" + _reachabilityStatus + ")");
//}
} else {
// matched what we expect
if (_log.shouldLog(Log.INFO))
_log.info("Same address as the current one");
}
}
if (fireTest) {
// always false, commented out above
_context.statManager().addRateData("udp.addressTestInsteadOfUpdate", 1);
_testEvent.forceRunImmediately(isIPv6);
} else if (updated) {
_context.statManager().addRateData("udp.addressUpdated", 1);
Map<String, String> changes = new HashMap<String, String>();
if (ourIP.length == 4 && !fixedPort)
changes.put(PROP_EXTERNAL_PORT, Integer.toString(ourPort));
// queue a country code lookup of the new IP
if (ourIP.length == 4)
_context.commSystem().queueLookup(ourIP);
// store these for laptop-mode (change ident on restart... or every time... when IP changes)
// IPV4 ONLY
String oldIP = _context.getProperty(PROP_IP);
String newIP = Addresses.toString(ourIP);
if (ourIP.length == 4 && !newIP.equals(oldIP)) {
long lastChanged = 0;
long now = _context.clock().now();
String lcs = _context.getProperty(PROP_IP_CHANGE);
if (lcs != null) {
try {
lastChanged = Long.parseLong(lcs);
} catch (NumberFormatException nfe) {}
}
changes.put(PROP_IP, newIP);
changes.put(PROP_IP_CHANGE, Long.toString(now));
_context.router().saveConfig(changes, null);
if (oldIP != null) {
_context.router().eventLog().addEvent(EventLog.CHANGE_IP, newIP);
}
// laptop mode
// For now, only do this at startup
if (oldIP != null &&
System.getProperty("wrapper.version") != null &&
_context.getBooleanProperty(PROP_LAPTOP_MODE) &&
now - lastChanged > 10*60*1000 &&
_context.router().getUptime() < 10*60*1000) {
_log.log(Log.CRIT, "IP changed, restarting with a new identity and port");
// this removes the UDP port config
_context.router().killKeys();
// do we need WrapperManager.signalStopped() like in ConfigServiceHandler ???
// without it, the wrapper complains "shutdown unexpectedly"
// but we can't have that dependency in the router
_context.router().shutdown(Router.EXIT_HARD_RESTART);
// doesn't return
}
} else if (ourIP.length == 4 && !fixedPort) {
// save PROP_EXTERNAL_PORT
_context.router().saveConfig(changes, null);
}
// deadlock thru here ticket #1699
_context.router().rebuildRouterInfo();
_testEvent.forceRunImmediately(isIPv6);
}
return updated;
}
/**
* @param laddr and raddr may be null
*/
private static final boolean eq(byte laddr[], int lport, byte raddr[], int rport) {
return (rport == lport) && DataHelper.eq(laddr, raddr);
}
/**
* An IPv6 address is only valid if we are configured to support IPv6
* AND we have a public IPv6 address.
*
* @param addr may be null, returns false
*/
public final boolean isValid(byte addr[]) {
if (addr == null) return false;
if (isPubliclyRoutable(addr) &&
(addr.length != 16 || _haveIPv6Address))
return true;
return allowLocal();
}
/**
* Was true before 0.9.2
* Now false if we need introducers (as perhaps that's why we need them,
* our firewall is changing our port), unless overridden by the property.
* We must have an accurate external port when firewalled, or else
* our signature of the SessionCreated packet will be invalid.
*/
private boolean getIsPortFixed() {
String prop = _context.getProperty(PROP_FIXED_PORT);
if (prop != null)
return Boolean.parseBoolean(prop);
Status status = getReachabilityStatus();
return status != Status.REJECT_UNSOLICITED &&
status != Status.IPV4_FIREWALLED_IPV6_OK &&
status != Status.IPV4_FIREWALLED_IPV6_UNKNOWN;
}
/**
* get the state for the peer at the given remote host/port, or null
* if no state exists
*/
PeerState getPeerState(RemoteHostId hostInfo) {
return _peersByRemoteHost.get(hostInfo);
}
/**
* Get the states for all peers at the given remote host, ignoring port.
* Used for a last-chance search for a peer that changed port, by PacketHandler.
* Always returns empty list for IPv6 hostInfo.
* @since 0.9.3
*/
List<PeerState> getPeerStatesByIP(RemoteHostId hostInfo) {
List<PeerState> rv = new ArrayList<PeerState>(4);
byte[] ip = hostInfo.getIP();
if (ip != null && ip.length == 4) {
for (PeerState ps : _peersByIdent.values()) {
if (DataHelper.eq(ip, ps.getRemoteIP()))
rv.add(ps);
}
}
return rv;
}
/**
* get the state for the peer with the given ident, or null
* if no state exists
*/
PeerState getPeerState(Hash remotePeer) {
return _peersByIdent.get(remotePeer);
}
/**
* Remove and add to peersByRemoteHost map
* @since 0.9.3
*/
void changePeerPort(PeerState peer, int newPort) {
// this happens a lot
int oldPort;
synchronized (_addDropLock) {
oldPort = peer.getRemotePort();
if (oldPort != newPort) {
_peersByRemoteHost.remove(peer.getRemoteHostId());
peer.changePort(newPort);
_peersByRemoteHost.put(peer.getRemoteHostId(), peer);
}
}
if (_log.shouldInfo() && oldPort != newPort)
_log.info("Changed port from " + oldPort + " to " + newPort + " for " + peer);
}
/**
* For IntroductionManager
* @return may be null if not started
* @since 0.9.2
*/
EstablishmentManager getEstablisher() {
return _establisher;
}
/**
* Intercept RouterInfo entries received directly from a peer to inject them into
* the PeersByCapacity listing.
*
*/
/*
public void messageReceived(I2NPMessage inMsg, RouterIdentity remoteIdent, Hash remoteIdentHash, long msToReceive, int bytesReceived) {
if (inMsg instanceof DatabaseStoreMessage) {
DatabaseStoreMessage dsm = (DatabaseStoreMessage)inMsg;
if (dsm.getValueType() == DatabaseStoreMessage.KEY_TYPE_ROUTERINFO) {
Hash from = remoteIdentHash;
if (from == null)
from = remoteIdent.getHash();
if (from.equals(dsm.getKey())) {
// db info received directly from the peer - inject it into the peersByCapacity
RouterInfo info = dsm.getRouterInfo();
Set addresses = info.getAddresses();
for (Iterator iter = addresses.iterator(); iter.hasNext(); ) {
RouterAddress addr = (RouterAddress)iter.next();
if (!STYLE.equals(addr.getTransportStyle()))
continue;
Properties opts = addr.getOptions();
if ( (opts != null) && (info.isValid()) ) {
String capacities = opts.getProperty(UDPAddress.PROP_CAPACITY);
if (capacities != null) {
if (_log.shouldLog(Log.INFO))
_log.info("Intercepting and storing the capacities for " + from.toBase64() + ": " + capacities);
PeerState peer = getPeerState(from);
for (int i = 0; i < capacities.length(); i++) {
char capacity = capacities.charAt(i);
int cap = capacity - 'A';
if ( (cap < 0) || (cap >= _peersByCapacity.length) )
continue;
List peers = _peersByCapacity[cap];
synchronized (peers) {
if ( (peers.size() < MAX_PEERS_PER_CAPACITY) && (!peers.contains(peer)) )
peers.add(peer);
}
}
}
}
// this was an SSU address so we're done now
break;
}
}
}
}
super.messageReceived(inMsg, remoteIdent, remoteIdentHash, msToReceive, bytesReceived);
}
*/
/**
* add the peer info, returning true if it went in properly, false if
* it was rejected (causes include peer ident already connected, or no
* remote host info known
*
*/
boolean addRemotePeerState(PeerState peer) {
if (_log.shouldLog(Log.INFO))
_log.info("Add remote peer state: " + peer);
synchronized(_addDropLock) {
return locked_addRemotePeerState(peer);
}
}
private boolean locked_addRemotePeerState(PeerState peer) {
Hash remotePeer = peer.getRemotePeer();
long oldEstablishedOn = -1;
PeerState oldPeer = null;
if (remotePeer != null) {
oldPeer = _peersByIdent.put(remotePeer, peer);
if ( (oldPeer != null) && (oldPeer != peer) ) {
// this happens a lot
if (_log.shouldInfo())
_log.info("Peer already connected (PBID): old=" + oldPeer + " new=" + peer);
// transfer over the old state/inbound message fragments/etc
peer.loadFrom(oldPeer);
oldEstablishedOn = oldPeer.getKeyEstablishedTime();
}
}
RemoteHostId remoteId = peer.getRemoteHostId();
if (oldPeer != null) {
oldPeer.dropOutbound();
_introManager.remove(oldPeer);
_expireEvent.remove(oldPeer);
RemoteHostId oldID = oldPeer.getRemoteHostId();
if (!remoteId.equals(oldID)) {
// leak fix, remove old address
if (_log.shouldInfo())
_log.info(remotePeer + " changed address FROM " + oldID + " TO " + remoteId);
PeerState oldPeer2 = _peersByRemoteHost.remove(oldID);
// different ones in the two maps? shouldn't happen
if (oldPeer2 != oldPeer && oldPeer2 != null) {
oldPeer2.dropOutbound();
_introManager.remove(oldPeer2);
_expireEvent.remove(oldPeer2);
}
}
}
// Should always be direct... except maybe for hidden mode?
// or do we always know the IP by now?
if (remoteId.getIP() == null && _log.shouldLog(Log.WARN))
_log.warn("Add indirect: " + peer);
// don't do this twice
PeerState oldPeer2 = _peersByRemoteHost.put(remoteId, peer);
if (oldPeer2 != null && oldPeer2 != peer && oldPeer2 != oldPeer) {
// this shouldn't happen, should have been removed above
if (_log.shouldLog(Log.WARN))
_log.warn("Peer already connected (PBRH): old=" + oldPeer2 + " new=" + peer);
// transfer over the old state/inbound message fragments/etc
peer.loadFrom(oldPeer2);
oldEstablishedOn = oldPeer2.getKeyEstablishedTime();
oldPeer2.dropOutbound();
_introManager.remove(oldPeer2);
_expireEvent.remove(oldPeer2);
}
if (_log.shouldLog(Log.WARN) && !_mismatchLogged && _peersByIdent.size() != _peersByRemoteHost.size()) {
_mismatchLogged = true;
_log.warn("Size Mismatch after add: " + peer
+ " byIDsz = " + _peersByIdent.size()
+ " byHostsz = " + _peersByRemoteHost.size());
}
_activeThrottle.unchoke(peer.getRemotePeer());
markReachable(peer.getRemotePeer(), peer.isInbound());
//_context.banlist().unbanlistRouter(peer.getRemotePeer(), STYLE);
//if (SHOULD_FLOOD_PEERS)
// _flooder.addPeer(peer);
_expireEvent.add(peer);
_introManager.add(peer);
if (oldEstablishedOn > 0)
_context.statManager().addRateData("udp.alreadyConnected", oldEstablishedOn);
synchronized(_rebuildLock) {
rebuildIfNecessary();
Status status = getReachabilityStatus();
if (status != Status.OK &&
status != Status.IPV4_OK_IPV6_UNKNOWN &&
status != Status.IPV4_OK_IPV6_FIREWALLED &&
status != Status.IPV4_DISABLED_IPV6_OK &&
status != Status.IPV4_DISABLED_IPV6_UNKNOWN &&
status != Status.IPV4_DISABLED_IPV6_FIREWALLED &&
status != Status.DISCONNECTED &&
_reachabilityStatusUnchanged < 7) {
_testEvent.forceRunSoon(peer.isIPv6());
}
}
return true;
}
/*** infinite loop
public RouterAddress getCurrentAddress() {
if (needsRebuild())
rebuildExternalAddress(false);
return super.getCurrentAddress();
}
***/
@Override
public void messageReceived(I2NPMessage inMsg, RouterIdentity remoteIdent, Hash remoteIdentHash, long msToReceive, int bytesReceived) {
if (inMsg.getType() == DatabaseStoreMessage.MESSAGE_TYPE) {
DatabaseStoreMessage dsm = (DatabaseStoreMessage)inMsg;
DatabaseEntry entry = dsm.getEntry();
if (entry == null)
return;
if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO &&
((RouterInfo) entry).getNetworkId() != _networkID) {
// this is pre-0.6.1.10, so it isn't going to happen any more
/*
if (remoteIdentHash != null) {
_context.banlist().banlistRouter(remoteIdentHash, "Sent us a peer from the wrong network");
dropPeer(remoteIdentHash);
if (_log.shouldLog(Log.ERROR))
_log.error("Dropping the peer " + remoteIdentHash
+ " because they are in the wrong net");
} else if (remoteIdent != null) {
_context.banlist().banlistRouter(remoteIdent.calculateHash(), "Sent us a peer from the wrong network");
dropPeer(remoteIdent.calculateHash());
if (_log.shouldLog(Log.ERROR))
_log.error("Dropping the peer " + remoteIdent.calculateHash()
+ " because they are in the wrong net");
}
*/
Hash peerHash = entry.getHash();
PeerState peer = getPeerState(peerHash);
if (peer != null) {
RemoteHostId remote = peer.getRemoteHostId();
_dropList.add(remote);
_context.statManager().addRateData("udp.dropPeerDroplist", 1);
_context.simpleTimer2().addEvent(new RemoveDropList(remote), DROPLIST_PERIOD);
}
markUnreachable(peerHash);
_context.banlist().banlistRouter(peerHash, "Part of the wrong network, version = " + ((RouterInfo) entry).getVersion());
//_context.banlist().banlistRouter(peerHash, "Part of the wrong network", STYLE);
if (peer != null)
sendDestroy(peer);
dropPeer(peerHash, false, "wrong network");
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping the peer " + peerHash + " because they are in the wrong net: " + entry);
return;
} else {
if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received an RI from the same net");
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received a leaseSet: " + dsm);
}
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received another message: " + inMsg.getClass().getName());
}
PeerState peer = getPeerState(remoteIdentHash);
super.messageReceived(inMsg, remoteIdent, remoteIdentHash, msToReceive, bytesReceived);
if (peer != null)
peer.expireInboundMessages();
}
private class RemoveDropList implements SimpleTimer.TimedEvent {
private final RemoteHostId _peer;
public RemoveDropList(RemoteHostId peer) { _peer = peer; }
public void timeReached() {
_dropList.remove(_peer);
}
}
boolean isInDropList(RemoteHostId peer) { return _dropList.contains(peer); }
/**
* This does not send a session destroy, caller must do that if desired.
*
* @param shouldBanlist doesn't really, only sets unreachable
*/
void dropPeer(Hash peer, boolean shouldBanlist, String why) {
PeerState state = getPeerState(peer);
if (state != null)
dropPeer(state, shouldBanlist, why);
}
/**
* This does not send a session destroy, caller must do that if desired.
*
* @param shouldBanlist doesn't really, only sets unreachable
*/
void dropPeer(PeerState peer, boolean shouldBanlist, String why) {
if (_log.shouldLog(Log.INFO)) {
long now = _context.clock().now();
StringBuilder buf = new StringBuilder(4096);
long timeSinceSend = now - peer.getLastSendTime();
long timeSinceRecv = now - peer.getLastReceiveTime();
long timeSinceAck = now - peer.getLastACKSend();
long timeSinceSendOK = now - peer.getLastSendFullyTime();
int consec = peer.getConsecutiveFailedSends();
buf.append("Dropping remote peer: ").append(peer.toString()).append(" banlist? ").append(shouldBanlist);
buf.append(" lifetime: ").append(now - peer.getKeyEstablishedTime());
buf.append(" time since send/fully/recv/ack: ").append(timeSinceSend).append(" / ");
buf.append(timeSinceSendOK).append(" / ");
buf.append(timeSinceRecv).append(" / ").append(timeSinceAck);
buf.append(" consec failures: ").append(consec);
if (why != null)
buf.append(" cause: ").append(why);
/*
buf.append("Existing peers: \n");
synchronized (_peersByIdent) {
for (Iterator iter = _peersByIdent.keySet().iterator(); iter.hasNext(); ) {
Hash c = (Hash)iter.next();
PeerState p = (PeerState)_peersByIdent.get(c);
if (c.equals(peer.getRemotePeer())) {
if (p != peer) {
buf.append(" SAME PEER, DIFFERENT STATE ");
} else {
buf.append(" same peer, same state ");
}
} else {
buf.append("Peer ").append(p.toString()).append(" ");
}
buf.append(" lifetime: ").append(now - p.getKeyEstablishedTime());
timeSinceSend = now - p.getLastSendTime();
timeSinceRecv = now - p.getLastReceiveTime();
timeSinceAck = now - p.getLastACKSend();
buf.append(" time since send/recv/ack: ").append(timeSinceSend).append(" / ");
buf.append(timeSinceRecv).append(" / ").append(timeSinceAck);
buf.append("\n");
}
}
*/
_log.info(buf.toString(), new Exception("Dropped by"));
}
synchronized(_addDropLock) {
locked_dropPeer(peer, shouldBanlist, why);
}
rebuildIfNecessary();
}
/**
* This does not send a session destroy, caller must do that if desired.
*
* @param shouldBanlist doesn't really, only sets unreachable
*/
private void locked_dropPeer(PeerState peer, boolean shouldBanlist, String why) {
peer.dropOutbound();
peer.expireInboundMessages();
_introManager.remove(peer);
_fragments.dropPeer(peer);
PeerState altByIdent = null;
if (peer.getRemotePeer() != null) {
dropPeerCapacities(peer);
if (shouldBanlist) {
markUnreachable(peer.getRemotePeer());
//_context.banlist().banlistRouter(peer.getRemotePeer(), "dropped after too many retries", STYLE);
}
long now = _context.clock().now();
_context.statManager().addRateData("udp.droppedPeer", now - peer.getLastReceiveTime(), now - peer.getKeyEstablishedTime());
altByIdent = _peersByIdent.remove(peer.getRemotePeer());
}
RemoteHostId remoteId = peer.getRemoteHostId();
PeerState altByHost = _peersByRemoteHost.remove(remoteId);
if (altByIdent != altByHost && _log.shouldLog(Log.WARN))
_log.warn("Mismatch on remove, RHID = " + remoteId
+ " byID = " + altByIdent
+ " byHost = " + altByHost
+ " byIDsz = " + _peersByIdent.size()
+ " byHostsz = " + _peersByRemoteHost.size());
// unchoke 'em, but just because we'll never talk again...
_activeThrottle.unchoke(peer.getRemotePeer());
//if (SHOULD_FLOOD_PEERS)
// _flooder.removePeer(peer);
_expireEvent.remove(peer);
// deal with races to make sure we drop the peers fully
if ( (altByIdent != null) && (peer != altByIdent) ) locked_dropPeer(altByIdent, shouldBanlist, "recurse");
if ( (altByHost != null) && (peer != altByHost) ) locked_dropPeer(altByHost, shouldBanlist, "recurse");
}
/**
* Rebuild the IPv4 external address if required
*/
private void rebuildIfNecessary() {
synchronized (_rebuildLock) {
if (locked_needsRebuild())
rebuildExternalAddress();
}
}
private boolean locked_needsRebuild() {
if (_needsRebuild) return true; // simple enough
if (_context.router().isHidden()) return false;
RouterAddress addr = getCurrentAddress(false);
if (introducersRequired()) {
UDPAddress ua = new UDPAddress(addr);
long now = _context.clock().now();
int valid = 0;
for (int i = 0; i < ua.getIntroducerCount(); i++) {
// warning: this is only valid as long as we use the ident hash as their key.
byte[] key = ua.getIntroducerKey(i);
if (key.length != Hash.HASH_LENGTH)
continue;
long exp = ua.getIntroducerExpiration(i);
if (exp > 0 && exp < now + INTRODUCER_EXPIRATION_MARGIN)
continue;
PeerState peer = getPeerState(new Hash(key));
if (peer != null)
valid++;
}
long sinceSelected = now - _introducersSelectedOn;
if (valid >= PUBLIC_RELAY_COUNT) {
// try to shift 'em around every 10 minutes or so
if (sinceSelected > 17*60*1000) {
if (_log.shouldLog(Log.WARN))
_log.warn("Our introducers are valid, but haven't changed in " + DataHelper.formatDuration(sinceSelected) + ", so lets rechoose");
return true;
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Our introducers are valid and were selected " + DataHelper.formatDuration(sinceSelected) + " ago");
return false;
}
} else if (sinceSelected > 2*60*1000) {
// Rate limit to prevent rapid churn after transition to firewalled or at startup
if (_log.shouldLog(Log.INFO))
_log.info("Need more introducers (have " +valid + " need " + PUBLIC_RELAY_COUNT + ')');
return true;
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Need more introducers (have " +valid + " need " + PUBLIC_RELAY_COUNT + ')' +
" but we just chose them " + DataHelper.formatDuration(sinceSelected) + " ago so wait");
// TODO also check to see if we actually have more available
return false;
}
} else {
byte[] externalListenHost = addr != null ? addr.getIP() : null;
int externalListenPort = addr != null ? addr.getPort() : -1;
boolean rv = (externalListenHost == null) || (externalListenPort <= 0);
if (!rv) {
// shortcut to determine if introducers are present
if (addr.getOption("ihost0") != null)
rv = true; // status == ok and we don't actually need introducers, so rebuild
}
if (rv) {
if (_log.shouldLog(Log.INFO))
_log.info("Need to initialize our direct SSU info (" + Addresses.toString(externalListenHost, externalListenPort) + ')');
} else if (addr.getPort() <= 0 || addr.getHost() == null) {
if (_log.shouldLog(Log.INFO))
_log.info("Our direct SSU info is initialized, but not used in our address yet");
rv = true;
} else {
//_log.info("Our direct SSU info is initialized");
}
return rv;
}
}
/**
* Make sure we don't think this dropped peer is capable of doing anything anymore...
*
*/
private void dropPeerCapacities(PeerState peer) {
/*
RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer.getRemotePeer());
if (info != null) {
String capacities = info.getOptions().getProperty(UDPAddress.PROP_CAPACITY);
if (capacities != null) {
for (int i = 0; i < capacities.length(); i++) {
char capacity = capacities.charAt(i);
int cap = capacity - 'A';
if ( (cap < 0) || (cap >= _peersByCapacity.length) )
continue;
List peers = _peersByCapacity[cap];
synchronized (peers) {
peers.remove(peer);
}
}
}
}
*/
}
/**
* This sends it directly out, bypassing OutboundMessageFragments.
* The only queueing is for the bandwidth limiter.
* BLOCKING if OB queue is full.
*/
void send(UDPPacket packet) {
if (_pusher != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending packet " + packet);
_pusher.send(packet);
} else {
_log.error("No pusher", new Exception());
}
}
/**
* Send a session destroy message, bypassing OMF and PacketPusher.
* BLOCKING if OB queue is full.
*
* @since 0.8.9
*/
void sendDestroy(PeerState peer) {
// peer must be fully established
if (peer.getCurrentCipherKey() == null)
return;
UDPPacket pkt = _destroyBuilder.buildSessionDestroyPacket(peer);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending destroy to : " + peer);
send(pkt);
}
/**
* Send a session destroy message to everybody.
* BLOCKING for at least 1 sec per 1K peers, more if BW is very low or if OB queue is full.
*
* @since 0.8.9
*/
private void destroyAll() {
for (UDPEndpoint endpoint : _endpoints) {
endpoint.clearOutbound();
}
int howMany = _peersByIdent.size();
// use no more than 1/4 of configured bandwidth
final int burst = 8;
int pps = Math.max(48, (_context.bandwidthLimiter().getOutboundKBytesPerSecond() * 1000 / 4) / 48);
int burstps = pps / burst;
// max of 1000 pps
int toSleep = Math.max(8, (1000 / burstps));
int count = 0;
if (_log.shouldInfo())
_log.info("Sending destroy to : " + howMany + " peers");
for (PeerState peer : _peersByIdent.values()) {
sendDestroy(peer);
// 1000 per second * 48 bytes = 400 KBps
if ((++count) % burst == 0) {
try {
Thread.sleep(toSleep);
} catch (InterruptedException ie) {}
}
}
toSleep = Math.min(howMany / 3, 750);
if (toSleep > 0) {
try {
Thread.sleep(toSleep);
} catch (InterruptedException ie) {}
}
}
public TransportBid bid(RouterInfo toAddress, long dataSize) {
if (dataSize > OutboundMessageState.MAX_MSG_SIZE) {
// NTCP max is lower, so msg will get dropped
return null;
}
Hash to = toAddress.getIdentity().calculateHash();
PeerState peer = getPeerState(to);
if (peer != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("bidding on a message to an established peer: " + peer);
if (preferUDP())
return _cachedBid[FAST_PREFERRED_BID];
else
return _cachedBid[FAST_BID];
} else {
// If we don't have a port, all is lost
if ( _reachabilityStatus == Status.HOSED) {
markUnreachable(to);
return null;
}
// Validate his SSU address
RouterAddress addr = getTargetAddress(toAddress);
if (addr == null) {
markUnreachable(to);
return null;
}
// Check for supported sig type
SigType type = toAddress.getIdentity().getSigType();
if (type == null || !type.isAvailable()) {
markUnreachable(to);
return null;
}
// Can we connect to them if we are not DSA?
RouterInfo us = _context.router().getRouterInfo();
if (us != null) {
RouterIdentity id = us.getIdentity();
if (id.getSigType() != SigType.DSA_SHA1) {
String v = toAddress.getVersion();
if (VersionComparator.comp(v, MIN_SIGTYPE_VERSION) < 0) {
markUnreachable(to);
return null;
}
}
}
if (!allowConnection())
return _cachedBid[TRANSIENT_FAIL_BID];
if (_log.shouldLog(Log.DEBUG))
_log.debug("bidding on a message to an unestablished peer: " + to);
// Try to maintain at least 5 peers (30 for v6) so we can determine our IP address and
// we have a selection to run peer tests with.
// If we are firewalled, and we don't have enough peers that volunteered to
// also introduce us, also bid aggressively so we are preferred over NTCP.
// (Otherwise we only talk UDP to those that are firewalled, and we will
// never get any introducers)
int count = _peersByIdent.size();
if (alwaysPreferUDP()) {
return _cachedBid[SLOW_PREFERRED_BID];
} else if (count < _min_peers ||
(_haveIPv6Address && count < _min_v6_peers) ||
(introducersRequired() && _introManager.introducerCount() < MIN_INTRODUCER_POOL)) {
// Even if we haven't hit our minimums, give NTCP a chance some of the time.
// This may make things work a little faster at startup
// (especially when we have an IPv6 address and the increased minimums),
// and if UDP is completely blocked we'll still have some connectivity.
// TODO After some time, decide that UDP is blocked/broken and return TRANSIENT_FAIL_BID?
if (_context.random().nextInt(4) == 0)
return _cachedBid[SLOWEST_BID];
else
return _cachedBid[SLOW_PREFERRED_BID];
} else if (preferUDP()) {
return _cachedBid[SLOW_BID];
} else if (haveCapacity()) {
if (addr.getCost() > DEFAULT_COST)
return _cachedBid[SLOWEST_COST_BID];
else
return _cachedBid[SLOWEST_BID];
} else {
if (addr.getCost() > DEFAULT_COST)
return _cachedBid[NEAR_CAPACITY_COST_BID];
else
return _cachedBid[NEAR_CAPACITY_BID];
}
}
}
/**
* Get first available address we can use.
* @return address or null
* @since 0.9.6
*/
RouterAddress getTargetAddress(RouterInfo target) {
List<RouterAddress> addrs = getTargetAddresses(target);
for (int i = 0; i < addrs.size(); i++) {
RouterAddress addr = addrs.get(i);
if (addr.getOption("ihost0") == null) {
byte[] ip = addr.getIP();
int port = addr.getPort();
if (ip == null || !TransportUtil.isValidPort(port) ||
(!isValid(ip)) ||
(Arrays.equals(ip, getExternalIP()) && !allowLocal())) {
continue;
}
} else {
// introducers
if (getIPv6Config() == IPV6_ONLY)
continue;
}
return addr;
}
return null;
}
private boolean preferUDP() {
String pref = _context.getProperty(PROP_PREFER_UDP, DEFAULT_PREFER_UDP);
return (pref != null) && ! "false".equals(pref);
}
private boolean alwaysPreferUDP() {
String pref = _context.getProperty(PROP_PREFER_UDP, DEFAULT_PREFER_UDP);
return (pref != null) && "always".equals(pref);
}
// We used to have MAX_IDLE_TIME = 5m, but this causes us to drop peers
// and lose the old introducer tags, causing introduction fails,
// so we keep the max time long to give the introducer keepalive code
// in the IntroductionManager a chance to work.
public static final int EXPIRE_TIMEOUT = 20*60*1000;
private static final int MAX_IDLE_TIME = EXPIRE_TIMEOUT;
public static final int MIN_EXPIRE_TIMEOUT = 165*1000;
public String getStyle() { return STYLE; }
@Override
public void send(OutNetMessage msg) {
if (msg == null) return;
if (msg.getTarget() == null) return;
if (msg.getTarget().getIdentity() == null) return;
if (_establisher == null) {
failed(msg, "UDP not up yet");
return;
}
msg.timestamp("sending on UDP transport");
Hash to = msg.getTarget().getIdentity().calculateHash();
PeerState peer = getPeerState(to);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending to " + (to != null ? to.toString() : ""));
if (peer != null) {
long lastSend = peer.getLastSendFullyTime();
long lastRecv = peer.getLastReceiveTime();
long now = _context.clock().now();
int inboundActive = peer.expireInboundMessages();
if ( (lastSend > 0) && (lastRecv > 0) ) {
if ( (now - lastSend > MAX_IDLE_TIME) &&
(now - lastRecv > MAX_IDLE_TIME) &&
(peer.getConsecutiveFailedSends() > 0) &&
(inboundActive <= 0)) {
// peer is waaaay idle, drop the con and queue it up as a new con
dropPeer(peer, false, "proactive reconnection");
msg.timestamp("peer is really idle, dropping con and reestablishing");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Proactive reestablish to " + to);
_establisher.establish(msg);
_context.statManager().addRateData("udp.proactiveReestablish", now-lastSend, now-peer.getKeyEstablishedTime());
return;
}
}
msg.timestamp("enqueueing for an already established peer");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Add to fragments for " + to);
// See comments in DummyThrottle.java
if (USE_PRIORITY)
_outboundMessages.add(msg);
else // skip the priority queue and go straight to the active pool
_fragments.add(msg);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Establish new connection to " + to);
msg.timestamp("establishing a new connection");
_establisher.establish(msg);
}
}
/**
* Send only if established, otherwise fail immediately.
* Never queue with the establisher.
* @since 0.9.2
*/
void sendIfEstablished(OutNetMessage msg) {
_fragments.add(msg);
}
/**
* "injected" message from the EstablishmentManager.
* If you have multiple messages, use the list variant,
* so the messages may be bundled efficiently.
*
* @param peer all messages MUST be going to this peer
*/
void send(I2NPMessage msg, PeerState peer) {
try {
OutboundMessageState state = new OutboundMessageState(_context, msg, peer);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Injecting a data message to a new peer: " + peer);
_fragments.add(state, peer);
} catch (IllegalArgumentException iae) {
if (_log.shouldLog(Log.WARN))
_log.warn("Shouldnt happen", new Exception("I did it"));
}
}
/**
* "injected" message from the EstablishmentManager,
* plus pending messages to send,
* so the messages may be bundled efficiently.
* Called at end of outbound establishment.
*
* @param msg may be null if nothing to inject
* @param msgs non-null, may be empty
* @param peer all messages MUST be going to this peer
* @since 0.9.24
*/
void send(I2NPMessage msg, List<OutNetMessage> msgs, PeerState peer) {
try {
int sz = msgs.size();
List<OutboundMessageState> states = new ArrayList<OutboundMessageState>(sz + 1);
if (msg != null) {
OutboundMessageState state = new OutboundMessageState(_context, msg, peer);
states.add(state);
}
for (int i = 0; i < sz; i++) {
OutboundMessageState state = new OutboundMessageState(_context, msgs.get(i), peer);
states.add(state);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Injecting " + states.size() + " data messages to a new peer: " + peer);
_fragments.add(states, peer);
} catch (IllegalArgumentException iae) {
if (_log.shouldLog(Log.WARN))
_log.warn("Shouldnt happen", new Exception("I did it"));
}
}
/**
* "injected" messages from the EstablishmentManager.
* Called at end of inbound establishment.
*
* @param peer all messages MUST be going to this peer
* @since 0.9.24
*/
void send(List<I2NPMessage> msgs, PeerState peer) {
try {
int sz = msgs.size();
List<OutboundMessageState> states = new ArrayList<OutboundMessageState>(sz);
for (int i = 0; i < sz; i++) {
OutboundMessageState state = new OutboundMessageState(_context, msgs.get(i), peer);
states.add(state);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Injecting " + sz + " data messages to a new peer: " + peer);
_fragments.add(states, peer);
} catch (IllegalArgumentException iae) {
if (_log.shouldLog(Log.WARN))
_log.warn("Shouldnt happen", new Exception("I did it"));
}
}
// we don't need the following, since we have our own queueing
protected void outboundMessageReady() { throw new UnsupportedOperationException("Not used for UDP"); }
public void startListening() {
startup();
}
public void stopListening() {
shutdown();
replaceAddress(null);
}
private boolean explicitAddressSpecified() {
String h = _context.getProperty(PROP_EXTERNAL_HOST);
// Bug in config.jsp prior to 0.7.14, sets an empty host config
return h != null && h.length() > 0;
}
/**
* Rebuild to get updated cost and introducers.
* Do not tell the router (he is the one calling this)
* @since 0.7.12
*/
@Override
public List<RouterAddress> updateAddress() {
rebuildExternalAddress(false);
return getCurrentAddresses();
}
/**
* Update our IPv4 addresses AND tell the router to rebuild and republish the router info.
*
* @return the new address if changed, else null
*/
private RouterAddress rebuildExternalAddress() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("REA1");
return rebuildExternalAddress(true);
}
/**
* Update our IPv4 address and optionally tell the router to rebuild and republish the router info.
*
* @param allowRebuildRouterInfo whether to tell the router
* @return the new address if changed, else null
*/
private RouterAddress rebuildExternalAddress(boolean allowRebuildRouterInfo) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("REA2 " + allowRebuildRouterInfo);
// if the external port is specified, we want to use that to bind to even
// if we don't know the external host.
int port = _context.getProperty(PROP_EXTERNAL_PORT, -1);
byte[] ip = null;
String host = null;
if (explicitAddressSpecified()) {
host = _context.getProperty(PROP_EXTERNAL_HOST);
if (host != null) {
String[] hosts = DataHelper.split(host, "[,; \r\n\t]");
RouterAddress rv = null;
for (int i = 0; i < hosts.length; i++) {
String h = hosts[i];
if (h.length() <= 0)
continue;
rv = rebuildExternalAddress(h, port, allowRebuildRouterInfo);
}
return rv;
}
} else {
if (!introducersRequired()) {
RouterAddress cur = getCurrentExternalAddress(false);
if (cur != null)
host = cur.getHost();
}
}
return rebuildExternalAddress(host, port, allowRebuildRouterInfo);
}
/**
* Update our IPv4 or IPv6 address and optionally tell the router to rebuild and republish the router info.
*
* @param ip new ip valid IPv4 or IPv6 or null
* @param port new valid port or -1
* @param allowRebuildRouterInfo whether to tell the router
* @return the new address if changed, else null
* @since IPv6
*/
private RouterAddress rebuildExternalAddress(byte[] ip, int port, boolean allowRebuildRouterInfo) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("REA3 " + Addresses.toString(ip, port));
if (ip == null)
return rebuildExternalAddress((String) null, port, allowRebuildRouterInfo);
if (isValid(ip))
return rebuildExternalAddress(Addresses.toString(ip), port, allowRebuildRouterInfo);
return null;
}
/**
* Update our IPv4 or IPv6 address and optionally tell the router to rebuild and republish the router info.
* FIXME no way to remove an IPv6 address
*
* @param host new validated IPv4 or IPv6 or DNS hostname or null
* @param port new validated port or 0/-1
* @param allowRebuildRouterInfo whether to tell the router
* @return the new address if changed, else null
* @since IPv6
*/
private RouterAddress rebuildExternalAddress(String host, int port, boolean allowRebuildRouterInfo) {
synchronized (_rebuildLock) {
return locked_rebuildExternalAddress(host, port, allowRebuildRouterInfo);
}
}
private RouterAddress locked_rebuildExternalAddress(String host, int port, boolean allowRebuildRouterInfo) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("REA4 " + host + ':' + port);
if (_context.router().isHidden())
return null;
OrderedProperties options = new OrderedProperties();
boolean directIncluded;
// DNS name assumed IPv4
boolean isIPv6 = host != null && host.contains(":");
boolean introducersRequired = (!isIPv6) && introducersRequired();
if (!introducersRequired && allowDirectUDP() && port > 0 && host != null) {
options.setProperty(UDPAddress.PROP_PORT, String.valueOf(port));
options.setProperty(UDPAddress.PROP_HOST, host);
directIncluded = true;
} else {
directIncluded = false;
}
boolean introducersIncluded = false;
if (introducersRequired) {
// intro manager now sorts introducers, so
// deepEquals() below will not fail even with same introducers.
// Was only a problem when we had very very few peers to pick from.
RouterAddress current = getCurrentAddress(isIPv6);
int found = _introManager.pickInbound(current, options, PUBLIC_RELAY_COUNT);
if (found > 0) {
if (_log.shouldLog(Log.INFO))
_log.info("Direct? " + directIncluded + " reqd? " + introducersRequired +
" picked introducers: " + found);
_introducersSelectedOn = _context.clock().now();
introducersIncluded = true;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Direct? " + directIncluded + " reqd? " + introducersRequired +
" no introducers");
}
}
// if we have explicit external addresses, they had better be reachable
if (introducersRequired)
options.setProperty(UDPAddress.PROP_CAPACITY, CAP_TESTING);
else
options.setProperty(UDPAddress.PROP_CAPACITY, CAP_TESTING_INTRO);
// MTU since 0.9.2
int mtu;
if (host == null) {
mtu = _mtu;
} else {
try {
InetAddress ia = InetAddress.getByName(host);
mtu = setMTU(ia);
} catch (UnknownHostException uhe) {
mtu = _mtu;
}
}
if (mtu != PeerState.LARGE_MTU)
options.setProperty(UDPAddress.PROP_MTU, Integer.toString(mtu));
if (directIncluded || introducersIncluded) {
// This is called via TransportManager.configTransports() before startup(), prevent NPE
// Note that peers won't connect to us without this - see EstablishmentManager
if (_introKey != null)
options.setProperty(UDPAddress.PROP_INTRO_KEY, _introKey.toBase64());
// SSU seems to regulate at about 85%, so make it a little higher.
// If this is too low, both NTCP and SSU always have incremented cost and
// the whole mechanism is not helpful.
int cost = DEFAULT_COST;
if (ADJUST_COST && !haveCapacity(91))
cost += CONGESTION_COST_ADJUSTMENT;
if (introducersIncluded)
cost += 2;
if (isIPv6) {
TransportUtil.IPv6Config config = getIPv6Config();
if (config == IPV6_PREFERRED)
cost--;
else if (config == IPV6_NOT_PREFERRED)
cost++;
}
RouterAddress addr = new RouterAddress(STYLE, options, cost);
RouterAddress current = getCurrentAddress(isIPv6);
boolean wantsRebuild = !addr.deepEquals(current);
// save the external address, even if we didn't publish it
if (port > 0 && host != null) {
RouterAddress local;
if (directIncluded) {
local = addr;
} else {
OrderedProperties localOpts = new OrderedProperties();
localOpts.setProperty(UDPAddress.PROP_PORT, String.valueOf(port));
localOpts.setProperty(UDPAddress.PROP_HOST, host);
local = new RouterAddress(STYLE, localOpts, cost);
}
replaceCurrentExternalAddress(local, isIPv6);
}
if (wantsRebuild) {
if (_log.shouldLog(Log.INFO))
_log.info("Address rebuilt: " + addr, new Exception());
replaceAddress(addr);
// warning, this calls back into us with allowRebuildRouterInfo = false,
// via CSFI.createAddresses->TM.getAddresses()->updateAddress()->REA
if (allowRebuildRouterInfo)
_context.router().rebuildRouterInfo();
} else {
addr = null;
}
if (!isIPv6)
_needsRebuild = false;
return addr;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Wanted to rebuild my SSU address, but couldn't specify either the direct or indirect info (needs introducers? "
+ introducersRequired + ")", new Exception("source"));
_needsRebuild = true;
// save the external address, even if we didn't publish it
if (port > 0 && host != null) {
OrderedProperties localOpts = new OrderedProperties();
localOpts.setProperty(UDPAddress.PROP_PORT, String.valueOf(port));
localOpts.setProperty(UDPAddress.PROP_HOST, host);
RouterAddress local = new RouterAddress(STYLE, localOpts, DEFAULT_COST);
replaceCurrentExternalAddress(local, isIPv6);
}
if (hasCurrentAddress()) {
// We must remove current address, otherwise the user will see
// "firewalled with inbound NTCP enabled" warning in console.
// Remove the IPv4 address only
removeAddress(false);
// warning, this calls back into us with allowRebuildRouterInfo = false,
// via CSFI.createAddresses->TM.getAddresses()->updateAddress()->REA
if (allowRebuildRouterInfo)
_context.router().rebuildRouterInfo();
}
return null;
}
}
/**
* Simple storage of IP and port, since
* we don't put them in the real, published RouterAddress anymore
* if we are firewalled.
*
* Caller must sync on _rebuildLock
*
* @since 0.9.18
*/
private void replaceCurrentExternalAddress(RouterAddress ra, boolean isIPv6) {
if (isIPv6)
_currentOurV6Address = ra;
else
_currentOurV4Address = ra;
}
/**
* Simple fetch of stored IP and port, since
* we don't put them in the real, published RouterAddress anymore
* if we are firewalled.
*
* @since 0.9.18
*/
private RouterAddress getCurrentExternalAddress(boolean isIPv6) {
// deadlock thru here ticket #1699
synchronized (_rebuildLock) {
return isIPv6 ? _currentOurV6Address : _currentOurV4Address;
}
}
/**
* Replace then tell NTCP that we changed.
*
* @param address the new address or null to remove all
*/
@Override
protected void replaceAddress(RouterAddress address) {
super.replaceAddress(address);
_context.commSystem().notifyReplaceAddress(address);
}
/**
* Remove then tell NTCP that we changed.
*
* @since 0.9.20
*/
@Override
protected void removeAddress(RouterAddress address) {
super.removeAddress(address);
_context.commSystem().notifyRemoveAddress(address);
}
/**
* Remove then tell NTCP that we changed.
*
* @since 0.9.20
*/
@Override
protected void removeAddress(boolean ipv6) {
super.removeAddress(ipv6);
if (ipv6)
_lastInboundIPv6 = 0;
_context.commSystem().notifyRemoveAddress(ipv6);
}
/**
* Calls replaceAddress(address), then shuts down the router if
* dynamic keys is enabled, which it never is, so all this is unused.
*
* @param address the new address or null to remove all
*/
/****
protected void replaceAddress(RouterAddress address, RouterAddress oldAddress) {
replaceAddress(address);
if (oldAddress != null) {
UDPAddress old = new UDPAddress(oldAddress);
InetAddress oldHost = old.getHostAddress();
UDPAddress newAddr = new UDPAddress(address);
InetAddress newHost = newAddr.getHostAddress();
if ( (old.getPort() > 0) && (oldHost != null) && (isValid(oldHost.getAddress())) &&
(newAddr.getPort() > 0) && (newHost != null) && (isValid(newHost.getAddress())) ) {
if ( (old.getPort() != newAddr.getPort()) || (!oldHost.equals(newHost)) ) {
// substantial data has changed, so if we are in 'dynamic keys' mode, restart the
// router hard and regenerate a new identity
if (_context.getBooleanProperty(Router.PROP_DYNAMIC_KEYS)) {
if (_log.shouldLog(Log.ERROR))
_log.error("SSU address updated. new address: "
+ newAddr.getHostAddress() + ":" + newAddr.getPort() + ", old address: "
+ old.getHostAddress() + ":" + old.getPort());
// shutdown itself checks the DYNAMIC_KEYS flag, and if its set to true, deletes
// the keys
_context.router().shutdown(Router.EXIT_HARD_RESTART);
}
}
}
}
}
****/
/**
* Do we require introducers?
*/
public boolean introducersRequired() {
/******************
* Don't do this anymore, as we are removing the checkbox from the UI,
* and we rarely if ever see the problem of false negatives for firewall detection -
* it's usually false positives.
******************
String forceIntroducers = _context.getProperty(PROP_FORCE_INTRODUCERS);
if ( (forceIntroducers != null) && (Boolean.parseBoolean(forceIntroducers)) ) {
if (_log.shouldLog(Log.INFO))
_log.info("Force introducers specified");
return true;
}
*******************/
Status status = getReachabilityStatus();
switch (status) {
case REJECT_UNSOLICITED:
case DIFFERENT:
case IPV4_FIREWALLED_IPV6_OK:
case IPV4_FIREWALLED_IPV6_UNKNOWN:
if (_log.shouldLog(Log.DEBUG))
_log.debug("Require introducers, because our status is " + status);
return true;
default:
if (!allowDirectUDP()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Require introducers, because we do not allow direct UDP connections");
return true;
}
return false;
}
}
/**
* MIGHT we require introducers?
* This is like introducersRequired, but if we aren't sure, this returns true.
* Used only by EstablishmentManager.
*
* @since 0.9.24
*/
boolean introducersMaybeRequired() {
Status status = getReachabilityStatus();
switch (status) {
case REJECT_UNSOLICITED:
case DIFFERENT:
case IPV4_FIREWALLED_IPV6_OK:
case IPV4_FIREWALLED_IPV6_UNKNOWN:
case IPV4_UNKNOWN_IPV6_OK:
case IPV4_UNKNOWN_IPV6_FIREWALLED:
case UNKNOWN:
return true;
default:
return !allowDirectUDP();
}
}
/**
* For EstablishmentManager
* @since 0.9.3
*/
boolean canIntroduce() {
// we don't expect inbound connections when hidden, but it could happen
// Don't offer if we are approaching max connections. While Relay Intros do not
// count as connections, we have to keep the connection to this peer up longer if
// we are offering introductions.
return
(!_context.router().isHidden()) &&
(!introducersRequired()) &&
haveCapacity() &&
(!_context.netDb().floodfillEnabled()) &&
_introManager.introducedCount() < IntroductionManager.MAX_OUTBOUND &&
_introManager.introducedCount() < getMaxConnections() / 4;
}
/** default true */
private boolean allowDirectUDP() {
return _context.getBooleanPropertyDefaultTrue(PROP_ALLOW_DIRECT);
}
String getPacketHandlerStatus() {
PacketHandler handler = _handler;
if (handler != null)
return handler.getHandlerStatus();
else
return "";
}
/** @since IPv6 */
PacketHandler getPacketHandler() {
return _handler;
}
public void failed(OutboundMessageState msg) { failed(msg, true); }
void failed(OutboundMessageState msg, boolean allowPeerFailure) {
if (msg == null) return;
OutNetMessage m = msg.getMessage();
if ( allowPeerFailure && (msg.getPeer() != null) &&
( (msg.getMaxSends() >= OutboundMessageFragments.MAX_VOLLEYS) ||
(msg.isExpired())) ) {
//long recvDelay = _context.clock().now() - msg.getPeer().getLastReceiveTime();
//long sendDelay = _context.clock().now() - msg.getPeer().getLastSendFullyTime();
//if (m != null)
// m.timestamp("message failure - volleys = " + msg.getMaxSends()
// + " lastReceived: " + recvDelay
// + " lastSentFully: " + sendDelay
// + " expired? " + msg.isExpired());
int consecutive = msg.getPeer().incrementConsecutiveFailedSends();
if (_log.shouldLog(Log.INFO))
_log.info("Consecutive failure #" + consecutive
+ " on " + msg.toString()
+ " to " + msg.getPeer());
if ( (_context.clock().now() - msg.getPeer().getLastSendFullyTime() <= 60*1000) || (consecutive < MAX_CONSECUTIVE_FAILED) ) {
// ok, a few conseutive failures, but we /are/ getting through to them
} else {
_context.statManager().addRateData("udp.dropPeerConsecutiveFailures", consecutive, msg.getPeer().getInactivityTime());
sendDestroy(msg.getPeer());
dropPeer(msg.getPeer(), false, "too many failures");
}
//if ( (consecutive > MAX_CONSECUTIVE_FAILED) && (msg.getPeer().getInactivityTime() > DROP_INACTIVITY_TIME))
// dropPeer(msg.getPeer(), false);
//else if (consecutive > 2 * MAX_CONSECUTIVE_FAILED) // they're sending us data, but we cant reply?
// dropPeer(msg.getPeer(), false);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Failed sending " + msg + " to " + msg.getPeer());
}
noteSend(msg, false);
if (m != null)
super.afterSend(m, false);
}
private void noteSend(OutboundMessageState msg, boolean successful) {
// bail before we do all the work
if (!_context.messageHistory().getDoLog())
return;
int pushCount = msg.getPushCount();
int sends = msg.getMaxSends();
boolean expired = msg.isExpired();
OutNetMessage m = msg.getMessage();
PeerState p = msg.getPeer();
StringBuilder buf = new StringBuilder(64);
buf.append(" lifetime: ").append(msg.getLifetime());
buf.append(" sends: ").append(sends);
buf.append(" pushes: ").append(pushCount);
buf.append(" expired? ").append(expired);
buf.append(" unacked: ").append(msg.getUnackedSize());
if ( (p != null) && (!successful) ) {
buf.append(" consec_failed: ").append(p.getConsecutiveFailedSends());
long timeSinceSend = _context.clock().now() - p.getLastSendFullyTime();
buf.append(" lastFullSend: ").append(timeSinceSend);
long timeSinceRecv = _context.clock().now() - p.getLastReceiveTime();
buf.append(" lastRecv: ").append(timeSinceRecv);
buf.append(" xfer: ").append(p.getSendBps()).append("/").append(p.getReceiveBps());
buf.append(" mtu: ").append(p.getMTU());
buf.append(" rto: ").append(p.getRTO());
buf.append(" sent: ").append(p.getMessagesSent()).append("/").append(p.getPacketsTransmitted());
buf.append(" recv: ").append(p.getMessagesReceived()).append("/").append(p.getPacketsReceived());
buf.append(" uptime: ").append(_context.clock().now()-p.getKeyEstablishedTime());
}
if ( (m != null) && (p != null) ) {
_context.messageHistory().sendMessage(m.getMessageType(), msg.getMessageId(), m.getExpiration(),
p.getRemotePeer(), successful, buf.toString());
} else {
_context.messageHistory().sendMessage("establish", msg.getMessageId(), -1,
(p != null ? p.getRemotePeer() : null), successful, buf.toString());
}
}
public void failed(OutNetMessage msg, String reason) {
if (msg == null) return;
if (_log.shouldLog(Log.INFO))
_log.info("Send failed: " + reason + " msg: " + msg, new Exception("failed from"));
if (_context.messageHistory().getDoLog())
_context.messageHistory().sendMessage(msg.getMessageType(), msg.getMessageId(), msg.getExpiration(),
msg.getTarget().getIdentity().calculateHash(), false, reason);
super.afterSend(msg, false);
}
public void succeeded(OutboundMessageState msg) {
if (msg == null) return;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending message succeeded: " + msg);
noteSend(msg, true);
OutNetMessage m = msg.getMessage();
if (m != null)
super.afterSend(m, true);
}
public int countPeers() {
return _peersByIdent.size();
}
public int countActivePeers() {
long old = _context.clock().now() - 5*60*1000;
int active = 0;
for (PeerState peer : _peersByIdent.values()) {
// PeerState initializes times at construction,
// so check message count also
if ((peer.getMessagesReceived() > 0 && peer.getLastReceiveTime() >= old) ||
(peer.getMessagesSent() > 0 && peer.getLastSendTime() >= old)) {
active++;
}
}
return active;
}
public int countActiveSendPeers() {
long old = _context.clock().now() - 60*1000;
int active = 0;
for (PeerState peer : _peersByIdent.values()) {
if (peer.getLastSendFullyTime() >= old)
active++;
}
return active;
}
@Override
public boolean isEstablished(Hash dest) {
return getPeerState(dest) != null;
}
/**
* @since 0.9.3
*/
@Override
public boolean isBacklogged(Hash dest) {
PeerState peer = _peersByIdent.get(dest);
return peer != null && peer.isBacklogged();
}
/**
* Tell the transport that we may disconnect from this peer.
* This is advisory only.
*
* @since 0.9.24
*/
@Override
public void mayDisconnect(final Hash peer) {
final PeerState ps = _peersByIdent.get(peer);
if (ps != null &&
ps.getWeRelayToThemAs() <= 0 &&
(ps.getTheyRelayToUsAs() <= 0 || ps.getIntroducerTime() < _context.clock().now() - 2*60*60*1000) &&
ps.getMessagesReceived() <= 2 && ps.getMessagesSent() <= 2) {
ps.setMayDisconnect();
}
}
public boolean allowConnection() {
return _peersByIdent.size() < getMaxConnections();
}
/**
* Return our peer clock skews on this transport.
* Vector composed of Long, each element representing a peer skew in seconds.
* A positive number means our clock is ahead of theirs.
*/
@Override
public Vector<Long> getClockSkews() {
Vector<Long> skews = new Vector<Long>();
// If our clock is way off, we may not have many (or any) successful connections,
// so try hard in that case to return good data
boolean includeEverybody = _context.router().getUptime() < 10*60*1000 || _peersByIdent.size() < 10;
long now = _context.clock().now();
for (PeerState peer : _peersByIdent.values()) {
if ((!includeEverybody) && now - peer.getLastReceiveTime() > 5*60*1000)
continue; // skip old peers
if (peer.getRTT() > PeerState.INIT_RTT - 250)
continue; // Big RTT makes for a poor calculation
skews.addElement(Long.valueOf(peer.getClockSkew() / 1000));
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("UDP transport returning " + skews.size() + " peer clock skews.");
return skews;
}
/**
* @return a new DHSessionKeyBuilder
* @since 0.9
*/
DHSessionKeyBuilder getDHBuilder() {
return _dhFactory.getBuilder();
}
/**
* @return the factory
* @since 0.9.2
*/
DHSessionKeyBuilder.Factory getDHFactory() {
return _dhFactory;
}
@Override
public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException {
TreeSet<PeerState> peers = new TreeSet<PeerState>(getComparator(sortFlags));
peers.addAll(_peersByIdent.values());
long offsetTotal = 0;
int bpsIn = 0;
int bpsOut = 0;
long uptimeMsTotal = 0;
long cwinTotal = 0;
long rttTotal = 0;
long rtoTotal = 0;
long sendTotal = 0;
long recvTotal = 0;
long resentTotal = 0;
long dupRecvTotal = 0;
int numPeers = 0;
StringBuilder buf = new StringBuilder(512);
buf.append("<h3 id=\"udpcon\">").append(_t("UDP connections")).append(": ").append(peers.size());
buf.append(". ").append(_t("Limit")).append(": ").append(getMaxConnections());
buf.append(". ").append(_t("Timeout")).append(": ").append(DataHelper.formatDuration2(_expireTimeout));
if (_context.getBooleanProperty(PROP_ADVANCED)) {
buf.append(". ").append(_t("Status")).append(": ").append(_t(_reachabilityStatus.toStatusString()));
}
buf.append(".</h3>\n");
buf.append("<table>\n");
buf.append("<tr><th class=\"smallhead\" nowrap><a href=\"#def.peer\">").append(_t("Peer")).append("</a><br>");
if (sortFlags != FLAG_ALPHA)
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by peer hash"), FLAG_ALPHA);
buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.dir\" title=\"")
.append(_t("Direction/Introduction")).append("\">").append(_t("Dir"))
.append("</a></th><th class=\"smallhead\" nowrap><a href=\"#def.ipv6\">").append(_t("IPv6"))
.append("</a></th><th class=\"smallhead\" nowrap><a href=\"#def.idle\">").append(_t("Idle")).append("</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by idle inbound"), FLAG_IDLE_IN);
buf.append(" / ");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by idle outbound"), FLAG_IDLE_OUT);
buf.append("</th>");
buf.append("<th class=\"smallhead\" nowrap><a href=\"#def.rate\">").append(_t("In/Out")).append("</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by inbound rate"), FLAG_RATE_IN);
buf.append(" / ");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by outbound rate"), FLAG_RATE_OUT);
buf.append("</th>\n");
buf.append("<th class=\"smallhead\" nowrap><a href=\"#def.up\">").append(_t("Up")).append("</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by connection uptime"), FLAG_UPTIME);
buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.skew\">").append(_t("Skew")).append("</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by clock skew"), FLAG_SKEW);
buf.append("</th>\n");
buf.append("<th class=\"smallhead\" nowrap><a href=\"#def.cwnd\">CWND</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by congestion window"), FLAG_CWND);
buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.ssthresh\">SST</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by slow start threshold"), FLAG_SSTHRESH);
buf.append("</th>\n");
buf.append("<th class=\"smallhead\" nowrap><a href=\"#def.rtt\">RTT</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by round trip time"), FLAG_RTT);
//buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.dev\">").append(_t("Dev")).append("</a><br>");
//appendSortLinks(buf, urlBase, sortFlags, _t("Sort by round trip time deviation"), FLAG_DEV);
buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.rto\">RTO</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by retransmission timeout"), FLAG_RTO);
buf.append("</th>\n");
buf.append("<th class=\"smallhead\" nowrap><a href=\"#def.mtu\">MTU</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by outbound maximum transmit unit"), FLAG_MTU);
buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.send\">").append(_t("TX")).append("</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by packets sent"), FLAG_SEND);
buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.recv\">").append(_t("RX")).append("</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by packets received"), FLAG_RECV);
buf.append("</th>\n");
buf.append("<th class=\"smallhead\" nowrap><a href=\"#def.resent\">").append(_t("Dup TX")).append("</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by packets retransmitted"), FLAG_RESEND);
buf.append("</th><th class=\"smallhead\" nowrap><a href=\"#def.dupRecv\">").append(_t("Dup RX")).append("</a><br>");
appendSortLinks(buf, urlBase, sortFlags, _t("Sort by packets received more than once"), FLAG_DUP);
buf.append("</th></tr>\n");
out.write(buf.toString());
buf.setLength(0);
long now = _context.clock().now();
for (PeerState peer : peers) {
if (now-peer.getLastReceiveTime() > 60*60*1000)
continue; // don't include old peers
buf.append("<tr><td class=\"cells\" align=\"left\" nowrap>");
buf.append(_context.commSystem().renderPeerHTML(peer.getRemotePeer()));
//byte ip[] = peer.getRemoteIP();
//if (ip != null)
// buf.append(' ').append(_context.blocklist().toStr(ip));
buf.append("</td><td class=\"cells\" nowrap align=\"left\">");
if (peer.isInbound())
buf.append("<img src=\"/themes/console/images/inbound.png\" alt=\"Inbound\" title=\"").append(_t("Inbound")).append("\">");
else
buf.append("<img src=\"/themes/console/images/outbound.png\" alt=\"Outbound\" title=\"").append(_t("Outbound")).append("\">");
if (peer.getWeRelayToThemAs() > 0)
buf.append(" <img src=\"/themes/console/images/outbound.png\" height=\"8\" width=\"12\" alt=\"^\" title=\"").append(_t("We offered to introduce them")).append("\">");
if (peer.getTheyRelayToUsAs() > 0)
buf.append(" <img src=\"/themes/console/images/inbound.png\" height=\"8\" width=\"12\" alt=\"V\" title=\"").append(_t("They offered to introduce us")).append("\">");
boolean appended = false;
if (_activeThrottle.isChoked(peer.getRemotePeer())) {
buf.append("<br><i>").append(_t("Choked")).append("</i>");
appended = true;
}
int cfs = peer.getConsecutiveFailedSends();
if (cfs > 0) {
if (!appended) buf.append("<br>");
buf.append(" <i>");
if (cfs == 1)
buf.append(_t("1 fail"));
else
buf.append(_t("{0} fails", cfs));
buf.append("</i>");
appended = true;
}
if (_context.banlist().isBanlisted(peer.getRemotePeer(), STYLE)) {
if (!appended) buf.append("<br>");
buf.append(" <i>").append(_t("Banned")).append("</i>");
appended = true;
}
//byte[] ip = getIP(peer.getRemotePeer());
//if (ip != null)
// buf.append(' ').append(_context.blocklist().toStr(ip));
buf.append("</td>");
buf.append("<td class=\"cells\" align=\"center\">");
if (peer.isIPv6())
buf.append("✓");
else
buf.append(" ");
buf.append("</td>");
long idleIn = Math.max(now-peer.getLastReceiveTime(), 0);
long idleOut = Math.max(now-peer.getLastSendTime(), 0);
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(DataHelper.formatDuration2(idleIn));
buf.append(THINSP);
buf.append(DataHelper.formatDuration2(idleOut));
buf.append("</td>");
int recvBps = (idleIn > 15*1000 ? 0 : peer.getReceiveBps());
int sendBps = (idleOut > 15*1000 ? 0 : peer.getSendBps());
buf.append("<td class=\"cells\" align=\"right\" nowrap>");
buf.append(formatKBps(recvBps));
buf.append(THINSP);
buf.append(formatKBps(sendBps));
//buf.append(" K/s");
//buf.append(formatKBps(peer.getReceiveACKBps()));
//buf.append("K/s/");
//buf.append(formatKBps(peer.getSendACKBps()));
//buf.append("K/s ");
buf.append("</td>");
long uptime = now - peer.getKeyEstablishedTime();
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(DataHelper.formatDuration2(uptime));
buf.append("</td>");
buf.append("<td class=\"cells\" align=\"right\">");
long skew = peer.getClockSkew();
buf.append(DataHelper.formatDuration2(skew));
buf.append("</td>");
offsetTotal = offsetTotal + skew;
long sendWindow = peer.getSendWindowBytes();
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(sendWindow/1024);
buf.append("K");
buf.append(THINSP).append(peer.getConcurrentSends());
buf.append(THINSP).append(peer.getConcurrentSendWindow());
buf.append(THINSP).append(peer.getConsecutiveSendRejections());
if (peer.isBacklogged())
buf.append(' ').append(_t("backlogged"));
buf.append("</td>");
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(peer.getSlowStartThreshold()/1024);
buf.append("K</td>");
int rtt = peer.getRTT();
int rto = peer.getRTO();
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(DataHelper.formatDuration2(rtt));
buf.append("</td>");
//buf.append("<td class=\"cells\" align=\"right\">");
//buf.append(DataHelper.formatDuration2(peer.getRTTDeviation()));
//buf.append("</td>");
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(DataHelper.formatDuration2(rto));
buf.append("</td>");
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(peer.getMTU()).append(THINSP).append(peer.getReceiveMTU());
//.append('/');
//buf.append(peer.getMTUIncreases()).append('/');
//buf.append(peer.getMTUDecreases());
buf.append("</td>");
long sent = peer.getMessagesSent();
long recv = peer.getMessagesReceived();
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(sent);
buf.append("</td>");
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(recv);
buf.append("</td>");
//double sent = (double)peer.getPacketsPeriodTransmitted();
//double sendLostPct = 0;
//if (sent > 0)
// sendLostPct = (double)peer.getPacketsRetransmitted()/(sent);
long resent = peer.getPacketsRetransmitted();
long dupRecv = peer.getPacketsReceivedDuplicate();
buf.append("<td class=\"cells\" align=\"right\">");
//buf.append(formatPct(sendLostPct));
buf.append(resent); // + "/" + peer.getPacketsPeriodRetransmitted() + "/" + sent);
//buf.append(peer.getPacketRetransmissionRate());
buf.append("</td>");
buf.append("<td class=\"cells\" align=\"right\">");
buf.append(dupRecv); //formatPct(recvDupPct));
buf.append("</td>");
buf.append("</tr>\n");
out.write(buf.toString());
buf.setLength(0);
bpsIn += recvBps;
bpsOut += sendBps;
uptimeMsTotal += uptime;
cwinTotal += sendWindow;
rttTotal += rtt;
rtoTotal += rto;
sendTotal += sent;
recvTotal += recv;
resentTotal += resent;
dupRecvTotal += dupRecv;
numPeers++;
}
if (numPeers > 0) {
// buf.append("<tr><td colspan=\"16\"><hr></td></tr>\n");
buf.append("<tr class=\"tablefooter\"><td colspan=\"4\" align=\"left\"><b>")
.append(ngettext("{0} peer", "{0} peers", peers.size()))
.append("</b></td>" +
"<td align=\"center\" nowrap><b>");
buf.append(formatKBps(bpsIn)).append(THINSP).append(formatKBps(bpsOut));
long x = uptimeMsTotal/numPeers;
buf.append("</b></td>" +
"<td align=\"center\"><b>").append(DataHelper.formatDuration2(x));
x = offsetTotal/numPeers;
buf.append("</b></td><td align=\"center\"><b>").append(DataHelper.formatDuration2(x)).append("</b></td>\n" +
"<td align=\"center\"><b>");
buf.append(cwinTotal/(numPeers*1024) + "K");
buf.append("</b></td><td> </td>\n" +
"<td align=\"center\"><b>");
buf.append(DataHelper.formatDuration2(rttTotal/numPeers));
//buf.append("</b></td><td> </td><td align=\"center\"><b>");
buf.append("</b></td><td align=\"center\"><b>");
buf.append(DataHelper.formatDuration2(rtoTotal/numPeers));
buf.append("</b></td><td align=\"center\"><b>").append(_mtu).append("</b></td><td align=\"center\"><b>");
buf.append(sendTotal).append("</b></td><td align=\"center\"><b>").append(recvTotal).append("</b></td>\n" +
"<td align=\"center\"><b>").append(resentTotal);
buf.append("</b></td><td align=\"center\"><b>").append(dupRecvTotal).append("</b></td></tr>\n");
if (sortFlags == FLAG_DEBUG) {
buf.append("<tr><td colspan=\"16\">");
buf.append("peersByIdent: ").append(_peersByIdent.size());
buf.append(" peersByRemoteHost: ").append(_peersByRemoteHost.size());
int dir = 0;
int indir = 0;
for (RemoteHostId rhi : _peersByRemoteHost.keySet()) {
if (rhi.getIP() != null)
dir++;
else
indir++;
}
buf.append(" pBRH direct: ").append(dir).append(" indirect: ").append(indir);
buf.append("</td></tr>");
}
} // numPeers > 0
buf.append("</table>\n");
/*****
long bytesTransmitted = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes();
// NPE here early
double averagePacketSize = _context.statManager().getRate("udp.sendPacketSize").getLifetimeAverageValue();
// lifetime value, not just the retransmitted packets of current connections
resentTotal = (long)_context.statManager().getRate("udp.packetsRetransmitted").getLifetimeEventCount();
double nondupSent = ((double)bytesTransmitted - ((double)resentTotal)*averagePacketSize);
double bwResent = (nondupSent <= 0 ? 0d : ((((double)resentTotal)*averagePacketSize) / nondupSent));
buf.append("<h3>Percentage of bytes retransmitted (lifetime): ").append(formatPct(bwResent));
buf.append("</h3><i>(Includes retransmission required by packet loss)</i>\n");
*****/
out.write(buf.toString());
buf.setLength(0);
}
private static final DecimalFormat _fmt = new DecimalFormat("#,##0.00");
private static final String formatKBps(int bps) {
synchronized (_fmt) {
return _fmt.format((float)bps/1024);
}
}
private static final DecimalFormat _pctFmt = new DecimalFormat("#0.0%");
/*
* Cache the bid to reduce object churn
*/
private class SharedBid extends TransportBid {
public SharedBid(int ms) { super(); setLatencyMs(ms); }
@Override
public Transport getTransport() { return UDPTransport.this; }
@Override
public String toString() { return "UDP bid @ " + getLatencyMs(); }
}
private class ExpirePeerEvent extends SimpleTimer2.TimedEvent {
// TODO why have separate Set, just use _peersByIdent.values()
private final Set<PeerState> _expirePeers;
private final List<PeerState> _expireBuffer;
private volatile boolean _alive;
private int _runCount;
private boolean _lastLoopShort;
// we've seen firewalls change ports after 40 seconds
private static final long PING_FIREWALL_TIME = 30*1000;
private static final long PING_FIREWALL_CUTOFF = PING_FIREWALL_TIME / 2;
// ping 1/4 of the peers every loop
private static final int SLICES = 4;
private static final long SHORT_LOOP_TIME = PING_FIREWALL_CUTOFF / (SLICES + 1);
private static final long LONG_LOOP_TIME = 25*1000;
private static final long EXPIRE_INCREMENT = 15*1000;
private static final long EXPIRE_DECREMENT = 45*1000;
private static final long MAY_DISCON_TIMEOUT = 10*1000;
public ExpirePeerEvent() {
super(_context.simpleTimer2());
_expirePeers = new ConcurrentHashSet<PeerState>(128);
_expireBuffer = new ArrayList<PeerState>();
}
public void timeReached() {
// Increase allowed idle time if we are well under allowed connections, otherwise decrease
boolean haveCap = haveCapacity(33);
if (haveCap) {
long inc;
// don't adjust too quickly if we are looping fast
if (_lastLoopShort)
inc = EXPIRE_INCREMENT * SHORT_LOOP_TIME / LONG_LOOP_TIME;
else
inc = EXPIRE_INCREMENT;
_expireTimeout = Math.min(_expireTimeout + inc, EXPIRE_TIMEOUT);
} else {
long dec;
if (_lastLoopShort)
dec = EXPIRE_DECREMENT * SHORT_LOOP_TIME / LONG_LOOP_TIME;
else
dec = EXPIRE_DECREMENT;
_expireTimeout = Math.max(_expireTimeout - dec, MIN_EXPIRE_TIMEOUT);
}
long now = _context.clock().now();
long shortInactivityCutoff = now - _expireTimeout;
long longInactivityCutoff = now - EXPIRE_TIMEOUT;
final long mayDisconCutoff = now - MAY_DISCON_TIMEOUT;
long pingCutoff = now - (2 * 60*60*1000);
long pingFirewallCutoff = now - PING_FIREWALL_CUTOFF;
boolean shouldPingFirewall = _reachabilityStatus != Status.OK;
int currentListenPort = getListenPort(false);
boolean pingOneOnly = shouldPingFirewall && getExternalPort(false) == currentListenPort;
boolean shortLoop = shouldPingFirewall || !haveCap || _context.netDb().floodfillEnabled();
_lastLoopShort = shortLoop;
_expireBuffer.clear();
_runCount++;
for (Iterator<PeerState> iter = _expirePeers.iterator(); iter.hasNext(); ) {
PeerState peer = iter.next();
long inactivityCutoff;
// if we offered to introduce them, or we used them as introducer in last 2 hours
if (peer.getWeRelayToThemAs() > 0 || peer.getIntroducerTime() > pingCutoff) {
inactivityCutoff = longInactivityCutoff;
} else if ((!haveCap || !peer.isInbound()) &&
peer.getMayDisconnect() &&
peer.getMessagesReceived() <= 2 && peer.getMessagesSent() <= 2) {
if (_log.shouldInfo())
_log.info("Possible early disconnect for: " + peer);
inactivityCutoff = mayDisconCutoff;
} else {
inactivityCutoff = shortInactivityCutoff;
}
if ( (peer.getLastReceiveTime() < inactivityCutoff) && (peer.getLastSendTime() < inactivityCutoff) ) {
_expireBuffer.add(peer);
iter.remove();
} else if (shouldPingFirewall &&
((_runCount ^ peer.hashCode()) & (SLICES - 1)) == 0 &&
peer.getLastSendOrPingTime() < pingFirewallCutoff &&
peer.getLastReceiveTime() < pingFirewallCutoff) {
// ping if firewall is mapping the port to keep port the same...
// if the port changes we are screwed
if (_log.shouldLog(Log.DEBUG))
_log.debug("Pinging for firewall: " + peer);
// don't update or idle time won't be right and peer won't get dropped
// TODO if both sides are firewalled should only one ping
// or else session will stay open forever?
//peer.setLastSendTime(now);
send(_destroyBuilder.buildPing(peer));
peer.setLastPingTime(now);
// If external port is different, it may be changing the port for every
// session, so ping all of them. Otherwise only one.
if (pingOneOnly)
shouldPingFirewall = false;
}
}
if (!_expireBuffer.isEmpty()) {
if (_log.shouldLog(Log.INFO))
_log.info("Expiring " + _expireBuffer.size() + " peers");
for (PeerState peer : _expireBuffer) {
sendDestroy(peer);
dropPeer(peer, false, "idle too long");
// TODO sleep to limit burst like in destroyAll() ??
// but we are on the timer thread...
// hopefully this isn't too many at once
// ... or only send a max of x, then requeue
}
_expireBuffer.clear();
}
if (_alive)
schedule(shortLoop ? SHORT_LOOP_TIME : LONG_LOOP_TIME);
}
public void add(PeerState peer) {
_expirePeers.add(peer);
}
public void remove(PeerState peer) {
_expirePeers.remove(peer);
}
public void setIsAlive(boolean isAlive) {
_alive = isAlive;
if (isAlive) {
reschedule(LONG_LOOP_TIME);
} else {
cancel();
_expirePeers.clear();
}
}
}
/**
* IPv4 only
*/
private void setReachabilityStatus(Status status) {
setReachabilityStatus(status, false);
}
/**
* @since 0.9.27
* @param isIPv6 Is the change an IPv6 change?
*/
void setReachabilityStatus(Status status, boolean isIPv6) {
synchronized (_rebuildLock) {
locked_setReachabilityStatus(status, isIPv6);
}
}
/**
* @param isIPv6 Is the change an IPv6 change?
*/
private void locked_setReachabilityStatus(Status newStatus, boolean isIPv6) {
Status old = _reachabilityStatus;
// merge new status into old
Status status = Status.merge(old, newStatus);
_testEvent.setLastTested(isIPv6);
// now modify if we are IPv6 only
TransportUtil.IPv6Config config = getIPv6Config();
if (config == IPV6_ONLY) {
if (status == Status.IPV4_UNKNOWN_IPV6_OK)
status = Status.IPV4_DISABLED_IPV6_OK;
else if (status == Status.IPV4_UNKNOWN_IPV6_FIREWALLED)
status = Status.IPV4_DISABLED_IPV6_FIREWALLED;
else if (status == Status.UNKNOWN)
status = Status.IPV4_DISABLED_IPV6_UNKNOWN;
}
if (status != Status.UNKNOWN) {
// now modify if we have no IPv6 address
if (_currentOurV6Address == null && !_haveIPv6Address) {
if (status == Status.IPV4_OK_IPV6_UNKNOWN)
status = Status.OK;
else if (status == Status.IPV4_FIREWALLED_IPV6_UNKNOWN)
status = Status.REJECT_UNSOLICITED;
else if (status == Status.IPV4_SNAT_IPV6_UNKNOWN)
status = Status.DIFFERENT;
// prevent firewalled -> OK -> firewalled+OK
else if (status == Status.IPV4_FIREWALLED_IPV6_OK)
status = Status.REJECT_UNSOLICITED;
else if (status == Status.IPV4_SNAT_IPV6_OK)
status = Status.DIFFERENT;
}
if (status != old) {
// for the following transitions ONLY, require two in a row
// to prevent thrashing
if ((old == Status.OK && (status == Status.DIFFERENT ||
status == Status.REJECT_UNSOLICITED ||
status == Status.IPV4_FIREWALLED_IPV6_OK ||
status == Status.IPV4_SNAT_IPV6_OK ||
status == Status.IPV4_OK_IPV6_FIREWALLED)) ||
(status == Status.OK && (old == Status.DIFFERENT ||
old == Status.REJECT_UNSOLICITED ||
old == Status.IPV4_FIREWALLED_IPV6_OK ||
old == Status.IPV4_SNAT_IPV6_OK ||
old == Status.IPV4_OK_IPV6_FIREWALLED))) {
if (status != _reachabilityStatusPending) {
if (_log.shouldLog(Log.WARN))
_log.warn("Old status: " + old + " status pending confirmation: " + status +
" Caused by update: " + newStatus);
_reachabilityStatusPending = status;
_testEvent.forceRunSoon(isIPv6);
return;
}
}
_reachabilityStatusUnchanged = 0;
long now = _context.clock().now();
_reachabilityStatusLastUpdated = now;
_reachabilityStatus = status;
} else {
_reachabilityStatusUnchanged++;
}
_reachabilityStatusPending = status;
}
if (status != old) {
if (_log.shouldLog(Log.WARN))
_log.warn("Old status: " + old + " New status: " + status +
" Caused by update: " + newStatus +
" from: ", new Exception("traceback"));
if (old != Status.UNKNOWN)
_context.router().eventLog().addEvent(EventLog.REACHABILITY,
"from " + _t(old.toStatusString()) + " to " + _t(status.toStatusString()));
// Always rebuild when the status changes, even if our address hasn't changed,
// as rebuildExternalAddress() calls replaceAddress() which calls CSFI.notifyReplaceAddress()
// which will start up NTCP inbound when we transition to OK.
// if (needsRebuild())
rebuildExternalAddress();
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Status unchanged: " + _reachabilityStatus +
" after update: " + newStatus +
" (unchanged " + _reachabilityStatusUnchanged + " consecutive times), last updated " +
DataHelper.formatDuration(_context.clock().now() - _reachabilityStatusLastUpdated) + " ago");
}
}
private static final String PROP_REACHABILITY_STATUS_OVERRIDE = "i2np.udp.status";
/**
* Previously returned short, now enum as of 0.9.20
*/
public Status getReachabilityStatus() {
String override = _context.getProperty(PROP_REACHABILITY_STATUS_OVERRIDE);
if (override != null) {
if ("ok".equals(override))
return Status.OK;
else if ("err-reject".equals(override))
return Status.REJECT_UNSOLICITED;
else if ("err-different".equals(override))
return Status.DIFFERENT;
}
return _reachabilityStatus;
}
/**
* @deprecated unused
*/
@Override
@Deprecated
public void recheckReachability() {
// FIXME locking if we do this again
//_testEvent.runTest();
}
/**
* Pick a Bob (if we are Alice) or a Charlie (if we are Bob).
*
* For Bob (as called from PeerTestEvent below), returns an established IPv4/v6 peer.
* While the protocol allows Alice to select an unestablished Bob, we don't support that.
*
* For Charlie (as called from PeerTestManager), returns an established IPv4 or IPv6 peer.
* (doesn't matter how Bob and Charlie communicate)
*
* Any returned peer must advertise an IPv4 address to prove it is IPv4-capable.
* Ditto for v6.
*
* @param peerRole The role of the peer we are looking for, BOB or CHARLIE only (NOT our role)
* @param isIPv6 true to get a v6-capable peer back
* @param dontInclude may be null
* @return IPv4 peer or null
*/
PeerState pickTestPeer(PeerTestState.Role peerRole, boolean isIPv6, RemoteHostId dontInclude) {
if (peerRole == ALICE)
throw new IllegalArgumentException();
List<PeerState> peers = new ArrayList<PeerState>(_peersByIdent.values());
for (Iterator<PeerState> iter = new RandomIterator<PeerState>(peers); iter.hasNext(); ) {
PeerState peer = iter.next();
if ( (dontInclude != null) && (dontInclude.equals(peer.getRemoteHostId())) )
continue;
// enforce IPv4/v6 connection if we are ALICE looking for a BOB
byte[] ip = peer.getRemoteIP();
if (peerRole == BOB) {
if (isIPv6) {
if (ip.length != 16)
continue;
} else {
if (ip.length != 4)
continue;
}
}
// enforce IPv4/v6 advertised for all
RouterInfo peerInfo = _context.netDb().lookupRouterInfoLocally(peer.getRemotePeer());
if (peerInfo == null)
continue;
if (isIPv6) {
String v = peerInfo.getVersion();
if (VersionComparator.comp(v, MIN_V6_PEER_TEST_VERSION) < 0)
continue;
}
ip = null;
List<RouterAddress> addrs = getTargetAddresses(peerInfo);
for (RouterAddress addr : addrs) {
byte[] rip = addr.getIP();
if (rip != null) {
if (isIPv6) {
if (rip.length != 16)
continue;
} else {
if (rip.length != 4)
continue;
}
// as of 0.9.27, we trust the 'B' cap for IPv6
String caps = addr.getOption(UDPAddress.PROP_CAPACITY);
if (caps != null && caps.contains(CAP_TESTING)) {
ip = rip;
break;
}
}
}
if (ip == null)
continue;
if (isTooClose(ip))
continue;
return peer;
}
return null;
}
/**
* Periodically ping the introducers, split out since we need to
* do it faster than we rebuild our address.
* @since 0.8.11
*/
private class PingIntroducers implements SimpleTimer.TimedEvent {
public void timeReached() {
if (introducersRequired())
_introManager.pingIntroducers();
}
}
/*******
private static final String BADIPS[] = new String[] { "192.168.0.1", "127.0.0.1", "10.3.4.5", "172.16.3.4", "224.5.6.7" };
private static final String GOODIPS[] = new String[] { "192.167.0.1", "126.0.0.1", "11.3.4.5", "172.15.3.4", "223.5.6.7" };
public static void main(String args[]) {
for (int i = 0; i < BADIPS.length; i++) {
try {
InetAddress addr = InetAddress.getByName(BADIPS[i]);
boolean routable = isPubliclyRoutable(addr.getAddress());
System.out.println("Routable: " + routable + " (" + BADIPS[i] + ")");
} catch (Exception e) { e.printStackTrace(); }
}
for (int i = 0; i < GOODIPS.length; i++) {
try {
InetAddress addr = InetAddress.getByName(GOODIPS[i]);
boolean routable = isPubliclyRoutable(addr.getAddress());
System.out.println("Routable: " + routable + " (" + GOODIPS[i] + ")");
} catch (Exception e) { e.printStackTrace(); }
}
}
*******/
}