2005-08-10 jrandom

* Deployed the peer testing implementation to be run every few minutes on
      each router, as well as any time the user requests a test manually.  The
      tests do not reconfigure the ports at the moment, merely determine under
      what conditions the local router is reachable.  The status shown in the
      top left will be "ERR-SymmetricNAT" if the user's IP and port show up
      differently for different peers, "ERR-Reject" if the router cannot
      receive unsolicited packets or the peer helping test could not find a
      collaborator, "Unknown" if the test has not been run or the test
      participants were unreachable, or "OK" if the router can receive
      unsolicited connections and those connections use the same IP and port.
This commit is contained in:
jrandom
2005-08-10 23:55:40 +00:00
committed by zzz
parent 2f53b9ff68
commit 77b995f5ed
18 changed files with 321 additions and 64 deletions

View File

@ -27,6 +27,7 @@ public class ConfigNetHandler extends FormHandler {
private boolean _guessRequested;
private boolean _reseedRequested;
private boolean _saveRequested;
private boolean _recheckReachabilityRequested;
private boolean _timeSyncEnabled;
private String _tcpPort;
private String _udpPort;
@ -44,6 +45,8 @@ public class ConfigNetHandler extends FormHandler {
reseed();
} else if (_saveRequested) {
saveChanges();
} else if (_recheckReachabilityRequested) {
recheckReachability();
} else {
// noop
}
@ -53,6 +56,7 @@ public class ConfigNetHandler extends FormHandler {
public void setReseed(String moo) { _reseedRequested = true; }
public void setSave(String moo) { _saveRequested = true; }
public void setEnabletimesync(String moo) { _timeSyncEnabled = true; }
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
public void setHostname(String hostname) {
_hostname = (hostname != null ? hostname.trim() : null);
@ -195,6 +199,11 @@ public class ConfigNetHandler extends FormHandler {
fos.close();
}
private void recheckReachability() {
_context.commSystem().recheckReachability();
addFormNotice("Rechecking router reachability...");
}
/**
* The user made changes to the network config and wants to save them, so
* lets go ahead and do so.

View File

@ -12,6 +12,7 @@ import net.i2p.data.Destination;
import net.i2p.data.LeaseSet;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion;
@ -97,6 +98,23 @@ public class SummaryHelper {
return (_context.netDb().getKnownRouters() < 10);
}
public int getAllPeers() { return _context.netDb().getKnownRouters(); }
public String getReachability() {
int status = _context.commSystem().getReachabilityStatus();
switch (status) {
case CommSystemFacade.STATUS_OK:
return "OK";
case CommSystemFacade.STATUS_DIFFERENT:
return "ERR-SymmetricNAT";
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
return "ERR-Reject";
case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
default:
return "Unknown";
}
}
/**
* Retrieve amount of used memory.
*

View File

@ -35,6 +35,8 @@ this port from arbitrary peers (this requirement will be removed in i2p 0.6.1, b
TCP port: <input name="tcpPort" type="text" size="5" value="<jsp:getProperty name="nethelper" property="tcpPort" />" /> <br />
<b>You must poke a hole in your firewall or NAT (if applicable) so that you can receive inbound TCP
connections on it (this requirement will be removed in i2p 0.6.1, but is necessary now)</b>
<br />
<input type="submit" name="recheckReachability" value="Check network reachability..." />
<hr />
<b>Bandwidth limiter</b><br />

View File

@ -14,7 +14,8 @@
<b>Version:</b> <jsp:getProperty name="helper" property="version" /><br />
<b>Uptime:</b> <jsp:getProperty name="helper" property="uptime" /><br />
<b>Now:</b> <jsp:getProperty name="helper" property="time" /><br />
<b>Memory:</b> <jsp:getProperty name="helper" property="memory" /><br /><%
<b>Memory:</b> <jsp:getProperty name="helper" property="memory" /><br />
<b>Status:</b> <a href="config.jsp"><jsp:getProperty name="helper" property="reachability" /></a><br /><%
if (helper.updateAvailable()) {
if ("true".equals(System.getProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"))) {
out.print(update.getStatus());
@ -39,7 +40,8 @@
<b>High capacity:</b> <jsp:getProperty name="helper" property="highCapacityPeers" /><br />
<b>Well integrated:</b> <jsp:getProperty name="helper" property="wellIntegratedPeers" /><br />
<b>Failing:</b> <jsp:getProperty name="helper" property="failingPeers" /><br />
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br /><%
<b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br />
<b>Known:</b> <jsp:getProperty name="helper" property="allPeers" /><br /><%
if (helper.getActivePeers() <= 0) {
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
}

View File

@ -1,4 +1,16 @@
$Id: history.txt,v 1.223 2005/08/07 14:31:58 jrandom Exp $
$Id: history.txt,v 1.224 2005/08/08 15:35:51 jrandom Exp $
2005-08-10 jrandom
* Deployed the peer testing implementation to be run every few minutes on
each router, as well as any time the user requests a test manually. The
tests do not reconfigure the ports at the moment, merely determine under
what conditions the local router is reachable. The status shown in the
top left will be "ERR-SymmetricNAT" if the user's IP and port show up
differently for different peers, "ERR-Reject" if the router cannot
receive unsolicited packets or the peer helping test could not find a
collaborator, "Unknown" if the test has not been run or the test
participants were unreachable, or "OK" if the router can receive
unsolicited connections and those connections use the same IP and port.
* 2005-08-08 0.6.0.2 released

View File

@ -30,6 +30,34 @@ public abstract class CommSystemFacade implements Service {
public int countActivePeers() { return 0; }
public List getMostRecentErrorMessages() { return Collections.EMPTY_LIST; }
/**
* Determine under what conditions we are remotely reachable.
*
*/
public short getReachabilityStatus() { return STATUS_OK; }
public void recheckReachability() {}
/**
* We are able to receive unsolicited connections
*/
public static final short STATUS_OK = 0;
/**
* We are behind a symmetric NAT which will make our 'from' address look
* differently when we talk to multiple people
*
*/
public static final short STATUS_DIFFERENT = 1;
/**
* We are able to talk to peers that we initiate communication with, but
* cannot receive unsolicited connections
*/
public static final short STATUS_REJECT_UNSOLICITED = 2;
/**
* Our reachability is unknown
*/
public static final short STATUS_UNKNOWN = 3;
}
class DummyCommSystemFacade extends CommSystemFacade {

View File

@ -15,9 +15,9 @@ import net.i2p.CoreVersion;
*
*/
public class RouterVersion {
public final static String ID = "$Revision: 1.212 $ $Date: 2005/08/07 14:31:58 $";
public final static String ID = "$Revision: 1.213 $ $Date: 2005/08/08 15:35:50 $";
public final static String VERSION = "0.6.0.2";
public final static long BUILD = 0;
public final static long BUILD = 1;
public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION);
System.out.println("Router ID: " + RouterVersion.ID);

View File

@ -57,7 +57,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
private boolean _initialized;
/** Clock independent time of when we started up */
private long _started;
private int _knownRouters;
private StartExplorersJob _exploreJob;
private HarvesterJob _harvestJob;
/** when was the last time an exploration found something new? */
@ -128,7 +127,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
_peerSelector = new PeerSelector(_context);
_publishingLeaseSets = new HashMap(8);
_lastExploreNew = 0;
_knownRouters = 0;
_activeRequests = new HashMap(8);
_enforceNetId = DEFAULT_ENFORCE_NETID;
}
@ -359,7 +357,21 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
return rv;
}
public int getKnownRouters() { return _knownRouters; }
public int getKnownRouters() {
CountRouters count = new CountRouters();
_kb.getAll(count);
return count.size();
}
private class CountRouters implements SelectionCollector {
private int _count;
public int size() { return _count; }
public void add(Hash entry) {
Object o = _ds.get(entry);
if (o instanceof RouterInfo)
_count++;
}
}
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
if (!_initialized) return;
@ -650,7 +662,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
+ routerInfo.getOptions().size() + " options on "
+ new Date(routerInfo.getPublished()));
_knownRouters++;
_ds.put(key, routerInfo);
synchronized (_lastSent) {
if (!_lastSent.containsKey(key))
@ -712,8 +723,6 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
synchronized (_passiveSendKeys) {
_passiveSendKeys.remove(dbEntry);
}
if (isRouterInfo)
_knownRouters--;
}
public void unpublish(LeaseSet localLeaseSet) {

View File

@ -74,7 +74,10 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
public List getMostRecentErrorMessages() {
return _manager.getMostRecentErrorMessages();
}
public short getReachabilityStatus() { return _manager.getReachabilityStatus(); }
public void recheckReachability() { _manager.recheckReachability(); }
public void renderStatusHTML(Writer out) throws IOException {
_manager.renderStatusHTML(out);
}

View File

@ -41,4 +41,6 @@ public interface Transport {
public List getMostRecentErrorMessages();
public void renderStatusHTML(Writer out) throws IOException;
public short getReachabilityStatus();
public void recheckReachability();
}

View File

@ -22,6 +22,7 @@ import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterIdentity;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.Job;
import net.i2p.router.MessageSelector;
import net.i2p.router.OutNetMessage;
@ -351,4 +352,7 @@ public abstract class TransportImpl implements Transport {
public void renderStatusHTML(Writer out) throws IOException {}
public RouterContext getContext() { return _context; }
public short getReachabilityStatus() { return CommSystemFacade.STATUS_UNKNOWN; }
public void recheckReachability() {}
}

View File

@ -10,6 +10,7 @@ package net.i2p.router.transport;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
@ -22,6 +23,7 @@ import net.i2p.data.RouterAddress;
import net.i2p.data.RouterIdentity;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.OutNetMessage;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.tcp.TCPTransport;
import net.i2p.router.transport.udp.UDPTransport;
@ -115,6 +117,24 @@ public class TransportManager implements TransportEventListener {
return peers;
}
public short getReachabilityStatus() {
if (_transports.size() <= 0) return CommSystemFacade.STATUS_UNKNOWN;
short status[] = new short[_transports.size()];
for (int i = 0; i < _transports.size(); i++) {
status[i] = ((Transport)_transports.get(i)).getReachabilityStatus();
}
// the values for the statuses are increasing for their 'badness'
Arrays.sort(status);
return status[0];
}
public void recheckReachability() {
for (int i = 0; i < _transports.size(); i++)
((Transport)_transports.get(i)).recheckReachability();
}
Map getAddresses() {
Map rv = new HashMap(_transports.size());
for (int i = 0; i < _transports.size(); i++) {

View File

@ -456,6 +456,9 @@ public class PacketBuilder {
* @return ready to send packet, or null if there was a problem
*/
public UDPPacket buildPeerTestFromAlice(InetAddress toIP, int toPort, SessionKey toIntroKey, long nonce, SessionKey aliceIntroKey) {
return buildPeerTestFromAlice(toIP, toPort, toIntroKey, toIntroKey, nonce, aliceIntroKey);
}
public UDPPacket buildPeerTestFromAlice(InetAddress toIP, int toPort, SessionKey toCipherKey, SessionKey toMACKey, long nonce, SessionKey aliceIntroKey) {
UDPPacket packet = UDPPacket.acquire(_context);
byte data[] = packet.getPacket().getData();
Arrays.fill(data, 0, data.length, (byte)0x0);
@ -486,7 +489,7 @@ public class PacketBuilder {
if ( (off % 16) != 0)
off += 16 - (off % 16);
packet.getPacket().setLength(off);
authenticate(packet, toIntroKey, toIntroKey);
authenticate(packet, toCipherKey, toMACKey);
setTo(packet, toIP, toPort);
return packet;
}

View File

@ -398,6 +398,8 @@ public class PacketHandler {
break;
case UDPPacket.PAYLOAD_TYPE_TEST:
_state = 51;
if (_log.shouldLog(Log.INFO))
_log.info("Received test packet: " + reader + " from " + from);
_testManager.receiveTest(from, reader);
break;
default:

View File

@ -4,6 +4,7 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.RouterContext;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterInfo;
@ -32,6 +33,8 @@ class PeerTestManager {
private InetAddress _bobIP;
private int _bobPort;
private SessionKey _bobIntroKey;
private SessionKey _bobCipherKey;
private SessionKey _bobMACKey;
private long _testBeginTime;
private long _lastSendTime;
private long _receiveBobReplyTime;
@ -44,7 +47,7 @@ class PeerTestManager {
/** longest we will keep track of a Charlie nonce for */
private static final int MAX_CHARLIE_LIFETIME = 10*1000;
public PeerTestManager(RouterContext context, UDPTransport transport) {
_context = context;
_transport = transport;
@ -57,11 +60,14 @@ class PeerTestManager {
private static final int RESEND_TIMEOUT = 5*1000;
private static final int MAX_TEST_TIME = 30*1000;
private static final long MAX_NONCE = (1l << 32) - 1l;
public void runTest(InetAddress bobIP, int bobPort, SessionKey bobIntroKey) {
//public void runTest(InetAddress bobIP, int bobPort, SessionKey bobIntroKey) {
public void runTest(InetAddress bobIP, int bobPort, SessionKey bobCipherKey, SessionKey bobMACKey) {
_currentTestNonce = _context.random().nextLong(MAX_NONCE);
_bobIP = bobIP;
_bobPort = bobPort;
_bobIntroKey = bobIntroKey;
//_bobIntroKey = bobIntroKey;
_bobCipherKey = bobCipherKey;
_bobMACKey = bobMACKey;
_charlieIP = null;
_charliePort = -1;
_charlieIntroKey = null;
@ -72,6 +78,9 @@ class PeerTestManager {
_receiveBobReplyPort = -1;
_receiveCharlieReplyPort = -1;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Running test with bob = " + bobIP + ":" + bobPort);
sendTestToBob();
SimpleTimer.getInstance().addEvent(new ContinueTest(), RESEND_TIMEOUT);
@ -104,10 +113,14 @@ class PeerTestManager {
}
private void sendTestToBob() {
_transport.send(_packetBuilder.buildPeerTestFromAlice(_bobIP, _bobPort, _bobIntroKey,
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending test to bob: " + _bobIP + ":" + _bobPort);
_transport.send(_packetBuilder.buildPeerTestFromAlice(_bobIP, _bobPort, _bobCipherKey, _bobMACKey, //_bobIntroKey,
_currentTestNonce, _transport.getIntroKey()));
}
private void sendTestToCharlie() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending test to charlie: " + _charlieIP + ":" + _charliePort);
_transport.send(_packetBuilder.buildPeerTestFromAlice(_charlieIP, _charliePort, _charlieIntroKey,
_currentTestNonce, _transport.getIntroKey()));
}
@ -118,20 +131,28 @@ class PeerTestManager {
* test
*/
private void receiveTestReply(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo) {
if (DataHelper.eq(from.getIP(), _bobIP.getAddress())) {
if ( (DataHelper.eq(from.getIP(), _bobIP.getAddress())) && (from.getPort() == _bobPort) ) {
_receiveBobReplyTime = _context.clock().now();
_receiveBobReplyPort = testInfo.readPort();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive test reply from bob @ " + _bobIP + " on " + _receiveBobReplyPort);
} else {
if (_receiveCharlieReplyTime > 0) {
// this is our second charlie, yay!
_receiveCharlieReplyPort = testInfo.readPort();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive test reply from charlie @ " + _charlieIP + " on " + _receiveCharlieReplyPort);
testComplete();
} else {
// ok, first charlie. send 'em a packet
_receiveCharlieReplyTime = _context.clock().now();
_charlieIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
testInfo.readIntroKey(_charlieIntroKey.getData(), 0);
_charliePort = from.getPort();
try {
_charlieIP = InetAddress.getByAddress(from.getIP());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive test from charlie @ " + _charlieIP + " on " + _charliePort);
sendTestToCharlie();
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.WARN))
@ -141,12 +162,6 @@ class PeerTestManager {
}
}
private static final short STATUS_REACHABLE_OK = 0;
private static final short STATUS_REACHABLE_DIFFERENT = 1;
private static final short STATUS_CHARLIE_DIED = 2;
private static final short STATUS_REJECT_UNSOLICITED = 3;
private static final short STATUS_BOB_SUCKS = 4;
/**
* Evaluate the info we have and act accordingly, since the test has either timed out or
* we have successfully received the second PeerTest from a Charlie.
@ -157,19 +172,19 @@ class PeerTestManager {
if (_receiveCharlieReplyPort > 0) {
// we received a second message from charlie
if (_receiveBobReplyPort == _receiveCharlieReplyPort) {
status = STATUS_REACHABLE_OK;
status = CommSystemFacade.STATUS_OK;
} else {
status = STATUS_REACHABLE_DIFFERENT;
status = CommSystemFacade.STATUS_DIFFERENT;
}
} else if (_receiveCharlieReplyTime > 0) {
// we received only one message from charlie
status = STATUS_CHARLIE_DIED;
status = CommSystemFacade.STATUS_UNKNOWN;
} else if (_receiveBobReplyTime > 0) {
// we received a message from bob but no messages from charlie
status = STATUS_REJECT_UNSOLICITED;
status = CommSystemFacade.STATUS_REJECT_UNSOLICITED;
} else {
// we never received anything from bob - he is either down or ignoring us
status = STATUS_BOB_SUCKS;
status = CommSystemFacade.STATUS_UNKNOWN;
}
honorStatus(status);
@ -179,6 +194,8 @@ class PeerTestManager {
_bobIP = null;
_bobPort = -1;
_bobIntroKey = null;
_bobCipherKey = null;
_bobMACKey = null;
_charlieIP = null;
_charliePort = -1;
_charlieIntroKey = null;
@ -196,15 +213,9 @@ class PeerTestManager {
*
*/
private void honorStatus(short status) {
switch (status) {
case STATUS_REACHABLE_OK:
case STATUS_REACHABLE_DIFFERENT:
case STATUS_CHARLIE_DIED:
case STATUS_REJECT_UNSOLICITED:
case STATUS_BOB_SUCKS:
if (_log.shouldLog(Log.INFO))
_log.info("Test results: status = " + status);
}
if (_log.shouldLog(Log.INFO))
_log.info("Test results: status = " + status);
_transport.setReachabilityStatus(status);
}
/**
@ -230,10 +241,17 @@ class PeerTestManager {
if ( ( (fromIP == null) && (fromPort <= 0) ) || // info is unknown or...
(DataHelper.eq(fromIP, from.getIP()) && (fromPort == from.getPort())) ) { // info matches sender
int knownIndex = -1;
boolean weAreCharlie = false;
synchronized (_receiveAsCharlie) {
weAreCharlie = (Arrays.binarySearch(_receiveAsCharlie, nonce) != -1);
for (int i = 0; (i < _receiveAsCharlie.length) && (knownIndex == -1); i++)
if (_receiveAsCharlie[i] == nonce)
knownIndex = i;
}
weAreCharlie = (knownIndex != -1);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive test with nonce " + nonce + ", known as charlie @ " + knownIndex);
if (weAreCharlie) {
receiveFromAliceAsCharlie(from, testInfo, nonce);
} else {
@ -261,18 +279,38 @@ class PeerTestManager {
return;
}
boolean isNew = true;
int index = -1;
synchronized (_receiveAsCharlie) {
index = _receiveAsCharlieIndex;
_receiveAsCharlie[index] = nonce;
_receiveAsCharlieIndex = (index + 1) % _receiveAsCharlie.length;
for (int i = 0; i < _receiveAsCharlie.length; i++) {
if (_receiveAsCharlie[i] == nonce) {
index = i;
isNew = false;
break;
}
}
if (index == -1) {
// ok, new nonce, store 'er
index = (_receiveAsCharlieIndex + 1) % _receiveAsCharlie.length;
_receiveAsCharlie[index] = nonce;
_receiveAsCharlieIndex = index;
}
}
SimpleTimer.getInstance().addEvent(new RemoveCharlie(nonce, index), MAX_CHARLIE_LIFETIME);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive test as charlie nonce " + nonce + ", stored at index " + index);
if (isNew)
SimpleTimer.getInstance().addEvent(new RemoveCharlie(nonce, index), MAX_CHARLIE_LIFETIME);
try {
InetAddress aliceIP = InetAddress.getByAddress(fromIP);
SessionKey aliceIntroKey = new SessionKey();
SessionKey aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
testInfo.readIntroKey(aliceIntroKey.getData(), 0);
UDPPacket packet = _packetBuilder.buildPeerTestToAlice(aliceIP, fromPort, aliceIntroKey, _transport.getIntroKey(), nonce);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive from bob as charlie and send to alice @ " + aliceIP + " on " + fromPort);
_transport.send(packet);
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.WARN))
@ -288,12 +326,26 @@ class PeerTestManager {
private void receiveFromAliceAsBob(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce) {
// we are Bob, so send Alice her PeerTest, pick a Charlie, and
// send Charlie Alice's info
PeerState charlie = _transport.getPeerState(UDPAddress.CAPACITY_TESTING);
PeerState charlie = null;
for (int i = 0; i < 5; i++) {
charlie = _transport.getPeerState(UDPAddress.CAPACITY_TESTING);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Picking charlie as " + charlie + " for alice of " + from);
if ( (charlie != null) && (!charlie.getRemoteHostId().equals(from)) ) {
break;
}
charlie = null;
}
if (charlie == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to pick a charlie");
return;
}
InetAddress aliceIP = null;
SessionKey aliceIntroKey = null;
try {
aliceIP = InetAddress.getByAddress(from.getIP());
aliceIntroKey = new SessionKey();
aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
testInfo.readIntroKey(aliceIntroKey.getData(), 0);
RouterInfo info = _context.netDb().lookupRouterInfoLocally(charlie.getRemotePeer());
@ -314,6 +366,11 @@ class PeerTestManager {
charlie.getRemotePort(),
charlie.getCurrentCipherKey(),
charlie.getCurrentMACKey());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive from alice as bob, picking charlie @ " + charlie.getRemoteIPAddress() + ":"
+ charlie.getRemotePort() + " for alice @ " + aliceIP + ":" + from.getPort());
_transport.send(packet);
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.WARN))
@ -328,9 +385,13 @@ class PeerTestManager {
private void receiveFromAliceAsCharlie(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo, long nonce) {
try {
InetAddress aliceIP = InetAddress.getByAddress(from.getIP());
SessionKey aliceIntroKey = new SessionKey();
SessionKey aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
testInfo.readIntroKey(aliceIntroKey.getData(), 0);
UDPPacket packet = _packetBuilder.buildPeerTestToAlice(aliceIP, from.getPort(), aliceIntroKey, _transport.getIntroKey(), nonce);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive from alice as charlie, w/ alice @ " + aliceIP + ":" + from.getPort() + " and nonce " + nonce);
_transport.send(packet);
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.WARN))

View File

@ -20,7 +20,7 @@ public class UDPAddress {
public static final String PROP_HOST = "host";
public static final String PROP_INTRO_KEY = "key";
public static final String PROP_CAPACITY = "opts";
public static final String PROP_CAPACITY = "caps";
public static final char CAPACITY_TESTING = 'A';
public static final char CAPACITY_INTRODUCER = 'B';

View File

@ -104,6 +104,8 @@ public class UDPPacketReader {
return "Session created packet";
case UDPPacket.PAYLOAD_TYPE_SESSION_REQUEST:
return "Session request packet";
case UDPPacket.PAYLOAD_TYPE_TEST:
return "Peer test packet";
default:
return "Other packet type...";
}

View File

@ -16,6 +16,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
@ -26,6 +27,7 @@ import net.i2p.data.RouterIdentity;
import net.i2p.data.SessionKey;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.Transport;
@ -63,6 +65,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
private UDPFlooder _flooder;
private PeerTestManager _testManager;
private ExpirePeerEvent _expireEvent;
private PeerTestEvent _testEvent;
private short _reachabilityStatus;
/** list of RelayPeer objects for people who will relay to us */
private List _relayPeers;
@ -82,7 +86,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
private TransportBid _slowBid;
/** shared slow bid for unconnected peers when we want to prefer UDP */
private TransportBid _slowPreferredBid;
public static final String STYLE = "SSU";
public static final String PROP_INTERNAL_PORT = "i2np.udp.internalPort";
@ -116,6 +120,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
private static final int MAX_CONSECUTIVE_FAILED = 5;
private static final int TEST_FREQUENCY = 3*60*1000;
public UDPTransport(RouterContext ctx) {
super(ctx);
_context = ctx;
@ -141,6 +147,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
_inboundFragments = new InboundMessageFragments(_context, _fragments, this);
_flooder = new UDPFlooder(_context, this);
_expireEvent = new ExpirePeerEvent();
_testEvent = new PeerTestEvent();
_reachabilityStatus = CommSystemFacade.STATUS_UNKNOWN;
_context.statManager().createRateStat("udp.droppedPeer", "How long ago did we receive from a dropped peer (duration == session lifetime", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.droppedPeerInactive", "How long ago did we receive from a dropped peer (duration == session lifetime)", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
@ -234,6 +242,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
_refiller.startup();
_flooder.startup();
_expireEvent.setIsAlive(true);
_testEvent.setIsAlive(true);
}
public void shutdown() {
@ -254,6 +263,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
if (_inboundFragments != null)
_inboundFragments.shutdown();
_expireEvent.setIsAlive(false);
_testEvent.setIsAlive(false);
}
/**
@ -361,29 +371,41 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
*
*/
public void messageReceived(I2NPMessage inMsg, RouterIdentity remoteIdent, Hash remoteIdentHash, long msToReceive, int bytesReceived) {
if (inMsg instanceof DatabaseStoreMessage) {
DatabaseStoreMessage dsm = (DatabaseStoreMessage)inMsg;
if (dsm.getType() == DatabaseStoreMessage.KEY_TYPE_ROUTERINFO) {
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();
Properties opts = info.getOptions();
if ( (opts != null) && (info.isValid()) ) {
String capacities = opts.getProperty(UDPAddress.PROP_CAPACITY);
if (capacities != null) {
PeerState peer = getPeerState(from);
for (int i = 0; i < capacities.length(); i++) {
char capacity = capacities.charAt(i);
List peers = _peersByCapacity[capacity];
synchronized (peers) {
if ( (peers.size() < MAX_PEERS_PER_CAPACITY) && (!peers.contains(peer)) )
peers.add(peer);
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);
List peers = _peersByCapacity[capacity-'A'];
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;
}
}
}
@ -532,7 +554,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
if (capacities != null) {
for (int i = 0; i < capacities.length(); i++) {
char capacity = capacities.charAt(i);
List peers = _peersByCapacity[capacity];
List peers = _peersByCapacity[capacity-'A'];
synchronized (peers) {
peers.remove(peer);
}
@ -665,6 +687,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
if ( (_externalListenPort > 0) && (_externalListenHost != null) ) {
options.setProperty(UDPAddress.PROP_PORT, String.valueOf(_externalListenPort));
options.setProperty(UDPAddress.PROP_HOST, _externalListenHost.getHostAddress());
// if we have explicit external addresses, they had better be reachable
options.setProperty(UDPAddress.PROP_CAPACITY, ""+UDPAddress.CAPACITY_TESTING);
} else {
// grab 3 relays randomly
synchronized (_relayPeers) {
@ -728,8 +752,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
public void succeeded(OutNetMessage msg) {
if (msg == null) return;
if (_log.shouldLog(Log.INFO))
_log.info("Sending message succeeded: " + msg);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending message succeeded: " + msg);
super.afterSend(msg, true);
}
@ -966,4 +990,60 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
}
}
void setReachabilityStatus(short status) { _reachabilityStatus = status; }
public short getReachabilityStatus() { return _reachabilityStatus; }
public void recheckReachability() {
_testEvent.runTest();
}
private static final String PROP_SHOULD_TEST = "i2np.udp.shouldTest";
private boolean shouldTest() {
if (true) return true;
String val = _context.getProperty(PROP_SHOULD_TEST);
return ( (val != null) && ("true".equals(val)) );
}
private class PeerTestEvent implements SimpleTimer.TimedEvent {
private boolean _alive;
/** when did we last test our reachability */
private long _lastTested;
public void timeReached() {
if (shouldTest()) {
long now = _context.clock().now();
if (now - _lastTested >= TEST_FREQUENCY) {
runTest();
}
}
if (_alive) {
long delay = _context.random().nextInt(2*TEST_FREQUENCY);
SimpleTimer.getInstance().addEvent(PeerTestEvent.this, delay);
}
}
private void runTest() {
PeerState bob = getPeerState(UDPAddress.CAPACITY_TESTING);
if (bob != null) {
if (_log.shouldLog(Log.INFO))
_log.info("Running periodic test with bob = " + bob);
_testManager.runTest(bob.getRemoteIPAddress(), bob.getRemotePort(), bob.getCurrentCipherKey(), bob.getCurrentMACKey());
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Unable to run a periodic test, as there are no peers with the capacity required");
}
_lastTested = _context.clock().now();
}
public void setIsAlive(boolean isAlive) {
_alive = isAlive;
if (isAlive) {
long delay = _context.random().nextInt(2*TEST_FREQUENCY);
SimpleTimer.getInstance().addEvent(PeerTestEvent.this, delay);
} else {
SimpleTimer.getInstance().removeEvent(PeerTestEvent.this);
}
}
}
}