diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java index 8b039732e..42290cb15 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java @@ -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. diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java index 26be53d92..7e33c5111 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java @@ -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. * diff --git a/apps/routerconsole/jsp/config.jsp b/apps/routerconsole/jsp/config.jsp index 99735f68b..0472807ee 100644 --- a/apps/routerconsole/jsp/config.jsp +++ b/apps/routerconsole/jsp/config.jsp @@ -35,6 +35,8 @@ this port from arbitrary peers (this requirement will be removed in i2p 0.6.1, b TCP port: " />
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) +
+
Bandwidth limiter
diff --git a/apps/routerconsole/jsp/summary.jsp b/apps/routerconsole/jsp/summary.jsp index 741717c39..4fa834e5a 100644 --- a/apps/routerconsole/jsp/summary.jsp +++ b/apps/routerconsole/jsp/summary.jsp @@ -14,7 +14,8 @@ Version:
Uptime:
Now:
- Memory:
<% + Memory:
+ Status:
<% if (helper.updateAvailable()) { if ("true".equals(System.getProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"))) { out.print(update.getStatus()); @@ -39,7 +40,8 @@ High capacity:
Well integrated:
Failing:
- Shitlisted:
<% + Shitlisted:
+ Known:
<% if (helper.getActivePeers() <= 0) { %>check your NAT/firewall
<% } diff --git a/history.txt b/history.txt index aa2878b2d..eda862f00 100644 --- a/history.txt +++ b/history.txt @@ -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 diff --git a/router/java/src/net/i2p/router/CommSystemFacade.java b/router/java/src/net/i2p/router/CommSystemFacade.java index 9a03ebcb9..f1e39ba21 100644 --- a/router/java/src/net/i2p/router/CommSystemFacade.java +++ b/router/java/src/net/i2p/router/CommSystemFacade.java @@ -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 { diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 10e487c5e..2759afd4a 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -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); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index beaf46f26..dbb684119 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -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) { diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 9ce8976f8..b13fb2754 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -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); } diff --git a/router/java/src/net/i2p/router/transport/Transport.java b/router/java/src/net/i2p/router/transport/Transport.java index c60c03ce4..e7cc3fa0e 100644 --- a/router/java/src/net/i2p/router/transport/Transport.java +++ b/router/java/src/net/i2p/router/transport/Transport.java @@ -41,4 +41,6 @@ public interface Transport { public List getMostRecentErrorMessages(); public void renderStatusHTML(Writer out) throws IOException; + public short getReachabilityStatus(); + public void recheckReachability(); } diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index c24abd3a0..3ca0c140a 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -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() {} + } diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index 551c9b9f3..06eca971f 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -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++) { diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java index 37de93cba..98145dff8 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java @@ -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; } diff --git a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java index 5eacf8736..19c891ab0 100644 --- a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java +++ b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java @@ -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: diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java index b0cb3417a..3184ba198 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java @@ -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)) diff --git a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java index d5f91083a..d7de46022 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java @@ -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'; diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java index f71dc178a..4a22875ba 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java @@ -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..."; } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index dd71a3eaf..1325c779c 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -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); + } + } + } }