From 677a2bb0f61873933cb9cf3eeddc5effd8b9c848 Mon Sep 17 00:00:00 2001 From: oakes Date: Wed, 26 Feb 2014 16:54:52 -0500 Subject: [PATCH] Update to I2P 0.9.11 --- .../client/streaming/AcceptingChannel.java | 4 +- .../i2p/client/streaming/I2PSocketEepGet.java | 7 +- .../streaming/I2PSocketManagerFactory.java | 5 +- .../{ => impl}/AcceptingChannelImpl.java | 14 +- .../streaming/{ => impl}/ConnThrottler.java | 4 +- .../streaming/{ => impl}/Connection.java | 7 +- .../{ => impl}/ConnectionDataReceiver.java | 2 +- .../{ => impl}/ConnectionHandler.java | 15 +- .../{ => impl}/ConnectionManager.java | 7 +- .../{ => impl}/ConnectionOptions.java | 12 +- .../{ => impl}/ConnectionPacketHandler.java | 3 +- .../{ => impl}/I2PServerSocketFull.java | 7 +- .../streaming/{ => impl}/I2PSocketFull.java | 4 +- .../{ => impl}/I2PSocketManagerFull.java | 12 +- .../{ => impl}/I2PSocketOptionsImpl.java | 6 +- .../streaming/{ => impl}/MessageChannel.java | 4 +- .../streaming/{ => impl}/MessageHandler.java | 10 +- .../{ => impl}/MessageInputStream.java | 9 +- .../{ => impl}/MessageOutputStream.java | 2 +- .../client/streaming/{ => impl}/Packet.java | 11 +- .../streaming/{ => impl}/PacketHandler.java | 6 +- .../streaming/{ => impl}/PacketLocal.java | 7 +- .../streaming/{ => impl}/PacketQueue.java | 10 +- .../streaming/{ => impl}/PcapWriter.java | 3 +- .../{ => impl}/RetransmissionTimer.java | 2 +- .../{ => impl}/SchedulerChooser.java | 6 +- .../streaming/{ => impl}/SchedulerClosed.java | 2 +- .../{ => impl}/SchedulerClosing.java | 2 +- .../{ => impl}/SchedulerConnectedBulk.java | 2 +- .../{ => impl}/SchedulerConnecting.java | 2 +- .../streaming/{ => impl}/SchedulerDead.java | 2 +- .../{ => impl}/SchedulerHardDisconnected.java | 2 +- .../streaming/{ => impl}/SchedulerImpl.java | 3 +- .../{ => impl}/SchedulerPreconnect.java | 2 +- .../{ => impl}/SchedulerReceived.java | 2 +- .../{ => impl}/StandardServerSocket.java | 10 +- .../streaming/{ => impl}/StandardSocket.java | 5 +- .../client/streaming/{ => impl}/TCBShare.java | 5 +- .../streaming/{ => impl}/TaskScheduler.java | 2 +- .../{ => impl}/TooManyStreamsException.java | 2 +- .../i2p/client/streaming/impl/package.html | 19 + .../net/i2p/client/streaming/package.html | 9 +- .../apps/org/klomp/snark/I2PSnarkUtil.java | 44 +- .../java/apps/org/klomp/snark/MetaInfo.java | 63 ++- .../apps/org/klomp/snark/PartialPiece.java | 5 +- common/java/apps/org/klomp/snark/Peer.java | 4 +- .../apps/org/klomp/snark/PeerCoordinator.java | 21 +- common/java/apps/org/klomp/snark/PeerID.java | 12 +- common/java/apps/org/klomp/snark/Piece.java | 8 +- common/java/apps/org/klomp/snark/Snark.java | 18 - .../apps/org/klomp/snark/SnarkManager.java | 60 +-- .../apps/org/klomp/snark/StorageListener.java | 2 +- .../apps/org/klomp/snark/TrackerClient.java | 11 +- .../org/klomp/snark/bencode/BDecoder.java | 4 +- .../apps/org/klomp/snark/bencode/BEValue.java | 10 +- .../org/klomp/snark/bencode/BEncoder.java | 30 +- .../klomp/snark/dht/CustomQueryHandler.java | 14 - .../apps/org/klomp/snark/dht/DHTNodes.java | 11 +- .../java/apps/org/klomp/snark/dht/KRPC.java | 66 +-- .../apps/org/klomp/snark/dht/NodeInfo.java | 11 +- .../apps/org/klomp/snark/dht/PersistDHT.java | 8 +- common/java/core/net/i2p/CoreVersion.java | 2 +- common/java/core/net/i2p/I2PAppContext.java | 20 +- common/java/core/net/i2p/app/Outproxy.java | 19 + .../net/i2p/client/ClientWriterRunner.java | 16 +- .../i2p/client/HostReplyMessageHandler.java | 38 ++ .../client/I2PClientMessageHandlerMap.java | 3 + .../java/core/net/i2p/client/I2PSession.java | 63 +++ .../core/net/i2p/client/I2PSessionImpl.java | 290 +++++++++-- .../core/net/i2p/client/I2PSimpleSession.java | 49 +- .../net/i2p/client/naming/LookupDest.java | 26 +- .../i2p/client/naming/MetaNamingService.java | 4 +- .../net/i2p/client/naming/NamingService.java | 4 +- .../core/net/i2p/crypto/DummyDSAEngine.java | 9 +- .../java/core/net/i2p/crypto/ECConstants.java | 4 +- .../crypto/TransientSessionKeyManager.java | 12 +- common/java/core/net/i2p/data/DataHelper.java | 36 +- .../java/core/net/i2p/data/DatabaseEntry.java | 10 +- .../core/net/i2p/data/PrivateKeyFile.java | 16 +- .../java/core/net/i2p/data/RouterAddress.java | 4 +- common/java/core/net/i2p/data/RouterInfo.java | 4 +- .../net/i2p/data/RoutingKeyGenerator.java | 92 +++- common/java/core/net/i2p/data/Signature.java | 6 +- .../net/i2p/data/i2cp/GetDateMessage.java | 49 +- .../net/i2p/data/i2cp/HostLookupMessage.java | 183 +++++++ .../net/i2p/data/i2cp/HostReplyMessage.java | 146 ++++++ .../net/i2p/data/i2cp/I2CPMessageHandler.java | 21 +- .../net/i2p/data/i2cp/I2CPMessageReader.java | 2 +- .../core/net/i2p/data/i2cp/SessionConfig.java | 5 +- .../core/net/i2p/data/i2cp/SessionId.java | 12 + .../net/i2p/kademlia/KBucket.java | 2 +- .../net/i2p/kademlia/KBucketImpl.java | 2 +- .../net/i2p/kademlia/KBucketSet.java | 4 +- .../net/i2p/kademlia/KBucketTrimmer.java | 2 +- .../net/i2p/kademlia/RandomIfOldTrimmer.java | 2 +- .../net/i2p/kademlia/RandomTrimmer.java | 5 +- .../net/i2p/kademlia/RejectTrimmer.java | 2 +- .../net/i2p/kademlia/SelectionCollector.java | 2 +- .../net/i2p/kademlia/XORComparator.java | 4 +- .../net/i2p/kademlia/package.html | 4 +- .../java/core/net/i2p/stat/StatManager.java | 7 +- common/java/core/net/i2p/util/ByteCache.java | 4 +- common/java/core/net/i2p/util/Clock.java | 3 +- common/java/core/net/i2p/util/EepGet.java | 15 +- common/java/core/net/i2p/util/FileUtil.java | 11 +- common/java/core/net/i2p/util/Log.java | 12 +- common/java/core/net/i2p/util/LogManager.java | 8 +- common/java/core/net/i2p/util/LogRecord.java | 6 +- .../core/net/i2p/util/OrderedProperties.java | 10 +- .../java/core/net/i2p/util/PartialEepGet.java | 11 +- .../java/core/net/i2p/util/SimpleTimer.java | 4 +- .../java/core/net/i2p/util/SystemVersion.java | 2 +- common/java/core/net/i2p/util/Translate.java | 47 +- .../java/router/net/i2p/router/Blocklist.java | 29 +- .../net/i2p/router/ClientTunnelSettings.java | 8 +- .../java/router/net/i2p/router/JobQueue.java | 2 +- .../router/net/i2p/router/MultiRouter.java | 3 + .../net/i2p/router/NetworkDatabaseFacade.java | 11 +- .../router/net/i2p/router/OutNetMessage.java | 13 +- .../net/i2p/router/PersistentKeyRing.java | 5 +- common/java/router/net/i2p/router/Router.java | 29 +- .../router/net/i2p/router/RouterContext.java | 14 +- .../net/i2p/router/StatisticsManager.java | 2 +- .../net/i2p/router/TunnelPoolSettings.java | 44 +- .../router/client/ClientConnectionRunner.java | 13 +- .../net/i2p/router/client/ClientManager.java | 10 +- .../client/ClientMessageEventListener.java | 119 ++++- .../net/i2p/router/client/LookupDestJob.java | 88 +++- .../dummy/DummyNetworkDatabaseFacade.java | 1 + .../OutboundClientMessageOneShotJob.java | 2 +- .../networkdb/kademlia/ExpireRoutersJob.java | 11 +- .../router/networkdb/kademlia/ExploreJob.java | 3 +- .../kademlia/ExploreKeySelectorJob.java | 35 +- .../kademlia/FloodOnlySearchJob.java | 8 +- .../kademlia/FloodfillMonitorJob.java | 2 +- .../FloodfillNetworkDatabaseFacade.java | 36 +- .../kademlia/FloodfillPeerSelector.java | 27 +- .../kademlia/IterativeSearchJob.java | 67 ++- .../router/networkdb/kademlia/KBucket.java | 83 --- .../networkdb/kademlia/KBucketImpl.java | 474 ------------------ .../router/networkdb/kademlia/KBucketSet.java | 219 -------- .../KademliaNetworkDatabaseFacade.java | 94 +++- .../networkdb/kademlia/PeerSelector.java | 12 +- .../kademlia/PersistentDataStore.java | 27 +- .../networkdb/kademlia/SearchState.java | 13 +- .../kademlia/SelectionCollector.java | 10 - .../router/networkdb/kademlia/StoreJob.java | 7 +- .../router/networkdb/kademlia/StoreState.java | 25 +- .../networkdb/kademlia/XORComparator.java | 36 -- .../i2p/router/networkdb/reseed/Reseeder.java | 8 +- .../peermanager/CapacityCalculator.java | 2 + .../i2p/router/peermanager/PeerManager.java | 21 +- .../router/peermanager/ProfileOrganizer.java | 40 +- .../i2p/router/startup/LoadClientAppsJob.java | 20 +- .../i2p/router/startup/RouterAppManager.java | 9 +- .../tasks/UpdateRoutingKeyModifierJob.java | 31 +- .../router/net/i2p/router/time/NtpClient.java | 7 +- .../transport/CommSystemFacadeImpl.java | 26 +- .../net/i2p/router/transport/GeoIP.java | 16 +- .../net/i2p/router/transport/GeoIPv6.java | 4 +- .../router/net/i2p/router/transport/UPnP.java | 3 +- .../router/transport/ntcp/EventPumper.java | 2 +- .../router/transport/ntcp/NTCPConnection.java | 105 ++-- .../transport/ntcp/NTCPSendFinisher.java | 2 +- .../net/i2p/router/transport/ntcp/Writer.java | 11 +- .../transport/udp/OutboundMessageState.java | 32 +- .../i2p/router/transport/udp/PeerState.java | 85 +++- .../router/transport/udp/UDPTransport.java | 8 +- .../tunnel/InboundMessageDistributor.java | 59 ++- .../tunnel/pool/ExploratoryPeerSelector.java | 4 +- .../i2p/router/tunnel/pool/TunnelPool.java | 5 +- .../router/net/i2p/router/util/EventLog.java | 9 +- .../net/i2p/router/util/PriBlockingQueue.java | 2 +- 173 files changed, 2254 insertions(+), 1805 deletions(-) rename common/java/apps/net/i2p/client/streaming/{ => impl}/AcceptingChannelImpl.java (91%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/ConnThrottler.java (94%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/Connection.java (99%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/ConnectionDataReceiver.java (99%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/ConnectionHandler.java (97%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/ConnectionManager.java (99%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/ConnectionOptions.java (99%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/ConnectionPacketHandler.java (99%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/I2PServerSocketFull.java (86%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/I2PSocketFull.java (97%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/I2PSocketManagerFull.java (97%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/I2PSocketOptionsImpl.java (97%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/MessageChannel.java (98%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/MessageHandler.java (94%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/MessageInputStream.java (98%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/MessageOutputStream.java (99%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/Packet.java (99%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/PacketHandler.java (98%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/PacketLocal.java (98%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/PacketQueue.java (95%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/PcapWriter.java (99%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/RetransmissionTimer.java (93%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/SchedulerChooser.java (92%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/SchedulerClosed.java (97%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/SchedulerClosing.java (98%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/SchedulerConnectedBulk.java (98%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/SchedulerConnecting.java (98%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/SchedulerDead.java (96%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/SchedulerHardDisconnected.java (97%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/SchedulerImpl.java (89%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/SchedulerPreconnect.java (97%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/SchedulerReceived.java (97%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/StandardServerSocket.java (92%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/StandardSocket.java (98%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/TCBShare.java (98%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/TaskScheduler.java (93%) rename common/java/apps/net/i2p/client/streaming/{ => impl}/TooManyStreamsException.java (91%) create mode 100644 common/java/apps/net/i2p/client/streaming/impl/package.html delete mode 100644 common/java/apps/org/klomp/snark/dht/CustomQueryHandler.java create mode 100644 common/java/core/net/i2p/app/Outproxy.java create mode 100644 common/java/core/net/i2p/client/HostReplyMessageHandler.java create mode 100644 common/java/core/net/i2p/data/i2cp/HostLookupMessage.java create mode 100644 common/java/core/net/i2p/data/i2cp/HostReplyMessage.java rename common/java/{apps => core}/net/i2p/kademlia/KBucket.java (97%) rename common/java/{apps => core}/net/i2p/kademlia/KBucketImpl.java (98%) rename common/java/{apps => core}/net/i2p/kademlia/KBucketSet.java (99%) rename common/java/{apps => core}/net/i2p/kademlia/KBucketTrimmer.java (91%) rename common/java/{apps => core}/net/i2p/kademlia/RandomIfOldTrimmer.java (91%) rename common/java/{apps => core}/net/i2p/kademlia/RandomTrimmer.java (87%) rename common/java/{apps => core}/net/i2p/kademlia/RejectTrimmer.java (85%) rename common/java/{apps => core}/net/i2p/kademlia/SelectionCollector.java (80%) rename common/java/{apps => core}/net/i2p/kademlia/XORComparator.java (88%) rename common/java/{apps => core}/net/i2p/kademlia/package.html (60%) delete mode 100644 common/java/router/net/i2p/router/networkdb/kademlia/KBucket.java delete mode 100644 common/java/router/net/i2p/router/networkdb/kademlia/KBucketImpl.java delete mode 100644 common/java/router/net/i2p/router/networkdb/kademlia/KBucketSet.java delete mode 100644 common/java/router/net/i2p/router/networkdb/kademlia/SelectionCollector.java delete mode 100644 common/java/router/net/i2p/router/networkdb/kademlia/XORComparator.java diff --git a/common/java/apps/net/i2p/client/streaming/AcceptingChannel.java b/common/java/apps/net/i2p/client/streaming/AcceptingChannel.java index 292bb28..3cf5949 100644 --- a/common/java/apps/net/i2p/client/streaming/AcceptingChannel.java +++ b/common/java/apps/net/i2p/client/streaming/AcceptingChannel.java @@ -16,11 +16,11 @@ */ public abstract class AcceptingChannel extends SelectableChannel { - abstract I2PSocket accept() throws I2PException, ConnectException; + protected abstract I2PSocket accept() throws I2PException, ConnectException; protected final I2PSocketManager _socketManager; - AcceptingChannel(I2PSocketManager manager) { + protected AcceptingChannel(I2PSocketManager manager) { this._socketManager = manager; } } diff --git a/common/java/apps/net/i2p/client/streaming/I2PSocketEepGet.java b/common/java/apps/net/i2p/client/streaming/I2PSocketEepGet.java index 5b29d9d..99c81af 100644 --- a/common/java/apps/net/i2p/client/streaming/I2PSocketEepGet.java +++ b/common/java/apps/net/i2p/client/streaming/I2PSocketEepGet.java @@ -6,6 +6,7 @@ import java.net.MalformedURLException; import java.net.UnknownHostException; import java.net.URL; +import java.util.Locale; import java.util.Properties; import net.i2p.I2PAppContext; @@ -193,13 +194,17 @@ protected String getRequest() throws IOException { buf.append("Accept-Encoding: \r\n" + "Cache-control: no-cache\r\n" + "Pragma: no-cache\r\n" + - "User-Agent: " + USER_AGENT + "\r\n" + "Connection: close\r\n"); + boolean uaOverridden = false; if (_extraHeaders != null) { for (String hdr : _extraHeaders) { + if (hdr.toLowerCase(Locale.US).startsWith("user-agent: ")) + uaOverridden = true; buf.append(hdr).append("\r\n"); } } + if(!uaOverridden) + buf.append("User-Agent: " + USER_AGENT + "\r\n"); buf.append("\r\n"); return buf.toString(); } diff --git a/common/java/apps/net/i2p/client/streaming/I2PSocketManagerFactory.java b/common/java/apps/net/i2p/client/streaming/I2PSocketManagerFactory.java index 7119996..e7aff4c 100644 --- a/common/java/apps/net/i2p/client/streaming/I2PSocketManagerFactory.java +++ b/common/java/apps/net/i2p/client/streaming/I2PSocketManagerFactory.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; -import java.util.Iterator; import java.util.Map; import java.util.Properties; @@ -26,7 +25,7 @@ public class I2PSocketManagerFactory { public static final String PROP_MANAGER = "i2p.streaming.manager"; - public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerFull"; + public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.impl.I2PSocketManagerFull"; /** * Create a socket manager using a brand new destination connected to the @@ -202,7 +201,7 @@ private static I2PSocketManager createManager(InputStream myPrivateKeyStream, St if (opts == null) opts = new Properties(); Properties syscopy = (Properties) System.getProperties().clone(); - for (Map.Entry e : syscopy.entrySet()) { + for (Map.Entry e : syscopy.entrySet()) { String name = (String) e.getKey(); if (!opts.containsKey(name)) opts.setProperty(name, (String) e.getValue()); diff --git a/common/java/apps/net/i2p/client/streaming/AcceptingChannelImpl.java b/common/java/apps/net/i2p/client/streaming/impl/AcceptingChannelImpl.java similarity index 91% rename from common/java/apps/net/i2p/client/streaming/AcceptingChannelImpl.java rename to common/java/apps/net/i2p/client/streaming/impl/AcceptingChannelImpl.java index 61dc901..b495243 100644 --- a/common/java/apps/net/i2p/client/streaming/AcceptingChannelImpl.java +++ b/common/java/apps/net/i2p/client/streaming/impl/AcceptingChannelImpl.java @@ -1,6 +1,4 @@ -package net.i2p.client.streaming; - -import net.i2p.I2PException; +package net.i2p.client.streaming.impl; import java.net.SocketTimeoutException; import java.net.ConnectException; @@ -12,6 +10,12 @@ import java.nio.channels.spi.AbstractSelectionKey; import java.nio.channels.spi.SelectorProvider; +import net.i2p.I2PException; +import net.i2p.client.streaming.AcceptingChannel; +import net.i2p.client.streaming.I2PServerSocket; +import net.i2p.client.streaming.I2PSocket; +import net.i2p.client.streaming.I2PSocketManager; + /** * As this does not (yet) extend ServerSocketChannel it cannot be returned by StandardServerSocket.getChannel(), * until we implement an I2P SocketAddress class. @@ -29,7 +33,7 @@ class AcceptingChannelImpl extends AcceptingChannel { private volatile I2PSocket next; private final I2PServerSocket socket; - I2PSocket accept() throws I2PException, ConnectException { + protected I2PSocket accept() throws I2PException, ConnectException { I2PSocket sock; try { sock = socket.accept(); @@ -43,7 +47,7 @@ I2PSocket accept() throws I2PException, ConnectException { } } - AcceptingChannelImpl(I2PSocketManager manager) { + protected AcceptingChannelImpl(I2PSocketManager manager) { super(manager); // this cheats and just sets the manager timeout low in order to repeatedly poll it. // that means we can "only" accept one new connection every 100 milliseconds. diff --git a/common/java/apps/net/i2p/client/streaming/ConnThrottler.java b/common/java/apps/net/i2p/client/streaming/impl/ConnThrottler.java similarity index 94% rename from common/java/apps/net/i2p/client/streaming/ConnThrottler.java rename to common/java/apps/net/i2p/client/streaming/impl/ConnThrottler.java index 4abe472..14add7b 100644 --- a/common/java/apps/net/i2p/client/streaming/ConnThrottler.java +++ b/common/java/apps/net/i2p/client/streaming/impl/ConnThrottler.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.util.concurrent.atomic.AtomicInteger; @@ -30,7 +30,7 @@ class ConnThrottler { _totalMax = totalMax; this.counter = new ObjectCounter(); _currentTotal = new AtomicInteger(); - // shorten the initial period by a random amount,mpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), + // shorten the initial period by a random amount // to prevent correlation across destinations // and identification of router startup time SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), diff --git a/common/java/apps/net/i2p/client/streaming/Connection.java b/common/java/apps/net/i2p/client/streaming/impl/Connection.java similarity index 99% rename from common/java/apps/net/i2p/client/streaming/Connection.java rename to common/java/apps/net/i2p/client/streaming/impl/Connection.java index db086d8..47cfd6a 100644 --- a/common/java/apps/net/i2p/client/streaming/Connection.java +++ b/common/java/apps/net/i2p/client/streaming/impl/Connection.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.IOException; import java.util.ArrayList; @@ -15,7 +15,6 @@ import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.util.Log; -import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer2; @@ -125,7 +124,7 @@ public Connection(I2PAppContext ctx, ConnectionManager manager, SchedulerChooser // FIXME pass through a passive flush delay setting as the 4th arg _outputStream = new MessageOutputStream(_context, timer, _receiver, (opts == null ? Packet.MAX_PAYLOAD_SIZE : opts.getMaxMessageSize())); _timer = timer; - _outboundPackets = new TreeMap(); + _outboundPackets = new TreeMap(); if (opts != null) { _localPort = opts.getLocalPort(); _remotePort = opts.getPort(); @@ -469,7 +468,7 @@ public List ackPackets(long ackThrough, long nacks[]) { } if (!nacked) { // aka ACKed if (acked == null) - acked = new ArrayList(8); + acked = new ArrayList(8); PacketLocal ackedPacket = e.getValue(); ackedPacket.ackReceived(); acked.add(ackedPacket); diff --git a/common/java/apps/net/i2p/client/streaming/ConnectionDataReceiver.java b/common/java/apps/net/i2p/client/streaming/impl/ConnectionDataReceiver.java similarity index 99% rename from common/java/apps/net/i2p/client/streaming/ConnectionDataReceiver.java rename to common/java/apps/net/i2p/client/streaming/impl/ConnectionDataReceiver.java index c758f69..dbf546a 100644 --- a/common/java/apps/net/i2p/client/streaming/ConnectionDataReceiver.java +++ b/common/java/apps/net/i2p/client/streaming/impl/ConnectionDataReceiver.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; import net.i2p.data.ByteArray; diff --git a/common/java/apps/net/i2p/client/streaming/ConnectionHandler.java b/common/java/apps/net/i2p/client/streaming/impl/ConnectionHandler.java similarity index 97% rename from common/java/apps/net/i2p/client/streaming/ConnectionHandler.java rename to common/java/apps/net/i2p/client/streaming/impl/ConnectionHandler.java index 2c22b63..5ac1468 100644 --- a/common/java/apps/net/i2p/client/streaming/ConnectionHandler.java +++ b/common/java/apps/net/i2p/client/streaming/impl/ConnectionHandler.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -6,7 +6,6 @@ import net.i2p.I2PAppContext; import net.i2p.data.Destination; import net.i2p.util.Log; -import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; /** @@ -22,7 +21,7 @@ class ConnectionHandler { private final Log _log; private final ConnectionManager _manager; private final LinkedBlockingQueue _synQueue; - private boolean _active; + private volatile boolean _active; private int _acceptTimeout; /** max time after receiveNewSyn() and before the matched accept() */ @@ -231,7 +230,8 @@ private void sendReset(Packet packet) { } private class TimeoutSyn implements SimpleTimer.TimedEvent { - private Packet _synPacket; + private final Packet _synPacket; + public TimeoutSyn(Packet packet) { _synPacket = packet; } @@ -240,12 +240,15 @@ public void timeReached() { boolean removed = _synQueue.remove(_synPacket); if (removed) { - if (_synPacket.isFlagSet(Packet.FLAG_SYNCHRONIZE)) + if (_synPacket.isFlagSet(Packet.FLAG_SYNCHRONIZE)) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Expired on the SYN queue: " + _synPacket); // timeout - send RST sendReset(_synPacket); - else + } else { // non-syn packet got stranded on the syn queue, send it to the con reReceivePacket(_synPacket); + } } else { // handled. noop } diff --git a/common/java/apps/net/i2p/client/streaming/ConnectionManager.java b/common/java/apps/net/i2p/client/streaming/impl/ConnectionManager.java similarity index 99% rename from common/java/apps/net/i2p/client/streaming/ConnectionManager.java rename to common/java/apps/net/i2p/client/streaming/impl/ConnectionManager.java index e4281b5..435a09c 100644 --- a/common/java/apps/net/i2p/client/streaming/ConnectionManager.java +++ b/common/java/apps/net/i2p/client/streaming/impl/ConnectionManager.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.util.HashSet; import java.util.Iterator; @@ -593,8 +593,9 @@ public boolean ping(Destination peer, long timeoutMs, boolean blocking, PingNoti Long id = Long.valueOf(_context.random().nextLong(Packet.MAX_STREAM_ID-1)+1); PacketLocal packet = new PacketLocal(_context, peer); packet.setSendStreamId(id.longValue()); - packet.setFlag(Packet.FLAG_ECHO); - packet.setFlag(Packet.FLAG_SIGNATURE_INCLUDED); + packet.setFlag(Packet.FLAG_ECHO | + Packet.FLAG_NO_ACK | + Packet.FLAG_SIGNATURE_INCLUDED); packet.setOptionalFrom(_session.getMyDestination()); //if ( (keyToUse != null) && (tagsToSend != null) ) { // packet.setKeyUsed(keyToUse); diff --git a/common/java/apps/net/i2p/client/streaming/ConnectionOptions.java b/common/java/apps/net/i2p/client/streaming/impl/ConnectionOptions.java similarity index 99% rename from common/java/apps/net/i2p/client/streaming/ConnectionOptions.java rename to common/java/apps/net/i2p/client/streaming/impl/ConnectionOptions.java index 9f2c5d5..fe4c5d0 100644 --- a/common/java/apps/net/i2p/client/streaming/ConnectionOptions.java +++ b/common/java/apps/net/i2p/client/streaming/impl/ConnectionOptions.java @@ -1,4 +1,6 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; + +import net.i2p.client.streaming.I2PSocketOptions; import java.util.Collections; import java.util.HashSet; @@ -767,13 +769,13 @@ private void initLists(Properties opts) { // but avoid concurrent modification just in case Set accessList, blackList; if (accessListEnabled) - accessList = new HashSet(); + accessList = new HashSet(); else - accessList = Collections.EMPTY_SET; + accessList = Collections.emptySet(); if (blackListEnabled) - blackList = new HashSet(); + blackList = new HashSet(); else - blackList = Collections.EMPTY_SET; + blackList = Collections.emptySet(); if (accessListEnabled || blackListEnabled) { String hashes = opts.getProperty(PROP_ACCESS_LIST); if (hashes == null) diff --git a/common/java/apps/net/i2p/client/streaming/ConnectionPacketHandler.java b/common/java/apps/net/i2p/client/streaming/impl/ConnectionPacketHandler.java similarity index 99% rename from common/java/apps/net/i2p/client/streaming/ConnectionPacketHandler.java rename to common/java/apps/net/i2p/client/streaming/impl/ConnectionPacketHandler.java index 011c949..0595c84 100644 --- a/common/java/apps/net/i2p/client/streaming/ConnectionPacketHandler.java +++ b/common/java/apps/net/i2p/client/streaming/impl/ConnectionPacketHandler.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.util.List; @@ -7,7 +7,6 @@ import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.util.Log; -import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; /** diff --git a/common/java/apps/net/i2p/client/streaming/I2PServerSocketFull.java b/common/java/apps/net/i2p/client/streaming/impl/I2PServerSocketFull.java similarity index 86% rename from common/java/apps/net/i2p/client/streaming/I2PServerSocketFull.java rename to common/java/apps/net/i2p/client/streaming/impl/I2PServerSocketFull.java index 5eddc45..bf67366 100644 --- a/common/java/apps/net/i2p/client/streaming/I2PServerSocketFull.java +++ b/common/java/apps/net/i2p/client/streaming/impl/I2PServerSocketFull.java @@ -1,7 +1,12 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.net.SocketTimeoutException; + import net.i2p.I2PException; +import net.i2p.client.streaming.AcceptingChannel; +import net.i2p.client.streaming.I2PServerSocket; +import net.i2p.client.streaming.I2PSocket; +import net.i2p.client.streaming.I2PSocketManager; /** * Bridge to allow accepting new connections diff --git a/common/java/apps/net/i2p/client/streaming/I2PSocketFull.java b/common/java/apps/net/i2p/client/streaming/impl/I2PSocketFull.java similarity index 97% rename from common/java/apps/net/i2p/client/streaming/I2PSocketFull.java rename to common/java/apps/net/i2p/client/streaming/impl/I2PSocketFull.java index 09060b5..cda4dee 100644 --- a/common/java/apps/net/i2p/client/streaming/I2PSocketFull.java +++ b/common/java/apps/net/i2p/client/streaming/impl/I2PSocketFull.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.IOException; import java.io.InputStream; @@ -8,6 +8,8 @@ import net.i2p.I2PAppContext; import net.i2p.client.I2PSession; +import net.i2p.client.streaming.I2PSocket; +import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.Destination; import net.i2p.util.Log; diff --git a/common/java/apps/net/i2p/client/streaming/I2PSocketManagerFull.java b/common/java/apps/net/i2p/client/streaming/impl/I2PSocketManagerFull.java similarity index 97% rename from common/java/apps/net/i2p/client/streaming/I2PSocketManagerFull.java rename to common/java/apps/net/i2p/client/streaming/impl/I2PSocketManagerFull.java index 0d06597..757eb37 100644 --- a/common/java/apps/net/i2p/client/streaming/I2PSocketManagerFull.java +++ b/common/java/apps/net/i2p/client/streaming/impl/I2PSocketManagerFull.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.IOException; import java.net.NoRouteToHostException; @@ -15,10 +15,13 @@ import net.i2p.I2PException; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; +import net.i2p.client.streaming.I2PServerSocket; +import net.i2p.client.streaming.I2PSocket; +import net.i2p.client.streaming.I2PSocketManager; +import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.Destination; import net.i2p.util.Log; - /** * Centralize the coordination and multiplexing of the local client's streaming. * There should be one I2PSocketManager for each I2PSession, and if an application @@ -302,8 +305,9 @@ private Socket connectToSocket(Destination peer, I2PSocketOptions options) throw I2PSocket sock = connect(peer, options); return new StandardSocket(sock); } catch (I2PException i2pe) { - // fixme in 1.6 change to cause - throw new IOException(i2pe.toString()); + IOException ioe = new IOException("connect fail"); + ioe.initCause(i2pe); + throw ioe; } } diff --git a/common/java/apps/net/i2p/client/streaming/I2PSocketOptionsImpl.java b/common/java/apps/net/i2p/client/streaming/impl/I2PSocketOptionsImpl.java similarity index 97% rename from common/java/apps/net/i2p/client/streaming/I2PSocketOptionsImpl.java rename to common/java/apps/net/i2p/client/streaming/impl/I2PSocketOptionsImpl.java index 02a4d9d..0c077cc 100644 --- a/common/java/apps/net/i2p/client/streaming/I2PSocketOptionsImpl.java +++ b/common/java/apps/net/i2p/client/streaming/impl/I2PSocketOptionsImpl.java @@ -1,7 +1,9 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.util.Properties; +import net.i2p.client.streaming.I2PSocketOptions; + /** * Define the configuration for streaming and verifying data on the socket. * Use I2PSocketManager.buildOptions() to get one of these. @@ -93,7 +95,7 @@ protected static int getInt(Properties opts, String name, int defaultVal) { } } - protected static double getDouble(Properties opts, String name, double defaultVal) { + public static double getDouble(Properties opts, String name, double defaultVal) { if (opts == null) return defaultVal; String val = opts.getProperty(name); if (val == null) { diff --git a/common/java/apps/net/i2p/client/streaming/MessageChannel.java b/common/java/apps/net/i2p/client/streaming/impl/MessageChannel.java similarity index 98% rename from common/java/apps/net/i2p/client/streaming/MessageChannel.java rename to common/java/apps/net/i2p/client/streaming/impl/MessageChannel.java index af10cf8..5e3ceca 100644 --- a/common/java/apps/net/i2p/client/streaming/MessageChannel.java +++ b/common/java/apps/net/i2p/client/streaming/impl/MessageChannel.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.IOException; import java.io.InterruptedIOException; @@ -14,6 +14,8 @@ import java.util.logging.Level; import java.util.logging.Logger; +import net.i2p.client.streaming.I2PSocket; + /** * As this does not (yet) extend SocketChannel it cannot be returned by StandardSocket.getChannel(), * until we implement an I2P SocketAddress class. diff --git a/common/java/apps/net/i2p/client/streaming/MessageHandler.java b/common/java/apps/net/i2p/client/streaming/impl/MessageHandler.java similarity index 94% rename from common/java/apps/net/i2p/client/streaming/MessageHandler.java rename to common/java/apps/net/i2p/client/streaming/impl/MessageHandler.java index 4aba07d..cb3dd64 100644 --- a/common/java/apps/net/i2p/client/streaming/MessageHandler.java +++ b/common/java/apps/net/i2p/client/streaming/impl/MessageHandler.java @@ -1,6 +1,5 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; -import java.util.Iterator; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -8,6 +7,8 @@ import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.client.I2PSessionMuxedListener; +import net.i2p.client.streaming.I2PSocketManager; +import net.i2p.client.streaming.I2PSocketManager.DisconnectListener; import net.i2p.util.Log; /** @@ -25,7 +26,7 @@ class MessageHandler implements I2PSessionMuxedListener { public MessageHandler(I2PAppContext ctx, ConnectionManager mgr) { _manager = mgr; _context = ctx; - _listeners = new CopyOnWriteArraySet(); + _listeners = new CopyOnWriteArraySet(); _log = ctx.logManager().getLog(MessageHandler.class); _context.statManager().createRateStat("stream.packetReceiveFailure", "When do we fail to decrypt or otherwise receive a packet sent to us?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 }); } @@ -93,8 +94,7 @@ public void disconnected(I2PSession session) { _log.warn("I2PSession disconnected"); _manager.disconnectAllHard(); - for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) { - I2PSocketManager.DisconnectListener lsnr = iter.next(); + for (I2PSocketManager.DisconnectListener lsnr : _listeners) { lsnr.sessionDisconnected(); } _listeners.clear(); diff --git a/common/java/apps/net/i2p/client/streaming/MessageInputStream.java b/common/java/apps/net/i2p/client/streaming/impl/MessageInputStream.java similarity index 98% rename from common/java/apps/net/i2p/client/streaming/MessageInputStream.java rename to common/java/apps/net/i2p/client/streaming/impl/MessageInputStream.java index bcfb643..8527bc7 100644 --- a/common/java/apps/net/i2p/client/streaming/MessageInputStream.java +++ b/common/java/apps/net/i2p/client/streaming/impl/MessageInputStream.java @@ -1,10 +1,9 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -63,11 +62,11 @@ class MessageInputStream extends InputStream { public MessageInputStream(I2PAppContext ctx) { _log = ctx.logManager().getLog(MessageInputStream.class); - _readyDataBlocks = new ArrayList(4); + _readyDataBlocks = new ArrayList(4); _highestReadyBlockId = -1; _highestBlockId = -1; _readTimeout = -1; - _notYetReadyBlocks = new HashMap(4); + _notYetReadyBlocks = new HashMap(4); _dataLock = new Object(); //_cache = ByteCache.getInstance(128, Packet.MAX_PAYLOAD_SIZE); } @@ -110,7 +109,7 @@ private long[] locked_getNacks() { // ACK } else { if (ids == null) - ids = new ArrayList(4); + ids = new ArrayList(4); ids.add(l); } } diff --git a/common/java/apps/net/i2p/client/streaming/MessageOutputStream.java b/common/java/apps/net/i2p/client/streaming/impl/MessageOutputStream.java similarity index 99% rename from common/java/apps/net/i2p/client/streaming/MessageOutputStream.java rename to common/java/apps/net/i2p/client/streaming/impl/MessageOutputStream.java index 5afb373..681c77b 100644 --- a/common/java/apps/net/i2p/client/streaming/MessageOutputStream.java +++ b/common/java/apps/net/i2p/client/streaming/impl/MessageOutputStream.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.IOException; import java.io.InterruptedIOException; diff --git a/common/java/apps/net/i2p/client/streaming/Packet.java b/common/java/apps/net/i2p/client/streaming/impl/Packet.java similarity index 99% rename from common/java/apps/net/i2p/client/streaming/Packet.java rename to common/java/apps/net/i2p/client/streaming/impl/Packet.java index 6fbe1dc..16784d3 100644 --- a/common/java/apps/net/i2p/client/streaming/Packet.java +++ b/common/java/apps/net/i2p/client/streaming/impl/Packet.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -295,8 +295,15 @@ public ByteArray acquirePayload() { */ public boolean isFlagSet(int flag) { return 0 != (_flags & flag); } + /** + * @param flag bitmask of any flag(s) + */ public void setFlag(int flag) { _flags |= flag; } + /** + * @param flag bitmask of any flag(s) + * @param set true to set, false to clear + */ public void setFlag(int flag, boolean set) { if (set) _flags |= flag; @@ -304,7 +311,7 @@ public void setFlag(int flag, boolean set) { _flags &= ~flag; } - public void setFlags(int flags) { _flags = flags; } + private void setFlags(int flags) { _flags = flags; } /** the signature on the packet (only included if the flag for it is set) * @return signature on the packet if the flag for signatures is set diff --git a/common/java/apps/net/i2p/client/streaming/PacketHandler.java b/common/java/apps/net/i2p/client/streaming/impl/PacketHandler.java similarity index 98% rename from common/java/apps/net/i2p/client/streaming/PacketHandler.java rename to common/java/apps/net/i2p/client/streaming/impl/PacketHandler.java index e528abe..c40afe4 100644 --- a/common/java/apps/net/i2p/client/streaming/PacketHandler.java +++ b/common/java/apps/net/i2p/client/streaming/impl/PacketHandler.java @@ -1,8 +1,7 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.Set; import net.i2p.I2PAppContext; import net.i2p.I2PException; @@ -350,8 +349,7 @@ else if (packet.getOptionalSignature() == null) } } else { PacketLocal pong = new PacketLocal(_context, packet.getOptionalFrom()); - pong.setFlag(Packet.FLAG_ECHO, true); - pong.setFlag(Packet.FLAG_SIGNATURE_INCLUDED, false); + pong.setFlag(Packet.FLAG_ECHO | Packet.FLAG_NO_ACK); pong.setReceiveStreamId(packet.getSendStreamId()); _manager.getPacketQueue().enqueue(pong); } diff --git a/common/java/apps/net/i2p/client/streaming/PacketLocal.java b/common/java/apps/net/i2p/client/streaming/impl/PacketLocal.java similarity index 98% rename from common/java/apps/net/i2p/client/streaming/PacketLocal.java rename to common/java/apps/net/i2p/client/streaming/impl/PacketLocal.java index 71b5d08..f95ff33 100644 --- a/common/java/apps/net/i2p/client/streaming/PacketLocal.java +++ b/common/java/apps/net/i2p/client/streaming/impl/PacketLocal.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.IOException; import java.util.Collections; @@ -8,6 +8,7 @@ import net.i2p.I2PAppContext; import net.i2p.data.Destination; import net.i2p.data.SessionKey; +import net.i2p.data.SessionTag; import net.i2p.util.Log; import net.i2p.util.SimpleTimer2; @@ -68,12 +69,12 @@ public void setKeyUsed(SessionKey key) { /** * @deprecated should always return null or an empty set */ - public Set getTagsSent() { return Collections.EMPTY_SET; } + public Set getTagsSent() { return Collections.emptySet(); } /** * @deprecated I2PSession throws out the tags */ - public void setTagsSent(Set tags) { + public void setTagsSent(Set tags) { if (tags != null && !tags.isEmpty()) _log.error("Who is sending tags thru the streaming lib? " + tags.size()); /**** diff --git a/common/java/apps/net/i2p/client/streaming/PacketQueue.java b/common/java/apps/net/i2p/client/streaming/impl/PacketQueue.java similarity index 95% rename from common/java/apps/net/i2p/client/streaming/PacketQueue.java rename to common/java/apps/net/i2p/client/streaming/impl/PacketQueue.java index 4764c9a..dd44087 100644 --- a/common/java/apps/net/i2p/client/streaming/PacketQueue.java +++ b/common/java/apps/net/i2p/client/streaming/impl/PacketQueue.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; import net.i2p.client.I2PSession; @@ -109,7 +109,13 @@ public boolean enqueue(PacketLocal packet) { options.setTagsToSend(INITIAL_TAGS_TO_SEND); options.setTagThreshold(MIN_TAG_THRESHOLD); } else if (packet.isFlagSet(FLAGS_FINAL_TAGS)) { - options.setSendLeaseSet(false); + if (packet.isFlagSet(Packet.FLAG_ECHO)) { + // Send LS for PING, not for PONG + if (packet.getSendStreamId() <= 0) // pong + options.setSendLeaseSet(false); + } else { + options.setSendLeaseSet(false); + } options.setTagsToSend(FINAL_TAGS_TO_SEND); options.setTagThreshold(FINAL_TAG_THRESHOLD); } else { diff --git a/common/java/apps/net/i2p/client/streaming/PcapWriter.java b/common/java/apps/net/i2p/client/streaming/impl/PcapWriter.java similarity index 99% rename from common/java/apps/net/i2p/client/streaming/PcapWriter.java rename to common/java/apps/net/i2p/client/streaming/impl/PcapWriter.java index 00448f4..8e3c77c 100644 --- a/common/java/apps/net/i2p/client/streaming/PcapWriter.java +++ b/common/java/apps/net/i2p/client/streaming/impl/PcapWriter.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.BufferedOutputStream; import java.io.File; @@ -9,7 +9,6 @@ import net.i2p.I2PAppContext; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; -import net.i2p.data.Hash; /** * Write a standard pcap file with a "TCP" packet that can be analyzed with diff --git a/common/java/apps/net/i2p/client/streaming/RetransmissionTimer.java b/common/java/apps/net/i2p/client/streaming/impl/RetransmissionTimer.java similarity index 93% rename from common/java/apps/net/i2p/client/streaming/RetransmissionTimer.java rename to common/java/apps/net/i2p/client/streaming/impl/RetransmissionTimer.java index 93e9fda..5ffac10 100644 --- a/common/java/apps/net/i2p/client/streaming/RetransmissionTimer.java +++ b/common/java/apps/net/i2p/client/streaming/impl/RetransmissionTimer.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; import net.i2p.util.SimpleTimer2; diff --git a/common/java/apps/net/i2p/client/streaming/SchedulerChooser.java b/common/java/apps/net/i2p/client/streaming/impl/SchedulerChooser.java similarity index 92% rename from common/java/apps/net/i2p/client/streaming/SchedulerChooser.java rename to common/java/apps/net/i2p/client/streaming/impl/SchedulerChooser.java index 1dd4739..f995954 100644 --- a/common/java/apps/net/i2p/client/streaming/SchedulerChooser.java +++ b/common/java/apps/net/i2p/client/streaming/impl/SchedulerChooser.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.util.ArrayList; import java.util.List; @@ -36,8 +36,8 @@ public TaskScheduler getScheduler(Connection con) { return _nullScheduler; } - private List createSchedulers() { - List rv = new ArrayList(8); + private List createSchedulers() { + List rv = new ArrayList(8); rv.add(new SchedulerHardDisconnected(_context)); rv.add(new SchedulerPreconnect(_context)); rv.add(new SchedulerConnecting(_context)); diff --git a/common/java/apps/net/i2p/client/streaming/SchedulerClosed.java b/common/java/apps/net/i2p/client/streaming/impl/SchedulerClosed.java similarity index 97% rename from common/java/apps/net/i2p/client/streaming/SchedulerClosed.java rename to common/java/apps/net/i2p/client/streaming/impl/SchedulerClosed.java index c9e8cae..4159c76 100644 --- a/common/java/apps/net/i2p/client/streaming/SchedulerClosed.java +++ b/common/java/apps/net/i2p/client/streaming/impl/SchedulerClosed.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; diff --git a/common/java/apps/net/i2p/client/streaming/SchedulerClosing.java b/common/java/apps/net/i2p/client/streaming/impl/SchedulerClosing.java similarity index 98% rename from common/java/apps/net/i2p/client/streaming/SchedulerClosing.java rename to common/java/apps/net/i2p/client/streaming/impl/SchedulerClosing.java index b8754f7..424c9a4 100644 --- a/common/java/apps/net/i2p/client/streaming/SchedulerClosing.java +++ b/common/java/apps/net/i2p/client/streaming/impl/SchedulerClosing.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; import net.i2p.util.Log; diff --git a/common/java/apps/net/i2p/client/streaming/SchedulerConnectedBulk.java b/common/java/apps/net/i2p/client/streaming/impl/SchedulerConnectedBulk.java similarity index 98% rename from common/java/apps/net/i2p/client/streaming/SchedulerConnectedBulk.java rename to common/java/apps/net/i2p/client/streaming/impl/SchedulerConnectedBulk.java index 77ec055..228b8a7 100644 --- a/common/java/apps/net/i2p/client/streaming/SchedulerConnectedBulk.java +++ b/common/java/apps/net/i2p/client/streaming/impl/SchedulerConnectedBulk.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; diff --git a/common/java/apps/net/i2p/client/streaming/SchedulerConnecting.java b/common/java/apps/net/i2p/client/streaming/impl/SchedulerConnecting.java similarity index 98% rename from common/java/apps/net/i2p/client/streaming/SchedulerConnecting.java rename to common/java/apps/net/i2p/client/streaming/impl/SchedulerConnecting.java index 1df4ecd..e856442 100644 --- a/common/java/apps/net/i2p/client/streaming/SchedulerConnecting.java +++ b/common/java/apps/net/i2p/client/streaming/impl/SchedulerConnecting.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; import net.i2p.util.Log; diff --git a/common/java/apps/net/i2p/client/streaming/SchedulerDead.java b/common/java/apps/net/i2p/client/streaming/impl/SchedulerDead.java similarity index 96% rename from common/java/apps/net/i2p/client/streaming/SchedulerDead.java rename to common/java/apps/net/i2p/client/streaming/impl/SchedulerDead.java index 9aa70ea..04c4205 100644 --- a/common/java/apps/net/i2p/client/streaming/SchedulerDead.java +++ b/common/java/apps/net/i2p/client/streaming/impl/SchedulerDead.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; diff --git a/common/java/apps/net/i2p/client/streaming/SchedulerHardDisconnected.java b/common/java/apps/net/i2p/client/streaming/impl/SchedulerHardDisconnected.java similarity index 97% rename from common/java/apps/net/i2p/client/streaming/SchedulerHardDisconnected.java rename to common/java/apps/net/i2p/client/streaming/impl/SchedulerHardDisconnected.java index 597e2c6..eaf682f 100644 --- a/common/java/apps/net/i2p/client/streaming/SchedulerHardDisconnected.java +++ b/common/java/apps/net/i2p/client/streaming/impl/SchedulerHardDisconnected.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; diff --git a/common/java/apps/net/i2p/client/streaming/SchedulerImpl.java b/common/java/apps/net/i2p/client/streaming/impl/SchedulerImpl.java similarity index 89% rename from common/java/apps/net/i2p/client/streaming/SchedulerImpl.java rename to common/java/apps/net/i2p/client/streaming/impl/SchedulerImpl.java index 5d042c9..7a5b635 100644 --- a/common/java/apps/net/i2p/client/streaming/SchedulerImpl.java +++ b/common/java/apps/net/i2p/client/streaming/impl/SchedulerImpl.java @@ -1,8 +1,7 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; import net.i2p.util.Log; -import net.i2p.util.SimpleScheduler; /** * Base scheduler diff --git a/common/java/apps/net/i2p/client/streaming/SchedulerPreconnect.java b/common/java/apps/net/i2p/client/streaming/impl/SchedulerPreconnect.java similarity index 97% rename from common/java/apps/net/i2p/client/streaming/SchedulerPreconnect.java rename to common/java/apps/net/i2p/client/streaming/impl/SchedulerPreconnect.java index fdbefe1..3f4855b 100644 --- a/common/java/apps/net/i2p/client/streaming/SchedulerPreconnect.java +++ b/common/java/apps/net/i2p/client/streaming/impl/SchedulerPreconnect.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; import net.i2p.util.Log; diff --git a/common/java/apps/net/i2p/client/streaming/SchedulerReceived.java b/common/java/apps/net/i2p/client/streaming/impl/SchedulerReceived.java similarity index 97% rename from common/java/apps/net/i2p/client/streaming/SchedulerReceived.java rename to common/java/apps/net/i2p/client/streaming/impl/SchedulerReceived.java index e44c68d..abe2cec 100644 --- a/common/java/apps/net/i2p/client/streaming/SchedulerReceived.java +++ b/common/java/apps/net/i2p/client/streaming/impl/SchedulerReceived.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PAppContext; import net.i2p.util.Log; diff --git a/common/java/apps/net/i2p/client/streaming/StandardServerSocket.java b/common/java/apps/net/i2p/client/streaming/impl/StandardServerSocket.java similarity index 92% rename from common/java/apps/net/i2p/client/streaming/StandardServerSocket.java rename to common/java/apps/net/i2p/client/streaming/impl/StandardServerSocket.java index 1429fa6..3bcf53a 100644 --- a/common/java/apps/net/i2p/client/streaming/StandardServerSocket.java +++ b/common/java/apps/net/i2p/client/streaming/impl/StandardServerSocket.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.IOException; import java.net.InetAddress; @@ -9,6 +9,7 @@ import java.nio.channels.ServerSocketChannel; import net.i2p.I2PException; +import net.i2p.client.streaming.I2PSocket; /** * Bridge to I2PServerSocket. @@ -43,8 +44,9 @@ public Socket accept() throws IOException { throw new IOException("No socket"); return new StandardSocket(sock); } catch (I2PException i2pe) { - // fixme in 1.6 change to cause - throw new IOException(i2pe.toString()); + IOException ioe = new IOException("accept fail"); + ioe.initCause(i2pe); + throw ioe; } } @@ -135,7 +137,7 @@ public boolean isBound() { @Override public boolean isClosed() { - return ((I2PSocketManagerFull)_socket.getManager()).getConnectionManager().getAllowIncomingConnections(); + return !((I2PSocketManagerFull)_socket.getManager()).getConnectionManager().getAllowIncomingConnections(); } /** diff --git a/common/java/apps/net/i2p/client/streaming/StandardSocket.java b/common/java/apps/net/i2p/client/streaming/impl/StandardSocket.java similarity index 98% rename from common/java/apps/net/i2p/client/streaming/StandardSocket.java rename to common/java/apps/net/i2p/client/streaming/impl/StandardSocket.java index 30de826..549df28 100644 --- a/common/java/apps/net/i2p/client/streaming/StandardSocket.java +++ b/common/java/apps/net/i2p/client/streaming/impl/StandardSocket.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.io.IOException; import java.io.InputStream; @@ -9,6 +9,9 @@ import java.net.SocketException; import java.nio.channels.SocketChannel; +import net.i2p.client.streaming.I2PSocket; +import net.i2p.client.streaming.I2PSocketOptions; + /** * Bridge to I2PSocket. * diff --git a/common/java/apps/net/i2p/client/streaming/TCBShare.java b/common/java/apps/net/i2p/client/streaming/impl/TCBShare.java similarity index 98% rename from common/java/apps/net/i2p/client/streaming/TCBShare.java rename to common/java/apps/net/i2p/client/streaming/impl/TCBShare.java index 67d30f5..393d3da 100644 --- a/common/java/apps/net/i2p/client/streaming/TCBShare.java +++ b/common/java/apps/net/i2p/client/streaming/impl/TCBShare.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import java.util.Iterator; import java.util.Map; @@ -6,12 +6,11 @@ import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; +import static net.i2p.client.streaming.impl.I2PSocketOptionsImpl.getDouble; import net.i2p.data.Destination; import net.i2p.util.Log; import net.i2p.util.SimpleTimer2; -import static net.i2p.client.streaming.I2PSocketOptionsImpl.getDouble; - /** * Share important TCP Control Block parameters across Connections * to the same remote peer. diff --git a/common/java/apps/net/i2p/client/streaming/TaskScheduler.java b/common/java/apps/net/i2p/client/streaming/impl/TaskScheduler.java similarity index 93% rename from common/java/apps/net/i2p/client/streaming/TaskScheduler.java rename to common/java/apps/net/i2p/client/streaming/impl/TaskScheduler.java index c998c84..7f515f4 100644 --- a/common/java/apps/net/i2p/client/streaming/TaskScheduler.java +++ b/common/java/apps/net/i2p/client/streaming/impl/TaskScheduler.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; /** * Coordinates what we do 'next'. The scheduler used by a connection is diff --git a/common/java/apps/net/i2p/client/streaming/TooManyStreamsException.java b/common/java/apps/net/i2p/client/streaming/impl/TooManyStreamsException.java similarity index 91% rename from common/java/apps/net/i2p/client/streaming/TooManyStreamsException.java rename to common/java/apps/net/i2p/client/streaming/impl/TooManyStreamsException.java index d53ef08..7ccb7ff 100644 --- a/common/java/apps/net/i2p/client/streaming/TooManyStreamsException.java +++ b/common/java/apps/net/i2p/client/streaming/impl/TooManyStreamsException.java @@ -1,4 +1,4 @@ -package net.i2p.client.streaming; +package net.i2p.client.streaming.impl; import net.i2p.I2PException; diff --git a/common/java/apps/net/i2p/client/streaming/impl/package.html b/common/java/apps/net/i2p/client/streaming/impl/package.html new file mode 100644 index 0000000..d629a68 --- /dev/null +++ b/common/java/apps/net/i2p/client/streaming/impl/package.html @@ -0,0 +1,19 @@ + +

Implementation of a TCP-like (reliable, authenticated, in order) set of sockets for +communicating over the IP-like (unreliable, unauthenticated, unordered) I2P +messages. + +This is the streaming implementation (moved for ticket #1135 to here). +For the API (which you probably want), see ministreaming. +Clients should not need to access anything in this package directly. +Use the interfaces and factory in net.i2p.streaming. + +Note that this class is split across two jars, streaming.jar and ministreaming.jar. +The interfaces and some code are in ministreaming.jar, but the +real work gets done in streaming.jar. Clients must have both jars +in their classpath. + +Most clients will require (only) streaming.jar, ministreaming.jar, and i2p.jar +in their classpath to communicate with the router. +

+ diff --git a/common/java/apps/net/i2p/client/streaming/package.html b/common/java/apps/net/i2p/client/streaming/package.html index 3e2ce48..b1168f6 100644 --- a/common/java/apps/net/i2p/client/streaming/package.html +++ b/common/java/apps/net/i2p/client/streaming/package.html @@ -1,9 +1,9 @@ -

Implements a TCP-like (reliable, authenticated, in order) set of sockets for +

API, interfaces, and factory for a TCP-like (reliable, authenticated, in order) set of sockets for communicating over the IP-like (unreliable, unauthenticated, unordered) I2P messages. Note that this class is split across two jars, streaming.jar and ministreaming.jar. -The interfaces and some very old code are in ministreaming.jar, but the +The interfaces are in ministreaming.jar, but the real work gets done in streaming.jar. Clients must have both jars in their classpath. Most clients will require (only) streaming.jar, ministreaming.jar, and i2p.jar @@ -23,7 +23,10 @@ application wants to create a new stream to a peer, it should do so with the appropriate {@link net.i2p.client.streaming.I2PSocketManager#connect} call.

-

There is a simple pair of demo applications available as well - +

This package also contains the I2PSocketEepGet utility, which is an HTTP client +that uses an existing I2PSocketManager.

+ +

There is a simple pair of demo applications available in the test directory - net.i2p.client.streaming.StreamSinkServer listens to a destination and dumps the data from all sockets it accepts to individual files, while net.i2p.client.streaming.StreamSinkClient connects to a particular destination diff --git a/common/java/apps/org/klomp/snark/I2PSnarkUtil.java b/common/java/apps/org/klomp/snark/I2PSnarkUtil.java index 13c445d..d103853 100644 --- a/common/java/apps/org/klomp/snark/I2PSnarkUtil.java +++ b/common/java/apps/org/klomp/snark/I2PSnarkUtil.java @@ -2,7 +2,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.InputStream; import java.io.IOException; import java.util.Collections; import java.util.HashMap; @@ -35,8 +34,6 @@ import org.klomp.snark.dht.DHT; import org.klomp.snark.dht.KRPC; -import org.klomp.snark.dht.NodeInfo; -import org.klomp.snark.dht.CustomQueryHandler; /** * I2P specific helpers for I2PSnark @@ -69,10 +66,6 @@ public class I2PSnarkUtil { private boolean _areFilesPublic; private List _openTrackers; private DHT _dht; - private InputStream _myPrivateKeyStream; - private NodeInfo _myNodeInfo; - private CustomQueryHandler _customQueryHandler; - private Runnable _dhtInitCallback; private static final int EEPGET_CONNECT_TIMEOUT = 45*1000; private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000; @@ -83,6 +76,7 @@ public class I2PSnarkUtil { public static final int MAX_CONNECTIONS = 16; // per torrent public static final String PROP_MAX_BW = "i2cp.outboundBytesPerSecond"; public static final boolean DEFAULT_USE_DHT = true; + public static final String EEPGET_USER_AGENT = "I2PSnark"; public I2PSnarkUtil(I2PAppContext ctx) { this(ctx, "i2psnark"); @@ -187,19 +181,6 @@ public void setStartupDelay(int minutes) { _configured = true; } - public void setDHTNode(InputStream privateKeyStream, NodeInfo nodeInfo) { - _myPrivateKeyStream = privateKeyStream; - _myNodeInfo = nodeInfo; - } - - public void setDHTCustomQueryHandler(CustomQueryHandler handler) { - _customQueryHandler = handler; - } - - public void setDHTInitCallback(Runnable callback) { - _dhtInitCallback = callback; - } - public String getI2CPHost() { return _i2cpHost; } public int getI2CPPort() { return _i2cpPort; } public Map getI2CPOptions() { return _opts; } @@ -272,19 +253,13 @@ synchronized public boolean connect() { opts.setProperty("i2p.streaming.enforceProtocol", "true"); if (opts.getProperty("i2p.streaming.disableRejectLogging") == null) opts.setProperty("i2p.streaming.disableRejectLogging", "true"); - if (_myPrivateKeyStream != null) { - _manager = I2PSocketManagerFactory.createManager(_myPrivateKeyStream, _i2cpHost, _i2cpPort, opts); - } else { - _manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts); - } + if (opts.getProperty("i2p.streaming.answerPings") == null) + opts.setProperty("i2p.streaming.answerPings", "false"); + _manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts); _connecting = false; } - if (_shouldUseDHT && _manager != null && _dht == null) { - _dht = new KRPC(_context, _baseName, _manager.getSession(), _myNodeInfo, _customQueryHandler); - if (_dhtInitCallback != null) { - _dhtInitCallback.run(); - } - } + if (_shouldUseDHT && _manager != null && _dht == null) + _dht = new KRPC(_context, _baseName, _manager.getSession()); return (_manager != null); } @@ -419,6 +394,7 @@ public File get(String url, boolean rewrite, int retries) { } } EepGet get = new I2PSocketEepGet(_context, _manager, retries, out.getAbsolutePath(), fetchURL); + get.addHeader("User-Agent", EEPGET_USER_AGENT); if (get.fetch(timeout)) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Fetch successful [" + url + "]: size=" + out.length()); @@ -460,6 +436,7 @@ public byte[] get(String url, boolean rewrite, int retries, int initialSize, int } ByteArrayOutputStream out = new ByteArrayOutputStream(initialSize); EepGet get = new I2PSocketEepGet(_context, _manager, retries, -1, maxSize, null, out, fetchURL); + get.addHeader("User-Agent", EEPGET_USER_AGENT); if (get.fetch(timeout)) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Fetch successful [" + url + "]: size=" + out.size()); @@ -623,10 +600,7 @@ public boolean shouldUseOpenTrackers() { public synchronized void setUseDHT(boolean yes) { _shouldUseDHT = yes; if (yes && _manager != null && _dht == null) { - _dht = new KRPC(_context, _baseName, _manager.getSession(), _myNodeInfo, _customQueryHandler); - if (_dhtInitCallback != null) { - _dhtInitCallback.run(); - } + _dht = new KRPC(_context, _baseName, _manager.getSession()); } else if (!yes && _dht != null) { _dht.stop(); _dht = null; diff --git a/common/java/apps/org/klomp/snark/MetaInfo.java b/common/java/apps/org/klomp/snark/MetaInfo.java index 8ec5442..97deb73 100644 --- a/common/java/apps/org/klomp/snark/MetaInfo.java +++ b/common/java/apps/org/klomp/snark/MetaInfo.java @@ -156,11 +156,11 @@ public MetaInfo(Map m) throws InvalidBEncodingException if (val == null) { this.announce_list = null; } else { - this.announce_list = new ArrayList(); + this.announce_list = new ArrayList>(); List bl1 = val.getList(); for (BEValue bev : bl1) { List bl2 = bev.getList(); - List sl2 = new ArrayList(); + List sl2 = new ArrayList(); for (BEValue bev2 : bl2) { sl2.add(bev2.getString()); } @@ -259,9 +259,9 @@ public MetaInfo(Map m) throws InvalidBEncodingException if (size == 0) throw new InvalidBEncodingException("zero size files list"); - List> m_files = new ArrayList(size); - List> m_files_utf8 = new ArrayList(size); - List m_lengths = new ArrayList(size); + List> m_files = new ArrayList>(size); + List> m_files_utf8 = new ArrayList>(size); + List m_lengths = new ArrayList(size); long l = 0; for (int i = 0; i < list.size(); i++) { @@ -287,7 +287,7 @@ public MetaInfo(Map m) throws InvalidBEncodingException if (path_length == 0) throw new InvalidBEncodingException("zero size file path list"); - List file = new ArrayList(path_length); + List file = new ArrayList(path_length); Iterator it = path_list.iterator(); while (it.hasNext()) { String s = it.next().getString(); @@ -310,7 +310,7 @@ public MetaInfo(Map m) throws InvalidBEncodingException path_list = val.getList(); path_length = path_list.size(); if (path_length > 0) { - file = new ArrayList(path_length); + file = new ArrayList(path_length); it = path_list.iterator(); while (it.hasNext()) file.add(it.next().getString()); @@ -573,10 +573,10 @@ public String toString() */ public MetaInfo reannounce(String announce) throws InvalidBEncodingException { - Map m = new HashMap(); + Map m = new HashMap(); if (announce != null) m.put("announce", new BEValue(DataHelper.getUTF8(announce))); - Map info = createInfoMap(); + Map info = createInfoMap(); m.put("info", new BEValue(info)); return new MetaInfo(m); } @@ -586,12 +586,12 @@ public MetaInfo reannounce(String announce) throws InvalidBEncodingException */ public synchronized byte[] getTorrentData() { - Map m = new HashMap(); + Map m = new HashMap(); if (announce != null) m.put("announce", announce); if (announce_list != null) m.put("announce-list", announce_list); - Map info = createInfoMap(); + Map info = createInfoMap(); m.put("info", info); // don't save this locally, we should only do this once return BEncoder.bencode(m); @@ -615,31 +615,42 @@ private Map createInfoMap() if (_log.shouldLog(Log.WARN)) _log.warn("Creating new infomap", new Exception()); // otherwise we must create it - Map info = new HashMap(); - info.put("name", name); + Map info = new HashMap(); + info.put("name", new BEValue(DataHelper.getUTF8(name))); if (name_utf8 != null) - info.put("name.utf-8", name_utf8); + info.put("name.utf-8", new BEValue(DataHelper.getUTF8(name_utf8))); // BEP 27 if (privateTorrent) - info.put("private", "1"); + info.put("private", new BEValue(DataHelper.getUTF8("1"))); - info.put("piece length", Integer.valueOf(piece_length)); - info.put("pieces", piece_hashes); + info.put("piece length", new BEValue(Integer.valueOf(piece_length))); + info.put("pieces", new BEValue(piece_hashes)); if (files == null) - info.put("length", Long.valueOf(length)); + info.put("length", new BEValue(Long.valueOf(length))); else { - List l = new ArrayList(); + List l = new ArrayList(); for (int i = 0; i < files.size(); i++) { - Map file = new HashMap(); - file.put("path", files.get(i)); - if ( (files_utf8 != null) && (files_utf8.size() > i) ) - file.put("path.utf-8", files_utf8.get(i)); - file.put("length", lengths.get(i)); - l.add(file); + Map file = new HashMap(); + List fi = files.get(i); + List befiles = new ArrayList(fi.size()); + for (int j = 0; j < fi.size(); j++) { + befiles.add(new BEValue(DataHelper.getUTF8(fi.get(j)))); + } + file.put("path", new BEValue(befiles)); + if ( (files_utf8 != null) && (files_utf8.size() > i) ) { + List fiu = files_utf8.get(i); + List beufiles = new ArrayList(fiu.size()); + for (int j = 0; j < fiu.size(); j++) { + beufiles.add(new BEValue(DataHelper.getUTF8(fiu.get(j)))); + } + file.put("path.utf-8", new BEValue(beufiles)); + } + file.put("length", new BEValue(lengths.get(i))); + l.add(new BEValue(file)); } - info.put("files", l); + info.put("files", new BEValue(l)); } // TODO if we add the ability for other keys in the first constructor diff --git a/common/java/apps/org/klomp/snark/PartialPiece.java b/common/java/apps/org/klomp/snark/PartialPiece.java index bfeb49b..bcf0aef 100644 --- a/common/java/apps/org/klomp/snark/PartialPiece.java +++ b/common/java/apps/org/klomp/snark/PartialPiece.java @@ -31,7 +31,7 @@ * * @since 0.8.2 */ -class PartialPiece implements Comparable { +class PartialPiece implements Comparable { // we store the piece so we can use it in compareTo() private final Piece piece; @@ -295,8 +295,7 @@ private void locked_release() { * then rarest first, * then highest downloaded first */ - public int compareTo(Object o) throws ClassCastException { - PartialPiece opp = (PartialPiece)o; + public int compareTo(PartialPiece opp) { int d = this.piece.compareTo(opp.piece); if (d != 0) return d; diff --git a/common/java/apps/org/klomp/snark/Peer.java b/common/java/apps/org/klomp/snark/Peer.java index 821c39d..683b3f2 100644 --- a/common/java/apps/org/klomp/snark/Peer.java +++ b/common/java/apps/org/klomp/snark/Peer.java @@ -39,7 +39,7 @@ import org.klomp.snark.bencode.BEValue; -public class Peer implements Comparable +public class Peer implements Comparable { private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(Peer.class); // Identifying property, the peer id of the other side. @@ -194,7 +194,7 @@ public boolean equals(Object o) * Compares the PeerIDs. * @deprecated unused? */ - public int compareTo(Object o) + public int compareTo(Peer o) { Peer p = (Peer)o; int rv = peerID.compareTo(p.peerID); diff --git a/common/java/apps/org/klomp/snark/PeerCoordinator.java b/common/java/apps/org/klomp/snark/PeerCoordinator.java index 80c10b1..c436516 100644 --- a/common/java/apps/org/klomp/snark/PeerCoordinator.java +++ b/common/java/apps/org/klomp/snark/PeerCoordinator.java @@ -134,9 +134,6 @@ class PeerCoordinator implements PeerListener private final CoordinatorListener listener; private final I2PSnarkUtil _util; private final Random _random; - - /** Maintain connections with peers even after the torrent has finished. */ - private boolean persistent = false; /** * @param metainfo null if in magnet mode @@ -380,7 +377,7 @@ public boolean needPeers() public boolean needOutboundPeers() { //return wantedBytes != 0 && needPeers(); // minus two to make it a little easier for new peers to get in on large swarms - return (wantedBytes != 0 || persistent) && + return wantedBytes != 0 && !halted && peers.size() < getMaxConnections() - 2 && (storage == null || !storage.isChecking()); @@ -410,14 +407,6 @@ private int getMaxConnections() { //return (max + 2) / 3; } - public void setPersistent(boolean isPersistent) { - persistent = isPersistent; - } - - public boolean getPersistent() { - return persistent; - } - public boolean halted() { return halted; } public void halt() @@ -699,7 +688,7 @@ public boolean gotBitField(Peer peer, BitField bitfield) } } } - return rv || (wantedBytes == 0 && persistent); + return rv; } /** @@ -1042,10 +1031,8 @@ public boolean gotPiece(Peer peer, PartialPiece pp) } if (done) { - if (!persistent) { - for (Peer p : toDisconnect) { - p.disconnect(true); - } + for (Peer p : toDisconnect) { + p.disconnect(true); } // put msg on the console if partial, since Storage won't do it diff --git a/common/java/apps/org/klomp/snark/PeerID.java b/common/java/apps/org/klomp/snark/PeerID.java index 4cf0f33..16dc922 100644 --- a/common/java/apps/org/klomp/snark/PeerID.java +++ b/common/java/apps/org/klomp/snark/PeerID.java @@ -42,7 +42,7 @@ * and the PeerID is not required. * Equality is now determined solely by the dest hash. */ -public class PeerID implements Comparable +class PeerID implements Comparable { private byte[] id; private Destination address; @@ -76,15 +76,15 @@ public PeerID(BDecoder be) * Creates a PeerID from a Map containing BEncoded peer id, ip and * port. */ - public PeerID(Map m) + public PeerID(Map m) throws InvalidBEncodingException, UnknownHostException { - BEValue bevalue = (BEValue)m.get("peer id"); + BEValue bevalue = m.get("peer id"); if (bevalue == null) throw new InvalidBEncodingException("peer id missing"); id = bevalue.getBytes(); - bevalue = (BEValue)m.get("ip"); + bevalue = m.get("ip"); if (bevalue == null) throw new InvalidBEncodingException("ip missing"); address = I2PSnarkUtil.getDestinationFromBase64(bevalue.getString()); @@ -195,10 +195,8 @@ public boolean equals(Object o) * Compares port, address and id. * @deprecated unused? and will NPE now that address can be null? */ - public int compareTo(Object o) + public int compareTo(PeerID pid) { - PeerID pid = (PeerID)o; - int result = port - pid.port; if (result != 0) return result; diff --git a/common/java/apps/org/klomp/snark/Piece.java b/common/java/apps/org/klomp/snark/Piece.java index 582505b..202afe4 100644 --- a/common/java/apps/org/klomp/snark/Piece.java +++ b/common/java/apps/org/klomp/snark/Piece.java @@ -7,7 +7,7 @@ * This class is used solely by PeerCoordinator. * Caller must synchronize on many of these methods. */ -class Piece implements Comparable { +class Piece implements Comparable { private final int id; private final Set peers; @@ -26,11 +26,11 @@ public Piece(int id) { * Highest priority first, * then rarest first */ - public int compareTo(Object o) throws ClassCastException { - int pdiff = ((Piece)o).priority - this.priority; // reverse + public int compareTo(Piece op) { + int pdiff = op.priority - this.priority; // reverse if (pdiff != 0) return pdiff; - return this.peers.size() - ((Piece)o).peers.size(); + return this.peers.size() - op.peers.size(); } @Override diff --git a/common/java/apps/org/klomp/snark/Snark.java b/common/java/apps/org/klomp/snark/Snark.java index 3358d0f..ba4dfc2 100644 --- a/common/java/apps/org/klomp/snark/Snark.java +++ b/common/java/apps/org/klomp/snark/Snark.java @@ -663,10 +663,6 @@ public Storage getStorage() { return storage; } - public String getDataDir() { - return rootDataDir; - } - /** * @since 0.8.4 */ @@ -1203,20 +1199,6 @@ public void storageCompleted(Storage storage) completeListener.torrentComplete(this); } - public void setPersistent(boolean isPersistent) { - if (coordinator != null) { - coordinator.setPersistent(isPersistent); - } - } - - public boolean getPersistent() { - if (coordinator != null) { - return coordinator.getPersistent(); - } - - return false; - } - public void setWantedPieces(Storage storage) { coordinator.setWantedPieces(); diff --git a/common/java/apps/org/klomp/snark/SnarkManager.java b/common/java/apps/org/klomp/snark/SnarkManager.java index 71fc543..3fe2b2a 100644 --- a/common/java/apps/org/klomp/snark/SnarkManager.java +++ b/common/java/apps/org/klomp/snark/SnarkManager.java @@ -112,7 +112,7 @@ public class SnarkManager implements CompleteListener { * "name", "announceURL=websiteURL" pairs * '=' in announceURL must be escaped as , */ - public static final String DEFAULT_TRACKERS[] = { + private static final String DEFAULT_TRACKERS[] = { // "Postman", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php=http://tracker.postman.i2p/" // , "eBook", "http://E71FRom6PZNEqTN2Lr8P-sr23b7HJVC32KoGnVQjaX6zJiXwhJy2HsXob36Qmj81TYFZdewFZa9mSJ533UZgGyQkXo2ahctg82JKYZfDe5uDxAn1E9YPjxZCWJaFJh0S~UwSs~9AZ7UcauSJIoNtpxrtbmRNVFLqnkEDdLZi26TeucfOmiFmIWnVblLniWv3tG1boE9Abd-6j3FmYVrRucYuepAILYt6katmVNOk6sXmno1Eynrp~~MBuFq0Ko6~jsc2E2CRVYXDhGHEMdt-j6JUz5D7S2RIVzDRqQyAZLKJ7OdQDmI31przzmne1vOqqqLC~1xUumZVIvF~yOeJUGNjJ1Vx0J8i2BQIusn1pQJ6UCB~ZtZZLQtEb8EPVCfpeRi2ri1M5CyOuxN0V5ekmPHrYIBNevuTCRC26NP7ZS5VDgx1~NaC3A-CzJAE6f1QXi0wMI9aywNG5KGzOPifcsih8eyGyytvgLtrZtV7ykzYpPCS-rDfITncpn5hliPUAAAA.i2p/pub/bt/announce.php=http://de-ebook-archiv.i2p/pub/bt/" // , "Gaytorrents", "http://uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA.i2p/announce.php=http://gaytorrents.i2p/" @@ -129,6 +129,17 @@ public class SnarkManager implements CompleteListener { // ,"Exotrack", "http://blbgywsjubw3d2zih2giokakhe3o2cko7jtte4risb3hohbcoyva.b32.i2p/announce.php=http://exotrack.i2p/" }; + public static final Set DEFAULT_TRACKER_ANNOUNCES; + + static { + Set ann = new HashSet(); + for (int i = 1; i < DEFAULT_TRACKERS.length; i += 2) { + String urls[] = DEFAULT_TRACKERS[i].split("=", 2); + ann.add(urls[0]); + } + DEFAULT_TRACKER_ANNOUNCES = Collections.unmodifiableSet(ann); + } + /** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */ public static final String PROP_TRACKERS = "i2psnark.trackers"; @@ -167,17 +178,11 @@ public SnarkManager(I2PAppContext ctx, String ctxPath, String ctxName) { * for i2cp host/port or i2psnark.dir */ public void start() { - start(true); - } - - public void start(boolean runDirMonitor) { _running = true; _peerCoordinatorSet = new PeerCoordinatorSet(); _connectionAcceptor = new ConnectionAcceptor(_util, _peerCoordinatorSet); - if (runDirMonitor) { - _monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true); - _monitor.start(); - } + _monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true); + _monitor.start(); // only if default instance if ("i2psnark".equals(_contextName)) // delay until UpdateManager is there @@ -430,7 +435,7 @@ private void updateConfig() { String i2cpHost = _config.getProperty(PROP_I2CP_HOST); int i2cpPort = getInt(PROP_I2CP_PORT, 7654); String opts = _config.getProperty(PROP_I2CP_OPTS); - Map i2cpOpts = new HashMap(); + Map i2cpOpts = new HashMap(); if (opts != null) { StringTokenizer tok = new StringTokenizer(opts, " "); while (tok.hasMoreTokens()) { @@ -895,10 +900,6 @@ public Snark getTorrentByInfoHash(byte[] infohash) { * @throws RuntimeException via Snark.fatal() */ private void addTorrent(String filename, boolean dontAutoStart) { - addTorrent(filename, dontAutoStart, this, getDataDir().getPath()); - } - - private void addTorrent(String filename, boolean dontAutoStart, CompleteListener listener, String dataDir) { if ((!dontAutoStart) && !_util.connected()) { addMessage(_("Connecting to I2P")); boolean ok = _util.connect(); @@ -915,6 +916,7 @@ private void addTorrent(String filename, boolean dontAutoStart, CompleteListener addMessage(_("Error: Could not add the torrent {0}", filename) + ": " + ioe); return; } + File dataDir = getDataDir(); Snark torrent = null; synchronized (_snarks) { torrent = _snarks.get(filename); @@ -978,9 +980,9 @@ private void addTorrent(String filename, boolean dontAutoStart, CompleteListener } else { // TODO load saved closest DHT nodes and pass to the Snark ? // This may take a LONG time - torrent = new Snark(_util, filename, null, -1, null, null, listener, + torrent = new Snark(_util, filename, null, -1, null, null, this, _peerCoordinatorSet, _connectionAcceptor, - false, dataDir); + false, dataDir.getPath()); loadSavedFilePriorities(torrent); synchronized (_snarks) { _snarks.put(filename, torrent); @@ -1040,21 +1042,15 @@ public void addMagnet(String name, byte[] ih, String trackerURL, boolean updateS * to save it across restarts, in case we don't get * the metadata before shutdown? * @param listener to intercept callbacks, should pass through to this - * @param dataDir directory to store the downloaded files * @return the new Snark or null on failure * @throws RuntimeException via Snark.fatal() * @since 0.9.4 */ public Snark addMagnet(String name, byte[] ih, String trackerURL, boolean updateStatus, boolean autoStart, CompleteListener listener) { - return addMagnet(name, ih, trackerURL, updateStatus, shouldAutoStart(), this, getDataDir().getPath()); - } - - public Snark addMagnet(String name, byte[] ih, String trackerURL, boolean updateStatus, - boolean autoStart, CompleteListener listener, String dataDir) { Snark torrent = new Snark(_util, name, ih, trackerURL, listener, _peerCoordinatorSet, _connectionAcceptor, - false, dataDir); + false, getDataDir().getPath()); synchronized (_snarks) { Snark snark = getTorrentByInfoHash(ih); @@ -1137,10 +1133,6 @@ public void addDownloader(Snark torrent) { * @since 0.8.4 */ public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, boolean dontAutoStart) throws IOException { - addTorrent(metainfo, bitfield, filename, dontAutoStart, this, getDataDir().getPath()); - } - - public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, boolean dontAutoStart, CompleteListener listener, String dataDir) throws IOException { // prevent interference by DirMonitor synchronized (_snarks) { Snark snark = getTorrentByInfoHash(metainfo.getInfoHash()); @@ -1153,7 +1145,7 @@ public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, bo try { locked_writeMetaInfo(metainfo, filename, areFilesPublic()); // hold the lock for a long time - addTorrent(filename, dontAutoStart, listener, dataDir); + addTorrent(filename, dontAutoStart); } catch (IOException ioe) { addMessage(_("Failed to copy torrent file to {0}", filename)); _log.error("Failed to write torrent file", ioe); @@ -1201,7 +1193,7 @@ public void copyAndAddTorrent(File fromfile, String filename) throws IOException private static void locked_writeMetaInfo(MetaInfo metainfo, String filename, boolean areFilesPublic) throws IOException { File file = new File(filename); if (file.exists()) - return; + throw new IOException("Cannot overwrite an existing .torrent file: " + file.getPath()); OutputStream out = null; try { if (areFilesPublic) @@ -1585,10 +1577,6 @@ public void updateStatus(Snark snark) { * @since 0.8.4 */ public String gotMetaInfo(Snark snark) { - return gotMetaInfo(snark, getDataDir().getPath()); - } - - public String gotMetaInfo(Snark snark, String dataDir) { MetaInfo meta = snark.getMetaInfo(); Storage storage = snark.getStorage(); if (meta != null && storage != null) { @@ -1603,7 +1591,7 @@ public String gotMetaInfo(Snark snark, String dataDir) { String name = storage.getBaseName(); try { // _snarks must use canonical - name = (new File(dataDir, storage.getBaseName() + ".torrent")).getCanonicalPath(); + name = (new File(getDataDir(), storage.getBaseName() + ".torrent")).getCanonicalPath(); // put the announce URL in the file String announce = snark.getTrackerURL(); if (announce != null) @@ -1675,7 +1663,7 @@ private void addMagnets() { */ private void monitorTorrents(File dir) { String fileNames[] = dir.list(TorrentFilenameFilter.instance()); - List foundNames = new ArrayList(0); + List foundNames = new ArrayList(0); if (fileNames != null) { for (int i = 0; i < fileNames.length; i++) { try { @@ -1761,7 +1749,7 @@ public Collection getTrackers() { * @since 0.9.1 */ public List getSortedTrackers() { - List rv = new ArrayList(_trackerMap.values()); + List rv = new ArrayList(_trackerMap.values()); Collections.sort(rv, new IgnoreCaseComparator()); return rv; } diff --git a/common/java/apps/org/klomp/snark/StorageListener.java b/common/java/apps/org/klomp/snark/StorageListener.java index d76a497..d484907 100644 --- a/common/java/apps/org/klomp/snark/StorageListener.java +++ b/common/java/apps/org/klomp/snark/StorageListener.java @@ -23,7 +23,7 @@ /** * Callback used when Storage changes. */ -public interface StorageListener +interface StorageListener { /** * Called when the storage creates a new file of a given length. diff --git a/common/java/apps/org/klomp/snark/TrackerClient.java b/common/java/apps/org/klomp/snark/TrackerClient.java index 5ba1c20..be854a4 100644 --- a/common/java/apps/org/klomp/snark/TrackerClient.java +++ b/common/java/apps/org/klomp/snark/TrackerClient.java @@ -84,7 +84,6 @@ public class TrackerClient implements Runnable { private final static long MIN_TRACKER_ANNOUNCE_INTERVAL = 15*60*1000; private final static long MIN_DHT_ANNOUNCE_INTERVAL = 10*60*1000; public static final int PORT = 6881; - public static final boolean DHT_ONLY = true; private final I2PSnarkUtil _util; private final MetaInfo meta; @@ -449,10 +448,6 @@ else if ((!runStarted) && _runCount < MAX_CONSEC_FAILS) * @return max peers seen */ private int getPeersFromTrackers(List trckrs) { - if (DHT_ONLY) { - return 0; - } - long left = coordinator.getLeft(); // -1 in magnet mode // First time we got a complete download? @@ -514,7 +509,7 @@ private int getPeersFromTrackers(List trckrs) { coordinator.getPeerCount() <= 0 && _util.getContext().clock().now() > _startedOn + 2*60*60*1000 && snark.getTotalLength() > 0 && - uploaded >= 2 * snark.getTotalLength()) { + uploaded >= snark.getTotalLength() * 3 / 2) { if (_log.shouldLog(Log.WARN)) _log.warn("Auto stopping " + snark.getBaseName()); snark.setAutoStoppable(false); @@ -593,10 +588,6 @@ private int getPeersFromTrackers(List trckrs) { * @return max peers seen */ private int getPeersFromPEX() { - if (DHT_ONLY) { - return 0; - } - // Get peers from PEX int rv = 0; if (coordinator.needOutboundPeers() && (meta == null || !meta.isPrivate()) && !stop) { diff --git a/common/java/apps/org/klomp/snark/bencode/BDecoder.java b/common/java/apps/org/klomp/snark/bencode/BDecoder.java index 2dc4e57..5384154 100644 --- a/common/java/apps/org/klomp/snark/bencode/BDecoder.java +++ b/common/java/apps/org/klomp/snark/bencode/BDecoder.java @@ -281,7 +281,7 @@ public BEValue bdecodeList() throws IOException + (char)c + "'"); indicator = 0; - List result = new ArrayList(); + List result = new ArrayList(); c = getNextIndicator(); while (c != 'e') { @@ -308,7 +308,7 @@ else if (c != 'd') + (char)c + "'"); indicator = 0; - Map result = new HashMap(); + Map result = new HashMap(); c = getNextIndicator(); while (c != 'e') { diff --git a/common/java/apps/org/klomp/snark/bencode/BEValue.java b/common/java/apps/org/klomp/snark/bencode/BEValue.java index 4cae288..0664065 100644 --- a/common/java/apps/org/klomp/snark/bencode/BEValue.java +++ b/common/java/apps/org/klomp/snark/bencode/BEValue.java @@ -49,12 +49,12 @@ public BEValue(Number value) this.value = value; } - public BEValue(List value) + public BEValue(List value) { this.value = value; } - public BEValue(Map value) + public BEValue(Map value) { this.value = value; } @@ -142,11 +142,12 @@ public long getLong() throws InvalidBEncodingException * succeeds when the BEValue is actually a List, otherwise it will * throw a InvalidBEncodingException. */ + @SuppressWarnings("unchecked") public List getList() throws InvalidBEncodingException { try { - return (List)value; + return (List)value; } catch (ClassCastException cce) { @@ -159,11 +160,12 @@ public List getList() throws InvalidBEncodingException * values. This operation only succeeds when the BEValue is actually * a Map, otherwise it will throw a InvalidBEncodingException. */ + @SuppressWarnings("unchecked") public Map getMap() throws InvalidBEncodingException { try { - return (Map)value; + return (Map)value; } catch (ClassCastException cce) { diff --git a/common/java/apps/org/klomp/snark/bencode/BEncoder.java b/common/java/apps/org/klomp/snark/bencode/BEncoder.java index 9584b0d..517b103 100644 --- a/common/java/apps/org/klomp/snark/bencode/BEncoder.java +++ b/common/java/apps/org/klomp/snark/bencode/BEncoder.java @@ -59,9 +59,9 @@ else if (o instanceof byte[]) else if (o instanceof Number) bencode((Number)o, out); else if (o instanceof List) - bencode((List)o, out); + bencode((List)o, out); else if (o instanceof Map) - bencode((Map)o, out); + bencode((Map)o, out); else if (o instanceof BEValue) bencode(((BEValue)o).getValue(), out); else @@ -110,7 +110,7 @@ public static void bencode(Number n, OutputStream out) throws IOException out.write('e'); } - public static byte[] bencode(List l) + public static byte[] bencode(List l) { try { @@ -124,10 +124,10 @@ public static byte[] bencode(List l) } } - public static void bencode(List l, OutputStream out) throws IOException + public static void bencode(List l, OutputStream out) throws IOException { out.write('l'); - Iterator it = l.iterator(); + Iterator it = l.iterator(); while (it.hasNext()) bencode(it.next(), out); out.write('e'); @@ -155,7 +155,7 @@ public static void bencode(byte[] bs, OutputStream out) throws IOException out.write(bs); } - public static byte[] bencode(Map m) + public static byte[] bencode(Map m) { try { @@ -169,23 +169,29 @@ public static byte[] bencode(Map m) } } - public static void bencode(Map m, OutputStream out) throws IOException + public static void bencode(Map m, OutputStream out) + throws IOException, IllegalArgumentException { out.write('d'); // Keys must be sorted. XXX - But is this the correct order? - Set s = m.keySet(); - List l = new ArrayList(s); + Set s = m.keySet(); + List l = new ArrayList(s.size()); + for (Object k : s) { + // Keys must be Strings. + if (String.class.isAssignableFrom(k.getClass())) + l.add((String) k); + else + throw new IllegalArgumentException("Cannot bencode map: contains non-String key of type " + k.getClass()); + } Collections.sort(l); Iterator it = l.iterator(); while(it.hasNext()) { - // Keys must be Strings. String key = it.next(); - Object value = m.get(key); bencode(key, out); - bencode(value, out); + bencode(m.get(key), out); } out.write('e'); diff --git a/common/java/apps/org/klomp/snark/dht/CustomQueryHandler.java b/common/java/apps/org/klomp/snark/dht/CustomQueryHandler.java deleted file mode 100644 index 548e711..0000000 --- a/common/java/apps/org/klomp/snark/dht/CustomQueryHandler.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.klomp.snark.dht; - -import java.util.Map; - -import org.klomp.snark.bencode.BEValue; - -/** - * Callback used when an unrecognized DHT query arrives. - */ -public interface CustomQueryHandler -{ - Map receiveQuery(String method, Map args); - void receiveResponse(Map args); -} diff --git a/common/java/apps/org/klomp/snark/dht/DHTNodes.java b/common/java/apps/org/klomp/snark/dht/DHTNodes.java index c3d553c..8207090 100644 --- a/common/java/apps/org/klomp/snark/dht/DHTNodes.java +++ b/common/java/apps/org/klomp/snark/dht/DHTNodes.java @@ -96,13 +96,8 @@ public NodeInfo putIfAbsent(NodeInfo nInfo) { } public NodeInfo remove(NID nid) { - NodeInfo ninfo = _nodeMap.get(nid); - if (ninfo != null && !ninfo.getPermanent()) { - _kad.remove(nid); - return _nodeMap.remove(nid); - } - - return null; + _kad.remove(nid); + return _nodeMap.remove(nid); } public Collection values() { @@ -160,7 +155,7 @@ public void timeReached() { int peerCount = 0; for (Iterator iter = DHTNodes.this.values().iterator(); iter.hasNext(); ) { NodeInfo peer = iter.next(); - if (peer.lastSeen() < now - _expireTime && !peer.getPermanent()) { + if (peer.lastSeen() < now - _expireTime) { iter.remove(); _kad.remove(peer.getNID()); } else { diff --git a/common/java/apps/org/klomp/snark/dht/KRPC.java b/common/java/apps/org/klomp/snark/dht/KRPC.java index d4782db..1ea2c99 100644 --- a/common/java/apps/org/klomp/snark/dht/KRPC.java +++ b/common/java/apps/org/klomp/snark/dht/KRPC.java @@ -106,8 +106,6 @@ public class KRPC implements I2PSessionMuxedListener, DHT { private final NID _myNID; /** 20 byte random id + 32 byte Hash + 2 byte port */ private final NodeInfo _myNodeInfo; - /** if not null, run this when receiving an unrecognized query */ - private CustomQueryHandler _customQueryHandler; /** unsigned dgrams */ private final int _rPort; /** signed dgrams */ @@ -162,15 +160,10 @@ public class KRPC implements I2PSessionMuxedListener, DHT { * @param baseName generally "i2psnark" */ public KRPC(I2PAppContext ctx, String baseName, I2PSession session) { - this(ctx, baseName, session, null, null); - } - - public KRPC(I2PAppContext ctx, String baseName, I2PSession session, NodeInfo myNodeInfo, CustomQueryHandler handler) { _context = ctx; _session = session; _log = ctx.logManager().getLog(KRPC.class); _tracker = new DHTTracker(ctx); - _customQueryHandler = handler; _sentQueries = new ConcurrentHashMap(); _outgoingTokens = new ConcurrentHashMap(); @@ -180,24 +173,17 @@ public KRPC(I2PAppContext ctx, String baseName, I2PSession session, NodeInfo myN // Construct my NodeInfo // Pick ports over a big range to marginally increase security // If we add a search DHT, adjust to stay out of each other's way - if (myNodeInfo == null) { - _qPort = TrackerClient.PORT + 10 + ctx.random().nextInt(65535 - 20 - TrackerClient.PORT); - if (SECURE_NID) { - _myNID = NodeInfo.generateNID(session.getMyDestination().calculateHash(), _qPort, _context.random()); - _myID = _myNID.getData(); - } else { - _myID = new byte[NID.HASH_LENGTH]; - ctx.random().nextBytes(_myID); - _myNID = new NID(_myID); - } - _myNodeInfo = new NodeInfo(_myNID, session.getMyDestination(), _qPort); - } else { - _qPort = myNodeInfo.getPort(); - _myNID = myNodeInfo.getNID(); + _qPort = TrackerClient.PORT + 10 + ctx.random().nextInt(65535 - 20 - TrackerClient.PORT); + _rPort = _qPort + 1; + if (SECURE_NID) { + _myNID = NodeInfo.generateNID(session.getMyDestination().calculateHash(), _qPort, _context.random()); _myID = _myNID.getData(); - _myNodeInfo = myNodeInfo; + } else { + _myID = new byte[NID.HASH_LENGTH]; + ctx.random().nextBytes(_myID); + _myNID = new NID(_myID); } - _rPort = _qPort + 1; + _myNodeInfo = new NodeInfo(_myNID, session.getMyDestination(), _qPort); _dhtFile = new File(ctx.getConfigDir(), baseName + DHT_FILE_SUFFIX); _backupDhtFile = baseName.equals("i2psnark") ? null : new File(ctx.getConfigDir(), "i2psnark" + DHT_FILE_SUFFIX); _knownNodes = new DHTNodes(ctx, _myNID); @@ -228,23 +214,6 @@ public int getRPort() { return _rPort; } - /** - * @return The NodeInfo object - */ - public NodeInfo getNodeInfo(Destination dest) { - if (dest == null) { - return _myNodeInfo; - } - - for (NodeInfo nInfo : _knownNodes.values()) { - if (dest.equals(nInfo.getDestination())) { - return nInfo; - } - } - - return null; - } - /** * Ping. We don't have a NID yet so the node is presumed * to be absent from our DHT. @@ -866,7 +835,7 @@ private boolean sendError(NodeInfo nInfo, MsgID msgID, int err, String msg) { * @param repliable true for all but announce * @return null on error */ - public ReplyWaiter sendQuery(NodeInfo nInfo, Map map, boolean repliable) { + private ReplyWaiter sendQuery(NodeInfo nInfo, Map map, boolean repliable) { if (nInfo.equals(_myNodeInfo)) throw new IllegalArgumentException("wtf don't send to ourselves"); if (_log.shouldLog(Log.DEBUG)) @@ -1153,17 +1122,8 @@ private void receiveQuery(MsgID msgID, Destination dest, int fromPort, String me byte[] token = args.get("token").getBytes(); receiveAnnouncePeer(msgID, ih, token); } else { - if (_customQueryHandler != null) { - Map resps = - _customQueryHandler.receiveQuery(method, args); - if (resps != null) { - Map map = new HashMap(); - map.put("r", resps); - sendResponse(nInfo, msgID, map); - } - } else if (_log.shouldLog(Log.WARN)) { + if (_log.shouldLog(Log.WARN)) _log.warn("Unknown query method rcvd: " + method); - } } } @@ -1202,7 +1162,7 @@ private NodeInfo heardFrom(NodeInfo nInfo) { * Package private for PersistDHT. * @return non-null nodeInfo from DB if present, otherwise the nInfo parameter is returned */ - public NodeInfo heardAbout(NodeInfo nInfo) { + NodeInfo heardAbout(NodeInfo nInfo) { // try to keep ourselves out of the DHT if (nInfo.equals(_myNodeInfo)) return _myNodeInfo; @@ -1367,8 +1327,6 @@ private void receiveResponse(ReplyWaiter waiter, Map response) List peers = values.getList(); List rlist = receivePeers(nInfo, peers); waiter.gotReply(REPLY_PEERS, rlist); - } else if (_customQueryHandler != null && response.size() > 1) { - _customQueryHandler.receiveResponse(response); } else { // a ping response or an announce peer response byte[] nid = response.get("id").getBytes(); diff --git a/common/java/apps/org/klomp/snark/dht/NodeInfo.java b/common/java/apps/org/klomp/snark/dht/NodeInfo.java index 0aa3782..c6fce8f 100644 --- a/common/java/apps/org/klomp/snark/dht/NodeInfo.java +++ b/common/java/apps/org/klomp/snark/dht/NodeInfo.java @@ -24,13 +24,12 @@ * @author zzz */ -public class NodeInfo extends SimpleDataStructure { +class NodeInfo extends SimpleDataStructure { private final NID nID; private final Hash hash; private Destination dest; private final int port; - private boolean permanent = false; public static final int LENGTH = NID.HASH_LENGTH + Hash.HASH_LENGTH + 2; @@ -207,14 +206,6 @@ public void setDestination(Destination dest) throws IllegalArgumentException { this.dest = dest; } - public void setPermanent(boolean isPermanent) { - permanent = isPermanent; - } - - public boolean getPermanent() { - return permanent; - } - public int getPort() { return this.port; } diff --git a/common/java/apps/org/klomp/snark/dht/PersistDHT.java b/common/java/apps/org/klomp/snark/dht/PersistDHT.java index c690bc3..8ecd111 100644 --- a/common/java/apps/org/klomp/snark/dht/PersistDHT.java +++ b/common/java/apps/org/klomp/snark/dht/PersistDHT.java @@ -37,10 +37,10 @@ else if (backupFile != null) public static synchronized void loadDHT(KRPC krpc, File file) { Log log = I2PAppContext.getGlobalContext().logManager().getLog(PersistDHT.class); int count = 0; - FileInputStream in = null; + BufferedReader br = null; try { - in = new FileInputStream(file); - BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); + br = new BufferedReader(new InputStreamReader( + new FileInputStream(file), "ISO-8859-1")); String line = null; while ( (line = br.readLine()) != null) { if (line.startsWith("#")) @@ -61,7 +61,7 @@ public static synchronized void loadDHT(KRPC krpc, File file) { if (log.shouldLog(Log.WARN) && file.exists()) log.warn("Error reading the DHT File", ioe); } finally { - if (in != null) try { in.close(); } catch (IOException ioe) {} + if (br != null) try { br.close(); } catch (IOException ioe) {} } if (log.shouldLog(Log.INFO)) log.info("Loaded " + count + " nodes from " + file); diff --git a/common/java/core/net/i2p/CoreVersion.java b/common/java/core/net/i2p/CoreVersion.java index 81212cb..5d59638 100644 --- a/common/java/core/net/i2p/CoreVersion.java +++ b/common/java/core/net/i2p/CoreVersion.java @@ -16,7 +16,7 @@ public class CoreVersion { /** deprecated */ public final static String ID = "Monotone"; - public final static String VERSION = "0.9.9"; + public final static String VERSION = "0.9.11"; public static void main(String args[]) { System.out.println("I2P Core version: " + VERSION); diff --git a/common/java/core/net/i2p/I2PAppContext.java b/common/java/core/net/i2p/I2PAppContext.java index 9a45598..7818be5 100644 --- a/common/java/core/net/i2p/I2PAppContext.java +++ b/common/java/core/net/i2p/I2PAppContext.java @@ -7,6 +7,7 @@ import java.util.Random; import java.util.Set; +import net.i2p.app.ClientAppManager; import net.i2p.client.naming.NamingService; import net.i2p.crypto.AESEngine; import net.i2p.crypto.CryptixAESEngine; @@ -121,6 +122,7 @@ public class I2PAppContext { private final File _logDir; private final File _appDir; private volatile File _tmpDir; + private final Random _tmpDirRand = new Random(); // split up big lock on this to avoid deadlocks private final Object _lock1 = new Object(), _lock2 = new Object(), _lock3 = new Object(), _lock4 = new Object(), _lock5 = new Object(), _lock6 = new Object(), _lock7 = new Object(), _lock8 = new Object(), @@ -403,7 +405,7 @@ public File getTempDir() { String d = getProperty("i2p.dir.temp", System.getProperty("java.io.tmpdir")); // our random() probably isn't warmed up yet byte[] rand = new byte[6]; - (new Random()).nextBytes(rand); + _tmpDirRand.nextBytes(rand); String f = "i2p-" + Base64.encode(rand) + ".tmp"; _tmpDir = new SecureDirectory(d, f); if (_tmpDir.exists()) { @@ -535,11 +537,12 @@ public boolean getBooleanPropertyDefaultTrue(String propName) { * * @return set of Strings containing the names of defined system properties */ - public Set getPropertyNames() { + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Set getPropertyNames() { // clone to avoid ConcurrentModificationException - Set names = new HashSet(((Properties) System.getProperties().clone()).keySet()); + Set names = new HashSet((Set) (Set) ((Properties) System.getProperties().clone()).keySet()); // TODO-Java6: s/keySet()/stringPropertyNames()/ if (_overrideProps != null) - names.addAll(_overrideProps.keySet()); + names.addAll((Set) (Set) _overrideProps.keySet()); // TODO-Java6: s/keySet()/stringPropertyNames()/ return names; } @@ -1021,4 +1024,13 @@ private void initializeSimpleTimer2() { public UpdateManager updateManager() { return null; } + + /** + * The RouterAppManager in RouterContext, null always in I2PAppContext + * @return null always + * @since 0.9.11, in RouterContext since 0.9.4 + */ + public ClientAppManager clientAppManager() { + return null; + } } diff --git a/common/java/core/net/i2p/app/Outproxy.java b/common/java/core/net/i2p/app/Outproxy.java new file mode 100644 index 0000000..9238cd3 --- /dev/null +++ b/common/java/core/net/i2p/app/Outproxy.java @@ -0,0 +1,19 @@ +package net.i2p.app; + +import java.io.IOException; +import java.net.Socket; + +/** + * + * @since 0.9.11 + */ +public interface Outproxy { + + public static final String NAME = "outproxy"; + + /** + * + */ + public Socket connect(String host, int port) throws IOException; + +} diff --git a/common/java/core/net/i2p/client/ClientWriterRunner.java b/common/java/core/net/i2p/client/ClientWriterRunner.java index d25f741..35ecce1 100644 --- a/common/java/core/net/i2p/client/ClientWriterRunner.java +++ b/common/java/core/net/i2p/client/ClientWriterRunner.java @@ -8,10 +8,12 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import net.i2p.I2PAppContext; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageException; import net.i2p.internal.PoisonI2CPMessage; import net.i2p.util.I2PAppThread; +import net.i2p.util.Log; /** * Copied from net.i2p.router.client @@ -25,15 +27,24 @@ class ClientWriterRunner implements Runnable { private final I2PSessionImpl _session; private final BlockingQueue _messagesToWrite; private static final AtomicLong __Id = new AtomicLong(); + //private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ClientWriterRunner.class); private static final int MAX_QUEUE_SIZE = 32; private static final long MAX_SEND_WAIT = 10*1000; - /** starts the thread too */ + /** + * As of 0.9.11 does not start the thread, caller must call startWriting() + */ public ClientWriterRunner(OutputStream out, I2PSessionImpl session) { _out = new BufferedOutputStream(out); _session = session; _messagesToWrite = new LinkedBlockingQueue(MAX_QUEUE_SIZE); + } + + /** + * @since 0.9.11 + */ + public void startWriting() { Thread t = new I2PAppThread(this, "I2CP Client Writer " + __Id.incrementAndGet(), true); t.start(); } @@ -76,7 +87,8 @@ public void run() { // only thread, we don't need synchronized try { msg.writeMessage(_out); - _out.flush(); + if (_messagesToWrite.isEmpty()) + _out.flush(); } catch (I2CPMessageException ime) { _session.propogateError("Error writing out the message", ime); _session.disconnect(); diff --git a/common/java/core/net/i2p/client/HostReplyMessageHandler.java b/common/java/core/net/i2p/client/HostReplyMessageHandler.java new file mode 100644 index 0000000..94cce47 --- /dev/null +++ b/common/java/core/net/i2p/client/HostReplyMessageHandler.java @@ -0,0 +1,38 @@ +package net.i2p.client; + +/* + * Released into the public domain + * with no warranty of any kind, either expressed or implied. + */ + +import net.i2p.I2PAppContext; +import net.i2p.data.i2cp.I2CPMessage; +import net.i2p.data.i2cp.HostReplyMessage; +import net.i2p.util.Log; + +import net.i2p.data.Destination; + +/** + * Handle I2CP dest replies from the router + * + * @since 0.9.11 + */ +class HostReplyMessageHandler extends HandlerImpl { + + public HostReplyMessageHandler(I2PAppContext ctx) { + super(ctx, HostReplyMessage.MESSAGE_TYPE); + } + + public void handleMessage(I2CPMessage message, I2PSessionImpl session) { + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Handle message " + message); + HostReplyMessage msg = (HostReplyMessage) message; + Destination d = msg.getDestination(); + long id = msg.getReqID(); + if (d != null) { + session.destReceived(id, d); + } else { + session.destLookupFailed(id); + } + } +} diff --git a/common/java/core/net/i2p/client/I2PClientMessageHandlerMap.java b/common/java/core/net/i2p/client/I2PClientMessageHandlerMap.java index 7bc912b..bd2de83 100644 --- a/common/java/core/net/i2p/client/I2PClientMessageHandlerMap.java +++ b/common/java/core/net/i2p/client/I2PClientMessageHandlerMap.java @@ -13,6 +13,7 @@ import net.i2p.data.i2cp.BandwidthLimitsMessage; import net.i2p.data.i2cp.DestReplyMessage; import net.i2p.data.i2cp.DisconnectMessage; +import net.i2p.data.i2cp.HostReplyMessage; import net.i2p.data.i2cp.MessagePayloadMessage; import net.i2p.data.i2cp.MessageStatusMessage; import net.i2p.data.i2cp.RequestLeaseSetMessage; @@ -40,6 +41,7 @@ public I2PClientMessageHandlerMap(I2PAppContext context) { highest = Math.max(highest, MessageStatusMessage.MESSAGE_TYPE); highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE); highest = Math.max(highest, DestReplyMessage.MESSAGE_TYPE); + highest = Math.max(highest, HostReplyMessage.MESSAGE_TYPE); highest = Math.max(highest, BandwidthLimitsMessage.MESSAGE_TYPE); highest = Math.max(highest, RequestVariableLeaseSetMessage.MESSAGE_TYPE); @@ -53,6 +55,7 @@ public I2PClientMessageHandlerMap(I2PAppContext context) { _handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context); _handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context); _handlers[RequestVariableLeaseSetMessage.MESSAGE_TYPE] = new RequestVariableLeaseSetMessageHandler(context); + _handlers[HostReplyMessage.MESSAGE_TYPE] = new HostReplyMessageHandler(context); } public I2CPMessageHandler getHandler(int messageTypeId) { diff --git a/common/java/core/net/i2p/client/I2PSession.java b/common/java/core/net/i2p/client/I2PSession.java index 8822ea3..d70e404 100644 --- a/common/java/core/net/i2p/client/I2PSession.java +++ b/common/java/core/net/i2p/client/I2PSession.java @@ -214,6 +214,7 @@ public boolean sendMessage(Destination dest, byte[] payload, int offset, int siz public Destination lookupDest(Hash h) throws I2PSessionException; /** + * Lookup a Destination by Hash. * Blocking. * @param maxWait ms * @since 0.8.3 @@ -221,6 +222,68 @@ public boolean sendMessage(Destination dest, byte[] payload, int offset, int siz */ public Destination lookupDest(Hash h, long maxWait) throws I2PSessionException; + /** + * Ask the router to lookup a Destination by host name. + * Blocking. Waits a max of 10 seconds by default. + * + * This only makes sense for a b32 hostname, OR outside router context. + * Inside router context, just query the naming service. + * Outside router context, this does NOT query the context naming service. + * Do that first if you expect a local addressbook. + * + * This will log a warning for non-b32 in router context. + * + * Suggested implementation: + * + *

+     *  if (name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p")) {
+     *      if (session != null)
+     *          return session.lookup(Hash.create(Base32.decode(name.toLowerCase(Locale.US).substring(0, 52))));
+     *      else
+     *          return ctx.namingService().lookup(name); // simple session for xxx.b32.i2p handled by naming service (optional if you need lookup w/o an existing session)
+     *  } else if (ctx.isRouterContext()) {
+     *      return ctx.namingService().lookup(name); // hostname from router's naming service
+     *  } else {
+     *      Destination d = ctx.namingService().lookup(name); // local naming svc, optional
+     *      if (d != null)
+     *          return d;
+     *      if (session != null)
+     *          return session.lookup(name);
+     *      // simple session (optional if you need lookup w/o an existing session)
+     *      Destination rv = null;
+     *      I2PClient client = new I2PSimpleClient();
+     *      Properties opts = new Properties();
+     *      opts.put(I2PClient.PROP_TCP_HOST, host);
+     *      opts.put(I2PClient.PROP_TCP_PORT, port);
+     *      I2PSession session = null;
+     *      try {
+     *          session = client.createSession(null, opts);
+     *          session.connect();
+     *          rv = session.lookupDest(name);
+     *      } finally {
+     *          if (session != null)
+     *              session.destroySession();
+     *      }
+     *      return rv;
+     *  }
+     *
+ * + * Requires router side to be 0.9.11 or higher. If the router is older, + * this will return null immediately. + * + * @since 0.9.11 + */ + public Destination lookupDest(String name) throws I2PSessionException; + + /** + * Ask the router to lookup a Destination by host name. + * Blocking. See above for details. + * @param maxWait ms + * @since 0.9.11 + * @return null on failure + */ + public Destination lookupDest(String name, long maxWait) throws I2PSessionException; + /** * Pass updated options to the router. * Does not remove properties previously present but missing from this options parameter. diff --git a/common/java/core/net/i2p/client/I2PSessionImpl.java b/common/java/core/net/i2p/client/I2PSessionImpl.java index 465ff8a..293bc91 100644 --- a/common/java/core/net/i2p/client/I2PSessionImpl.java +++ b/common/java/core/net/i2p/client/I2PSessionImpl.java @@ -18,15 +18,17 @@ import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; import net.i2p.CoreVersion; import net.i2p.I2PAppContext; +import net.i2p.data.Base32; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -36,6 +38,7 @@ import net.i2p.data.i2cp.DestLookupMessage; import net.i2p.data.i2cp.GetBandwidthLimitsMessage; import net.i2p.data.i2cp.GetDateMessage; +import net.i2p.data.i2cp.HostLookupMessage; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageReader; import net.i2p.data.i2cp.MessagePayloadMessage; @@ -47,6 +50,7 @@ import net.i2p.util.I2PSSLSocketFactory; import net.i2p.util.LHMCache; import net.i2p.util.Log; +import net.i2p.util.OrderedProperties; import net.i2p.util.SimpleTimer; import net.i2p.util.VersionComparator; @@ -99,12 +103,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa /** hashes of lookups we are waiting for */ protected final LinkedBlockingQueue _pendingLookups = new LinkedBlockingQueue(); + private final AtomicInteger _lookupID = new AtomicInteger(); protected final Object _bwReceivedLock = new Object(); protected volatile int[] _bwLimits; protected final I2PClientMessageHandlerMap _handlerMap; - /** used to seperate things out so we can get rid of singletons */ + /** used to separate things out so we can get rid of singletons */ protected final I2PAppContext _context; /** monitor for waiting until a lease set has been granted */ @@ -115,6 +120,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa */ protected enum State { OPENING, + /** @since 0.9.11 */ + GOTDATE, OPEN, CLOSING, CLOSED @@ -123,11 +130,6 @@ protected enum State { private State _state = State.CLOSED; protected final Object _stateLock = new Object(); - /** have we received the current date from the router yet? */ - private volatile boolean _dateReceived; - /** lock that we wait upon, that the SetDateMessageHandler notifies */ - private final Object _dateReceivedLock = new Object(); - /** * thread that we tell when new messages are available who then tells us * to fetch them. The point of this is so that the fetch doesn't block the @@ -140,14 +142,20 @@ protected enum State { private boolean _isReduced; private final boolean _fastReceive; private volatile boolean _routerSupportsFastReceive; + private volatile boolean _routerSupportsHostLookup; /** + * Since 0.9.11, key is either a Hash or a String * @since 0.8.9 */ - private static final Map _lookupCache = new LHMCache(16); + private static final Map _lookupCache = new LHMCache(16); + private static final String MIN_HOST_LOOKUP_VERSION = "0.9.11"; + private static final boolean TEST_LOOKUP = false; /** SSL interface (only) @since 0.8.3 */ protected static final String PROP_ENABLE_SSL = "i2cp.SSL"; + protected static final String PROP_USER = "i2cp.username"; + protected static final String PROP_PW = "i2cp.password"; private static final long VERIFY_USAGE_TIME = 60*1000; @@ -160,9 +168,15 @@ void dateUpdated(String routerVersion) { _routerSupportsFastReceive = _context.isRouterContext() || (routerVersion != null && routerVersion.length() > 0 && VersionComparator.comp(routerVersion, MIN_FAST_VERSION) >= 0); - _dateReceived = true; - synchronized (_dateReceivedLock) { - _dateReceivedLock.notifyAll(); + _routerSupportsHostLookup = _context.isRouterContext() || + TEST_LOOKUP || + (routerVersion != null && routerVersion.length() > 0 && + VersionComparator.comp(routerVersion, MIN_HOST_LOOKUP_VERSION) >= 0); + synchronized (_stateLock) { + if (_state == State.OPENING) { + _state = State.GOTDATE; + _stateLock.notifyAll(); + } } } @@ -206,6 +220,8 @@ private I2PSessionImpl(I2PAppContext context, Properties options, _privateKey = null; _signingPrivateKey = null; } + _routerSupportsFastReceive = _context.isRouterContext(); + _routerSupportsHostLookup = _context.isRouterContext(); } /** @@ -240,12 +256,12 @@ private final Properties loadConfig(Properties opts) { // auto-add auth if required, not set in the options, and we are not in the same JVM if ((!_context.isRouterContext()) && _context.getBooleanProperty("i2cp.auth") && - ((!opts.containsKey("i2cp.username")) || (!opts.containsKey("i2cp.password")))) { - String configUser = _context.getProperty("i2cp.username"); - String configPW = _context.getProperty("i2cp.password"); + ((!opts.containsKey(PROP_USER)) || (!opts.containsKey(PROP_PW)))) { + String configUser = _context.getProperty(PROP_USER); + String configPW = _context.getProperty(PROP_PW); if (configUser != null && configPW != null) { - options.setProperty("i2cp.username", configUser); - options.setProperty("i2cp.password", configPW); + options.setProperty(PROP_USER, configUser); + options.setProperty(PROP_PW, configPW); } } if (options.getProperty(I2PClient.PROP_FAST_RECEIVE) == null) @@ -288,8 +304,8 @@ private int getPort() { /** save some memory, don't pass along the pointless properties */ private Properties filter(Properties options) { Properties rv = new Properties(); - for (Iterator iter = options.keySet().iterator(); iter.hasNext();) { - String key = (String) iter.next(); + for (Object oKey : options.keySet()) { // TODO-Java6: s/keySet()/stringPropertyNames()/ + String key = (String) oKey; if (key.startsWith("java.") || key.startsWith("user.") || key.startsWith("os.") || @@ -400,6 +416,7 @@ public void connect() throws I2PSessionException { loop = false; break; case OPENING: + case GOTDATE: wasOpening = true; try { _stateLock.wait(10*1000); @@ -456,6 +473,7 @@ public void connect() throws I2PSessionException { out.write(I2PClient.PROTOCOL_BYTE); out.flush(); _writer = new ClientWriterRunner(out, this); + _writer.startWriting(); InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE); _reader = new I2CPMessageReader(in, this); } @@ -463,26 +481,23 @@ public void connect() throws I2PSessionException { if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "before startReading"); _reader.startReading(); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before getDate"); - sendMessage(new GetDateMessage(CoreVersion.VERSION)); - if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After getDate / begin waiting for a response"); - int waitcount = 0; - while (!_dateReceived) { - if (waitcount++ > 30) { - throw new IOException("No handshake received from the router"); - } - synchronized (_dateReceivedLock) { - // InterruptedException caught below - _dateReceivedLock.wait(1000); - } + Properties auth = null; + if ((!_context.isRouterContext()) && _options.containsKey(PROP_USER) && _options.containsKey(PROP_PW)) { + // Only supported by routers 0.9.11 or higher, but we don't know the version yet. + // Auth will also be sent in the SessionConfig. + auth = new OrderedProperties(); + auth.setProperty(PROP_USER, _options.getProperty(PROP_USER)); + auth.setProperty(PROP_PW, _options.getProperty(PROP_PW)); } - if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After received a SetDate response"); + sendMessage(new GetDateMessage(CoreVersion.VERSION, auth)); + waitForDate(); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before producer.connect()"); _producer.connect(this); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After producer.connect()"); // wait until we have created a lease set - waitcount = 0; + int waitcount = 0; while (_leaseSet == null) { if (waitcount++ > 5*60) { throw new IOException("No tunnels built after waiting 5 minutes. Your network connection may be down, or there is severe network congestion."); @@ -525,6 +540,28 @@ public void connect() throws I2PSessionException { } } + /** + * @since 0.9.11 moved from connect() + */ + protected void waitForDate() throws InterruptedException, IOException { + if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After getDate / begin waiting for a response"); + int waitcount = 0; + while (true) { + if (waitcount++ > 30) { + throw new IOException("No handshake received from the router"); + } + synchronized(_stateLock) { + if (_state == State.GOTDATE) + break; + if (_state != State.OPENING) + throw new IOException("Socket closed"); + // InterruptedException caught by caller + _stateLock.wait(1000); + } + } + if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "After received a SetDate response"); + } + /** * Pull the unencrypted data from the message that we've already prefetched and * notified the user that its available. @@ -891,13 +928,16 @@ public void disconnected(I2CPMessageReader reader) { * Will interrupt a connect in progress. */ protected void disconnect() { + State oldState; synchronized(_stateLock) { if (_state == State.CLOSING || _state == State.CLOSED) return; + oldState = _state; changeState(State.CLOSING); } if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Disconnect() called", new Exception("Disconnect")); - if (shouldReconnect()) { + // don't try to reconnect if it failed before GETTDATE + if (oldState != State.OPENING && shouldReconnect()) { if (reconnect()) { if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "I2CP reconnection successful"); return; @@ -964,14 +1004,17 @@ protected String getPrefix() { return buf.toString(); } - /** called by the message handler */ + /** + * Called by the message handler + * on reception of DestReplyMessage + */ void destReceived(Destination d) { Hash h = d.calculateHash(); synchronized (_lookupCache) { _lookupCache.put(h, d); } for (LookupWaiter w : _pendingLookups) { - if (w.hash.equals(h)) { + if (h.equals(w.hash)) { w.destination = d; synchronized (w) { w.notifyAll(); @@ -980,10 +1023,52 @@ void destReceived(Destination d) { } } - /** called by the message handler */ + /** + * Called by the message handler + * on reception of DestReplyMessage + */ void destLookupFailed(Hash h) { for (LookupWaiter w : _pendingLookups) { - if (w.hash.equals(h)) { + if (h.equals(w.hash)) { + synchronized (w) { + w.notifyAll(); + } + } + } + } + + /** + * Called by the message handler + * on reception of HostReplyMessage + * @since 0.9.11 + */ + void destReceived(long nonce, Destination d) { + // notify by hash + destReceived(d); + // notify by nonce + for (LookupWaiter w : _pendingLookups) { + if (nonce == w.nonce) { + w.destination = d; + if (w.name != null) { + synchronized (_lookupCache) { + _lookupCache.put(w.name, d); + } + } + synchronized (w) { + w.notifyAll(); + } + } + } + } + + /** + * Called by the message handler + * on reception of HostReplyMessage + * @since 0.9.11 + */ + void destLookupFailed(long nonce) { + for (LookupWaiter w : _pendingLookups) { + if (nonce == w.nonce) { synchronized (w) { w.notifyAll(); } @@ -1004,13 +1089,31 @@ void bwReceived(int[] i) { * @since 0.8.3 */ private static class LookupWaiter { - /** the request */ + /** the request (Hash mode) */ public final Hash hash; + /** the request (String mode) */ + public final String name; + /** the request (nonce mode) */ + public final long nonce; /** the reply */ public volatile Destination destination; public LookupWaiter(Hash h) { + this(h, -1); + } + + /** @since 0.9.11 */ + public LookupWaiter(Hash h, long nonce) { this.hash = h; + this.name = null; + this.nonce = nonce; + } + + /** @since 0.9.11 */ + public LookupWaiter(String name, long nonce) { + this.hash = null; + this.name = name; + this.nonce = nonce; } } @@ -1038,12 +1141,119 @@ public Destination lookupDest(Hash h, long maxWait) throws I2PSessionException { if (rv != null) return rv; } - if (isClosed()) + if (isClosed()) { + if (_log.shouldLog(Log.INFO)) + _log.info("Session closed, cannot lookup " + h); return null; - LookupWaiter waiter = new LookupWaiter(h); + } + LookupWaiter waiter; + long nonce; + if (_routerSupportsHostLookup) { + nonce = _lookupID.incrementAndGet() & 0x7fffffff; + waiter = new LookupWaiter(h, nonce); + } else { + nonce = 0; // won't be used + waiter = new LookupWaiter(h); + } _pendingLookups.offer(waiter); try { - sendMessage(new DestLookupMessage(h)); + if (_routerSupportsHostLookup) { + if (_log.shouldLog(Log.INFO)) + _log.info("Sending HostLookup for " + h); + SessionId id = _sessionId; + if (id == null) + id = new SessionId(65535); + sendMessage(new HostLookupMessage(id, h, nonce, maxWait)); + } else { + if (_log.shouldLog(Log.INFO)) + _log.info("Sending DestLookup for " + h); + sendMessage(new DestLookupMessage(h)); + } + try { + synchronized (waiter) { + waiter.wait(maxWait); + } + } catch (InterruptedException ie) { + throw new I2PSessionException("Interrupted", ie); + } + } finally { + _pendingLookups.remove(waiter); + } + return waiter.destination; + } + + /** + * Ask the router to lookup a Destination by host name. + * Blocking. Waits a max of 10 seconds by default. + * + * This only makes sense for a b32 hostname, OR outside router context. + * Inside router context, just query the naming service. + * Outside router context, this does NOT query the context naming service. + * Do that first if you expect a local addressbook. + * + * This will log a warning for non-b32 in router context. + * + * See interface for suggested implementation. + * + * Requires router side to be 0.9.11 or higher. If the router is older, + * this will return null immediately. + * + * @since 0.9.11 + */ + public Destination lookupDest(String name) throws I2PSessionException { + return lookupDest(name, 10*1000); + } + + /** + * Ask the router to lookup a Destination by host name. + * Blocking. See above for details. + * @param maxWait ms + * @since 0.9.11 + * @return null on failure + */ + public Destination lookupDest(String name, long maxWait) throws I2PSessionException { + if (name.length() == 0) + return null; + // Shortcut for b64 + if (name.length() >= 516) { + try { + return new Destination(name); + } catch (DataFormatException dfe) { + return null; + } + } + // won't fit in Mapping + if (name.length() >= 256 && !_context.isRouterContext()) + return null; + synchronized (_lookupCache) { + Destination rv = _lookupCache.get(name); + if (rv != null) + return rv; + } + if (isClosed()) { + if (_log.shouldLog(Log.INFO)) + _log.info("Session closed, cannot lookup " + name); + return null; + } + if (!_routerSupportsHostLookup) { + // do them a favor and convert to Hash lookup + if (name.length() == 60 && name.toLowerCase(Locale.US).endsWith(".b32.i2p")) + return lookupDest(Hash.create(Base32.decode(name.toLowerCase(Locale.US).substring(0, 52))), maxWait); + // else unsupported + if (_log.shouldLog(Log.WARN)) + _log.warn("Router does not support HostLookup for " + name); + return null; + } + int nonce = _lookupID.incrementAndGet() & 0x7fffffff; + LookupWaiter waiter = new LookupWaiter(name, nonce); + _pendingLookups.offer(waiter); + try { + if (_log.shouldLog(Log.INFO)) + _log.info("Sending HostLookup for " + name); + SessionId id = _sessionId; + if (id == null) + id = new SessionId(65535); + sendMessage(new HostLookupMessage(id, name, nonce, maxWait)); try { synchronized (waiter) { waiter.wait(maxWait); diff --git a/common/java/core/net/i2p/client/I2PSimpleSession.java b/common/java/core/net/i2p/client/I2PSimpleSession.java index e3a911e..b6f8bb7 100644 --- a/common/java/core/net/i2p/client/I2PSimpleSession.java +++ b/common/java/core/net/i2p/client/I2PSimpleSession.java @@ -14,13 +14,20 @@ import java.security.GeneralSecurityException; import java.util.Properties; +import net.i2p.CoreVersion; import net.i2p.I2PAppContext; import net.i2p.data.i2cp.BandwidthLimitsMessage; import net.i2p.data.i2cp.DestReplyMessage; +import net.i2p.data.i2cp.DisconnectMessage; +import net.i2p.data.i2cp.GetDateMessage; +import net.i2p.data.i2cp.HostReplyMessage; import net.i2p.data.i2cp.I2CPMessageReader; +import net.i2p.data.i2cp.SetDateMessage; import net.i2p.internal.InternalClientManager; import net.i2p.internal.QueuedI2CPMessageReader; import net.i2p.util.I2PSSLSocketFactory; +import net.i2p.util.Log; +import net.i2p.util.OrderedProperties; /** * Create a new session for doing naming and bandwidth queries only. Do not create a Destination. @@ -68,6 +75,7 @@ public void connect() throws I2PSessionException { // the following may throw an I2PSessionException _queue = mgr.connect(); _reader = new QueuedI2CPMessageReader(_queue, this); + _reader.startReading(); } else { if (Boolean.parseBoolean(getOptions().getProperty(PROP_ENABLE_SSL))) { try { @@ -85,14 +93,47 @@ public void connect() throws I2PSessionException { out.write(I2PClient.PROTOCOL_BYTE); out.flush(); _writer = new ClientWriterRunner(out, this); + _writer.startWriting(); InputStream in = new BufferedInputStream(_socket.getInputStream(), BUF_SIZE); _reader = new I2CPMessageReader(in, this); + _reader.startReading(); } } + // must be out of synch block for writer to get unblocked + if (!_context.isRouterContext()) { + Properties opts = getOptions(); + // Send auth message if required + // Auth was not enforced on a simple session until 0.9.11 + // We will get disconnected for router version < 0.9.11 since it doesn't + // support the AuthMessage + if ((!opts.containsKey(PROP_USER)) && (!opts.containsKey(PROP_PW))) { + // auto-add auth if not set in the options + String configUser = _context.getProperty(PROP_USER); + String configPW = _context.getProperty(PROP_PW); + if (configUser != null && configPW != null) { + opts.setProperty(PROP_USER, configUser); + opts.setProperty(PROP_PW, configPW); + } + } + if (opts.containsKey(PROP_USER) && opts.containsKey(PROP_PW)) { + Properties auth = new OrderedProperties(); + auth.setProperty(PROP_USER, opts.getProperty(PROP_USER)); + auth.setProperty(PROP_PW, opts.getProperty(PROP_PW)); + sendMessage(new GetDateMessage(CoreVersion.VERSION, auth)); + } else { + // we must now send a GetDate even in SimpleSession, or we won't know + // what version we are talking with and cannot use HostLookup + sendMessage(new GetDateMessage(CoreVersion.VERSION)); + } + waitForDate(); + } // we do not receive payload messages, so we do not need an AvailabilityNotifier // ... or an Idle timer, or a VerifyUsage - _reader.startReading(); success = true; + if (_log.shouldLog(Log.INFO)) + _log.info(getPrefix() + " simple session connected"); + } catch (InterruptedException ie) { + throw new I2PSessionException("Interrupted", ie); } catch (UnknownHostException uhe) { throw new I2PSessionException(getPrefix() + "Cannot connect to the router on " + _hostname + ':' + _portNum, uhe); } catch (IOException ioe) { @@ -115,9 +156,15 @@ public void updateOptions(Properties options) {} private static class SimpleMessageHandlerMap extends I2PClientMessageHandlerMap { public SimpleMessageHandlerMap(I2PAppContext context) { int highest = Math.max(DestReplyMessage.MESSAGE_TYPE, BandwidthLimitsMessage.MESSAGE_TYPE); + highest = Math.max(highest, DisconnectMessage.MESSAGE_TYPE); + highest = Math.max(highest, HostReplyMessage.MESSAGE_TYPE); + highest = Math.max(highest, SetDateMessage.MESSAGE_TYPE); _handlers = new I2CPMessageHandler[highest+1]; _handlers[DestReplyMessage.MESSAGE_TYPE] = new DestReplyMessageHandler(context); _handlers[BandwidthLimitsMessage.MESSAGE_TYPE] = new BWLimitsMessageHandler(context); + _handlers[DisconnectMessage.MESSAGE_TYPE] = new DisconnectMessageHandler(context); + _handlers[HostReplyMessage.MESSAGE_TYPE] = new HostReplyMessageHandler(context); + _handlers[SetDateMessage.MESSAGE_TYPE] = new SetDateMessageHandler(context); } } } diff --git a/common/java/core/net/i2p/client/naming/LookupDest.java b/common/java/core/net/i2p/client/naming/LookupDest.java index 79f269e..c8e5381 100644 --- a/common/java/core/net/i2p/client/naming/LookupDest.java +++ b/common/java/core/net/i2p/client/naming/LookupDest.java @@ -34,6 +34,9 @@ class LookupDest { private static final long DEFAULT_TIMEOUT = 15*1000; + private static final String PROP_ENABLE_SSL = "i2cp.SSL"; + private static final String PROP_USER = "i2cp.username"; + private static final String PROP_PW = "i2cp.password"; protected LookupDest(I2PAppContext context) {} @@ -61,12 +64,23 @@ static Destination lookupHash(I2PAppContext ctx, byte[] h) throws I2PSessionExce Destination rv = null; I2PClient client = new I2PSimpleClient(); Properties opts = new Properties(); - String s = ctx.getProperty(I2PClient.PROP_TCP_HOST); - if (s != null) - opts.put(I2PClient.PROP_TCP_HOST, s); - s = ctx.getProperty(I2PClient.PROP_TCP_PORT); - if (s != null) - opts.put(I2PClient.PROP_TCP_PORT, s); + if (!ctx.isRouterContext()) { + String s = ctx.getProperty(I2PClient.PROP_TCP_HOST); + if (s != null) + opts.put(I2PClient.PROP_TCP_HOST, s); + s = ctx.getProperty(I2PClient.PROP_TCP_PORT); + if (s != null) + opts.put(I2PClient.PROP_TCP_PORT, s); + s = ctx.getProperty(PROP_ENABLE_SSL); + if (s != null) + opts.put(PROP_ENABLE_SSL, s); + s = ctx.getProperty(PROP_USER); + if (s != null) + opts.put(PROP_USER, s); + s = ctx.getProperty(PROP_PW); + if (s != null) + opts.put(PROP_PW, s); + } I2PSession session = null; try { session = client.createSession(null, opts); diff --git a/common/java/core/net/i2p/client/naming/MetaNamingService.java b/common/java/core/net/i2p/client/naming/MetaNamingService.java index 03e0ef7..fab433a 100644 --- a/common/java/core/net/i2p/client/naming/MetaNamingService.java +++ b/common/java/core/net/i2p/client/naming/MetaNamingService.java @@ -38,8 +38,8 @@ public MetaNamingService(I2PAppContext context) { _services = new CopyOnWriteArrayList(); while (tok.hasMoreTokens()) { try { - Class cls = Class.forName(tok.nextToken()); - Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class }); + Class cls = Class.forName(tok.nextToken()); + Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class }); addNamingService((NamingService)con.newInstance(new Object[] { context }), false); } catch (Exception ex) { } diff --git a/common/java/core/net/i2p/client/naming/NamingService.java b/common/java/core/net/i2p/client/naming/NamingService.java index ceade7e..001cc42 100644 --- a/common/java/core/net/i2p/client/naming/NamingService.java +++ b/common/java/core/net/i2p/client/naming/NamingService.java @@ -462,8 +462,8 @@ public static final synchronized NamingService createInstance(I2PAppContext cont NamingService instance = null; String impl = context.getProperty(PROP_IMPL, DEFAULT_IMPL); try { - Class cls = Class.forName(impl); - Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class }); + Class cls = Class.forName(impl); + Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class }); instance = (NamingService)con.newInstance(new Object[] { context }); } catch (Exception ex) { Log log = context.logManager().getLog(NamingService.class); diff --git a/common/java/core/net/i2p/crypto/DummyDSAEngine.java b/common/java/core/net/i2p/crypto/DummyDSAEngine.java index 3a35a96..b8e7e19 100644 --- a/common/java/core/net/i2p/crypto/DummyDSAEngine.java +++ b/common/java/core/net/i2p/crypto/DummyDSAEngine.java @@ -10,6 +10,9 @@ * */ public class DummyDSAEngine extends DSAEngine { + + private static final Signature FAKE_SIGNATURE = new Signature(new byte[Signature.SIGNATURE_BYTES]); + public DummyDSAEngine(I2PAppContext context) { super(context); } @@ -21,8 +24,6 @@ public boolean verifySignature(Signature signature, byte signedData[], SigningPu @Override public Signature sign(byte data[], SigningPrivateKey signingKey) { - Signature sig = new Signature(); - sig.setData(Signature.FAKE_SIGNATURE); - return sig; + return FAKE_SIGNATURE; } -} \ No newline at end of file +} diff --git a/common/java/core/net/i2p/crypto/ECConstants.java b/common/java/core/net/i2p/crypto/ECConstants.java index a3d563f..8487523 100644 --- a/common/java/core/net/i2p/crypto/ECConstants.java +++ b/common/java/core/net/i2p/crypto/ECConstants.java @@ -41,8 +41,8 @@ private static void log(String s, Throwable t) { boolean loaded; if (Security.getProvider("BC") == null) { try { - Class cls = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); - Constructor con = cls.getConstructor(new Class[0]); + Class cls = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); + Constructor con = cls.getConstructor(new Class[0]); Provider bc = (Provider)con.newInstance(new Object[0]); Security.addProvider(bc); log("Added BC provider"); diff --git a/common/java/core/net/i2p/crypto/TransientSessionKeyManager.java b/common/java/core/net/i2p/crypto/TransientSessionKeyManager.java index 8ebfdbf..4f4dba1 100644 --- a/common/java/core/net/i2p/crypto/TransientSessionKeyManager.java +++ b/common/java/core/net/i2p/crypto/TransientSessionKeyManager.java @@ -541,12 +541,10 @@ public void tagsReceived(SessionKey key, Set sessionTags, long expir if (old != null) { // drop both old and tagSet tags synchronized (_inboundTagSets) { - for (Iterator iter = old.getTags().iterator(); iter.hasNext(); ) { - SessionTag tag = iter.next(); + for (SessionTag tag : old.getTags()) { _inboundTagSets.remove(tag); } - for (Iterator iter = sessionTags.iterator(); iter.hasNext(); ) { - SessionTag tag = iter.next(); + for (SessionTag tag : sessionTags) { _inboundTagSets.remove(tag); } } @@ -734,8 +732,7 @@ public void renderStatusHTML(Writer out) throws IOException { buf.append("Session key: ").append(skey.toBase64()).append("" + "# Sets: ").append(sets.size()).append("" + "
    "); - for (Iterator siter = sets.iterator(); siter.hasNext();) { - TagSet ts = siter.next(); + for (TagSet ts : sets) { int size = ts.getTags().size(); total += size; buf.append("
  • ID: ").append(ts.getID()); @@ -1050,8 +1047,7 @@ public int availableTags() { public long getLastExpirationDate() { long last = 0; synchronized (_tagSets) { - for (Iterator iter = _tagSets.iterator(); iter.hasNext();) { - TagSet set = iter.next(); + for (TagSet set : _tagSets) { if ( (set.getDate() > last) && (!set.getTags().isEmpty()) ) last = set.getDate(); } diff --git a/common/java/core/net/i2p/data/DataHelper.java b/common/java/core/net/i2p/data/DataHelper.java index 83fe675..12b1783 100644 --- a/common/java/core/net/i2p/data/DataHelper.java +++ b/common/java/core/net/i2p/data/DataHelper.java @@ -226,7 +226,7 @@ public static void writeProperties(OutputStream rawStream, Properties props, boo p = props; } ByteArrayOutputStream baos = new ByteArrayOutputStream(p.size() * 64); - for (Map.Entry entry : p.entrySet()) { + for (Map.Entry entry : p.entrySet()) { String key = (String) entry.getKey(); String val = (String) entry.getValue(); if (utf8) @@ -273,7 +273,7 @@ public static int toProperties(byte target[], int offset, Properties props) thro OrderedProperties p = new OrderedProperties(); p.putAll(props); ByteArrayOutputStream baos = new ByteArrayOutputStream(p.size() * 64); - for (Map.Entry entry : p.entrySet()) { + for (Map.Entry entry : p.entrySet()) { String key = (String) entry.getKey(); String val = (String) entry.getValue(); writeStringUTF8(baos, key); @@ -367,7 +367,7 @@ public static byte[] toProperties(Properties opts) throws DataFormatException { * (unless the options param is an OrderedProperties) */ public static String toString(Properties options) { - return toString((Map) options); + return toString((Map) options); } /** @@ -378,7 +378,7 @@ public static String toString(Properties options) { public static String toString(Map options) { StringBuilder buf = new StringBuilder(); if (options != null) { - for (Map.Entry entry : options.entrySet()) { + for (Map.Entry entry : options.entrySet()) { String key = (String) entry.getKey(); String val = (String) entry.getValue(); buf.append("[").append(key).append("] = [").append(val).append("]"); @@ -399,6 +399,7 @@ public static String toString(Map options) { * - Leading whitespace is not trimmed * - '=' is the only key-termination character (not ':' or whitespace) * + * As of 0.9.10, an empty value is allowed. */ public static void loadProps(Properties props, File file) throws IOException { loadProps(props, file, false); @@ -442,11 +443,12 @@ public static void loadProps(Properties props, InputStream inStr, boolean forceL // it was a horrible idea anyway //val = val.replaceAll("\\\\r","\r"); //val = val.replaceAll("\\\\n","\n"); - if ( (key.length() > 0) && (val.length() > 0) ) - if (forceLowerCase) - props.setProperty(key.toLowerCase(Locale.US), val); - else - props.setProperty(key, val); + + // as of 0.9.10, an empty value is allowed + if (forceLowerCase) + props.setProperty(key.toLowerCase(Locale.US), val); + else + props.setProperty(key, val); } } finally { if (in != null) try { in.close(); } catch (IOException ioe) {} @@ -470,7 +472,7 @@ public static void storeProps(Properties props, File file) throws IOException { try { out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8"))); out.println("# NOTE: This I2P config file must use UTF-8 encoding"); - for (Map.Entry entry : props.entrySet()) { + for (Map.Entry entry : props.entrySet()) { String name = (String) entry.getKey(); String val = (String) entry.getValue(); if (name.contains("#") || @@ -497,10 +499,10 @@ public static void storeProps(Properties props, File file) throws IOException { * Pretty print the collection * */ - public static String toString(Collection col) { + public static String toString(Collection col) { StringBuilder buf = new StringBuilder(); if (col != null) { - for (Iterator iter = col.iterator(); iter.hasNext();) { + for (Iterator iter = col.iterator(); iter.hasNext();) { Object o = iter.next(); buf.append("[").append(o).append("]"); if (iter.hasNext()) buf.append(", "); @@ -964,12 +966,12 @@ public final static boolean eq(Object lhs, Object rhs) { * based on the value of each at each step along the way. * */ - public final static boolean eq(Collection lhs, Collection rhs) { + public final static boolean eq(Collection lhs, Collection rhs) { if ((lhs == null) && (rhs == null)) return true; if ((lhs == null) || (rhs == null)) return false; if (lhs.size() != rhs.size()) return false; - Iterator liter = lhs.iterator(); - Iterator riter = rhs.iterator(); + Iterator liter = lhs.iterator(); + Iterator riter = rhs.iterator(); while ((liter.hasNext()) && (riter.hasNext())) if (!(eq(liter.next(), riter.next()))) return false; return true; @@ -1132,10 +1134,10 @@ public static int hashCode(byte b[]) { * Calculate the hashcode of the collection, using 0 for null * */ - public static int hashCode(Collection col) { + public static int hashCode(Collection col) { if (col == null) return 0; int c = 0; - for (Iterator iter = col.iterator(); iter.hasNext();) + for (Iterator iter = col.iterator(); iter.hasNext();) c = 7 * c + hashCode(iter.next()); return c; } diff --git a/common/java/core/net/i2p/data/DatabaseEntry.java b/common/java/core/net/i2p/data/DatabaseEntry.java index 92e9d97..e10e4ab 100644 --- a/common/java/core/net/i2p/data/DatabaseEntry.java +++ b/common/java/core/net/i2p/data/DatabaseEntry.java @@ -107,15 +107,17 @@ public Hash getHash() { */ public Hash getRoutingKey() { RoutingKeyGenerator gen = RoutingKeyGenerator.getInstance(); - if ((gen.getModData() == null) || (_routingKeyGenMod == null) - || (!DataHelper.eq(gen.getModData(), _routingKeyGenMod))) { + byte[] mod = gen.getModData(); + if (!DataHelper.eq(mod, _routingKeyGenMod)) { _currentRoutingKey = gen.getRoutingKey(getHash()); - _routingKeyGenMod = gen.getModData(); + _routingKeyGenMod = mod; } return _currentRoutingKey; } - + /** + * @deprecated unused + */ public void setRoutingKey(Hash key) { _currentRoutingKey = key; } diff --git a/common/java/core/net/i2p/data/PrivateKeyFile.java b/common/java/core/net/i2p/data/PrivateKeyFile.java index 2808f9a..9c71cc9 100644 --- a/common/java/core/net/i2p/data/PrivateKeyFile.java +++ b/common/java/core/net/i2p/data/PrivateKeyFile.java @@ -6,7 +6,6 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.util.Iterator; import java.util.Map; import java.util.Properties; @@ -192,13 +191,21 @@ public void setDestination(Destination d) { this.dest = d; } - /** change cert type - caller must also call write() */ + /** + * Change cert type - caller must also call write(). + * Side effect - creates new Destination object. + */ public Certificate setCertType(int t) { if (this.dest == null) throw new IllegalArgumentException("Dest is null"); Certificate c = new Certificate(); c.setCertificateType(t); - this.dest.setCertificate(c); + // dests now immutable, must create new + Destination newdest = new Destination(); + newdest.setPublicKey(dest.getPublicKey()); + newdest.setSigningPublicKey(dest.getSigningPublicKey()); + newdest.setCertificate(c); + dest = newdest; return c; } @@ -402,8 +409,7 @@ public static boolean verifySignature(Destination d) { System.out.println("Attempting to verify using " + sz + " hosts, this may take a while"); } - for (Iterator iter = hosts.entrySet().iterator(); iter.hasNext(); ) { - Map.Entry entry = (Map.Entry)iter.next(); + for (Map.Entry entry : hosts.entrySet()) { String s = (String) entry.getValue(); Destination signer = new Destination(s); // make it go faster if we have the signerHash hint diff --git a/common/java/core/net/i2p/data/RouterAddress.java b/common/java/core/net/i2p/data/RouterAddress.java index 0b6fde6..4cdf3c5 100644 --- a/common/java/core/net/i2p/data/RouterAddress.java +++ b/common/java/core/net/i2p/data/RouterAddress.java @@ -157,7 +157,7 @@ public Properties getOptions() { * @return an unmodifiable view, non-null, sorted * @since 0.8.13 */ - public Map getOptionsMap() { + public Map getOptionsMap() { return Collections.unmodifiableMap(_options); } @@ -324,7 +324,7 @@ public String toString() { buf.append("\n\tCost: ").append(_cost); //buf.append("\n\tExpiration: ").append(_expiration); buf.append("\n\tOptions (").append(_options.size()).append("):"); - for (Map.Entry e : _options.entrySet()) { + for (Map.Entry e : _options.entrySet()) { String key = (String) e.getKey(); String val = (String) e.getValue(); buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); diff --git a/common/java/core/net/i2p/data/RouterInfo.java b/common/java/core/net/i2p/data/RouterInfo.java index 72b54b8..2149378 100644 --- a/common/java/core/net/i2p/data/RouterInfo.java +++ b/common/java/core/net/i2p/data/RouterInfo.java @@ -244,7 +244,7 @@ public Properties getOptions() { * @return an unmodifiable view, non-null, sorted * @since 0.8.13 */ - public Map getOptionsMap() { + public Map getOptionsMap() { return Collections.unmodifiableMap(_options); } @@ -626,7 +626,7 @@ public String toString() { } } buf.append("\n\tOptions (").append(_options.size()).append("):"); - for (Map.Entry e : _options.entrySet()) { + for (Map.Entry e : _options.entrySet()) { String key = (String) e.getKey(); String val = (String) e.getValue(); buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); diff --git a/common/java/core/net/i2p/data/RoutingKeyGenerator.java b/common/java/core/net/i2p/data/RoutingKeyGenerator.java index 08e8ee6..e66049c 100644 --- a/common/java/core/net/i2p/data/RoutingKeyGenerator.java +++ b/common/java/core/net/i2p/data/RoutingKeyGenerator.java @@ -17,6 +17,7 @@ import net.i2p.I2PAppContext; import net.i2p.crypto.SHA256Generator; +import net.i2p.util.HexDump; import net.i2p.util.Log; /** @@ -37,6 +38,8 @@ * Also - the method generateDateBasedModData() should be called after midnight GMT * once per day to generate the correct routing keys! * + * Warning - API subject to change. Not for use outside the router. + * */ public class RoutingKeyGenerator { private final Log _log; @@ -54,6 +57,8 @@ public static RoutingKeyGenerator getInstance() { } private volatile byte _currentModData[]; + private volatile byte _nextModData[]; + private volatile long _nextMidnight; private volatile long _lastChanged; private final static Calendar _cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT")); @@ -61,22 +66,48 @@ public static RoutingKeyGenerator getInstance() { private static final int LENGTH = FORMAT.length(); private final static SimpleDateFormat _fmt = new SimpleDateFormat(FORMAT); + /** + * The current (today's) mod data. + * Warning - not a copy, do not corrupt. + * + * @return non-null, 8 bytes + */ public byte[] getModData() { return _currentModData; } + /** + * Tomorrow's mod data. + * Warning - not a copy, do not corrupt. + * For debugging use only. + * + * @return non-null, 8 bytes + * @since 0.9.10 + */ + public byte[] getNextModData() { + return _nextModData; + } + public long getLastChanged() { return _lastChanged; } /** - * Update the current modifier data with some bytes derived from the current - * date (yyyyMMdd in GMT) + * How long until midnight (ms) * - * @return true if changed + * @return could be slightly negative + * @since 0.9.10 moved from UpdateRoutingKeyModifierJob */ - public synchronized boolean generateDateBasedModData() { - long now = _context.clock().now(); + public long getTimeTillMidnight() { + return _nextMidnight - _context.clock().now(); + } + + /** + * Set _cal to midnight for the time given. + * Caller must synch. + * @since 0.9.10 + */ + private void setCalToPreviousMidnight(long now) { _cal.setTime(new Date(now)); _cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR)); // gcj <= 4.0 workaround _cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround @@ -84,6 +115,14 @@ public synchronized boolean generateDateBasedModData() { _cal.set(Calendar.MINUTE, 0); _cal.set(Calendar.SECOND, 0); _cal.set(Calendar.MILLISECOND, 0); + } + + /** + * Generate mod data from _cal. + * Caller must synch. + * @since 0.9.10 + */ + private byte[] generateModDataFromCal() { Date today = _cal.getTime(); String modVal = _fmt.format(today); @@ -92,19 +131,37 @@ public synchronized boolean generateDateBasedModData() { byte[] mod = new byte[LENGTH]; for (int i = 0; i < LENGTH; i++) mod[i] = (byte)(modVal.charAt(i) & 0xFF); + return mod; + } + + /** + * Update the current modifier data with some bytes derived from the current + * date (yyyyMMdd in GMT) + * + * @return true if changed + */ + public synchronized boolean generateDateBasedModData() { + long now = _context.clock().now(); + setCalToPreviousMidnight(now); + byte[] mod = generateModDataFromCal(); boolean changed = !DataHelper.eq(_currentModData, mod); if (changed) { + // add a day and store next midnight and mod data for convenience + _cal.add(Calendar.DATE, 1); + _nextMidnight = _cal.getTime().getTime(); + byte[] next = generateModDataFromCal(); _currentModData = mod; + _nextModData = next; _lastChanged = now; if (_log.shouldLog(Log.INFO)) - _log.info("Routing modifier generated: " + modVal); + _log.info("Routing modifier generated: " + HexDump.dump(mod)); } return changed; } /** * Generate a modified (yet consistent) hash from the origKey by generating the - * SHA256 of the targetKey with the current modData appended to it, *then* + * SHA256 of the targetKey with the current modData appended to it * * This makes Sybil's job a lot harder, as she needs to essentially take over the * whole keyspace. @@ -112,10 +169,29 @@ public synchronized boolean generateDateBasedModData() { * @throws IllegalArgumentException if origKey is null */ public Hash getRoutingKey(Hash origKey) { + return getKey(origKey, _currentModData); + } + + /** + * Get the routing key using tomorrow's modData, not today's + * + * @since 0.9.10 + */ + public Hash getNextRoutingKey(Hash origKey) { + return getKey(origKey, _nextModData); + } + + /** + * Generate a modified (yet consistent) hash from the origKey by generating the + * SHA256 of the targetKey with the specified modData appended to it + * + * @throws IllegalArgumentException if origKey is null + */ + private static Hash getKey(Hash origKey, byte[] modData) { if (origKey == null) throw new IllegalArgumentException("Original key is null"); byte modVal[] = new byte[Hash.HASH_LENGTH + LENGTH]; System.arraycopy(origKey.getData(), 0, modVal, 0, Hash.HASH_LENGTH); - System.arraycopy(_currentModData, 0, modVal, Hash.HASH_LENGTH, LENGTH); + System.arraycopy(modData, 0, modVal, Hash.HASH_LENGTH, LENGTH); return SHA256Generator.getInstance().calculateHash(modVal); } diff --git a/common/java/core/net/i2p/data/Signature.java b/common/java/core/net/i2p/data/Signature.java index 55b60c7..83f0410 100644 --- a/common/java/core/net/i2p/data/Signature.java +++ b/common/java/core/net/i2p/data/Signature.java @@ -25,7 +25,11 @@ public class Signature extends SimpleDataStructure { private static final SigType DEF_TYPE = SigType.DSA_SHA1; /** 40 */ public final static int SIGNATURE_BYTES = DEF_TYPE.getSigLen(); - /** all zeros */ + + /** + * all zeros + * @deprecated to be removed + */ public final static byte[] FAKE_SIGNATURE = new byte[SIGNATURE_BYTES]; private final SigType _type; diff --git a/common/java/core/net/i2p/data/i2cp/GetDateMessage.java b/common/java/core/net/i2p/data/i2cp/GetDateMessage.java index f76aaaf..00c5a75 100644 --- a/common/java/core/net/i2p/data/i2cp/GetDateMessage.java +++ b/common/java/core/net/i2p/data/i2cp/GetDateMessage.java @@ -12,19 +12,24 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Map; +import java.util.Properties; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; +import net.i2p.util.OrderedProperties; /** - * Request the other side to send us what they think the current time is/ + * Request the other side to send us what they think the current time is. * Only supported from client to router. * * Since 0.8.7, optionally include a version string. + * Since 0.9.11, optionally include options. */ public class GetDateMessage extends I2CPMessageImpl { public final static int MESSAGE_TYPE = 32; private String _version; + private Properties _options; public GetDateMessage() { super(); @@ -39,6 +44,21 @@ public GetDateMessage(String version) { _version = version; } + /** + * @param version the client's version String to be sent to the router; may be null; + * must be non-null if options is non-null and non-empty. + * @param options Client options to be sent to the router; primarily for authentication; may be null; + * keys and values 255 bytes (not chars) max each + * @since 0.9.11 + */ + public GetDateMessage(String version, Properties options) { + super(); + if (version == null && options != null && !options.isEmpty()) + throw new IllegalArgumentException(); + _version = version; + _options = options; + } + /** * @return may be null * @since 0.8.7 @@ -47,11 +67,24 @@ public String getVersion() { return _version; } + /** + * Retrieve any configuration options for the connection. + * Primarily for authentication. + * + * @return may be null + * @since 0.9.11 + */ + public Properties getOptions() { + return _options; + } + @Override protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { if (size > 0) { try { _version = DataHelper.readString(in); + if (size > 1 + _version.length()) // assume ascii + _options = DataHelper.readProperties(in); } catch (DataFormatException dfe) { throw new I2CPMessageException("Bad version string", dfe); } @@ -62,9 +95,11 @@ protected void doReadMessage(InputStream in, int size) throws I2CPMessageExcepti protected byte[] doWriteMessage() throws I2CPMessageException, IOException { if (_version == null) return new byte[0]; - ByteArrayOutputStream os = new ByteArrayOutputStream(16); + ByteArrayOutputStream os = new ByteArrayOutputStream(_options != null ? 128 : 16); try { DataHelper.writeString(os, _version); + if (_options != null && !_options.isEmpty()) + DataHelper.writeProperties(os, _options, true); // UTF-8 } catch (DataFormatException dfe) { throw new I2CPMessageException("Error writing out the message data", dfe); } @@ -80,6 +115,16 @@ public String toString() { StringBuilder buf = new StringBuilder(); buf.append("[GetDateMessage]"); buf.append("\n\tVersion: ").append(_version); + if (_options != null && !_options.isEmpty()) { + buf.append("\n\tOptions: #: ").append(_options.size()); + Properties sorted = new OrderedProperties(); + sorted.putAll(_options); + for (Map.Entry e : sorted.entrySet()) { + String key = (String) e.getKey(); + String val = (String) e.getValue(); + buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); + } + } return buf.toString(); } } diff --git a/common/java/core/net/i2p/data/i2cp/HostLookupMessage.java b/common/java/core/net/i2p/data/i2cp/HostLookupMessage.java new file mode 100644 index 0000000..1a8fcfe --- /dev/null +++ b/common/java/core/net/i2p/data/i2cp/HostLookupMessage.java @@ -0,0 +1,183 @@ +package net.i2p.data.i2cp; + +/* + * Released into the public domain + * with no warranty of any kind, either expressed or implied. + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; + +/** + * Request the router look up the dest for a hash + * or a host. Replaces DestLookupMessage. + * + * @since 0.9.11; do not send to routers older than 0.9.11. + */ +public class HostLookupMessage extends I2CPMessageImpl { + public final static int MESSAGE_TYPE = 38; + + private long _reqID; + private long _timeout; + private int _lookupType; + private Hash _hash; + private String _host; + private SessionId _sessionId; + + public static final int LOOKUP_HASH = 0; + public static final int LOOKUP_HOST = 1; + + private static final long MAX_INT = (1L << 32) - 1; + + public HostLookupMessage() {} + + /** + * @param reqID 0 to 2**32 - 1 + * @param timeout ms 1 to 2**32 - 1 + */ + public HostLookupMessage(SessionId id, Hash h, long reqID, long timeout) { + if (id == null || h == null) + throw new IllegalArgumentException(); + if (reqID < 0 || reqID > MAX_INT) + throw new IllegalArgumentException(); + if (timeout <= 0 || timeout > MAX_INT) + throw new IllegalArgumentException(); + _sessionId = id; + _hash = h; + _reqID = reqID; + _timeout = timeout; + _lookupType = LOOKUP_HASH; + } + + /** + * @param reqID 0 to 2**32 - 1 + * @param timeout ms 1 to 2**32 - 1 + */ + public HostLookupMessage(SessionId id, String host, long reqID, long timeout) { + if (id == null || host == null) + throw new IllegalArgumentException(); + if (reqID < 0 || reqID > MAX_INT) + throw new IllegalArgumentException(); + if (timeout <= 0 || timeout > MAX_INT) + throw new IllegalArgumentException(); + _sessionId = id; + _host = host; + _reqID = reqID; + _timeout = timeout; + _lookupType = LOOKUP_HOST; + } + + public SessionId getSessionId() { + return _sessionId; + } + + /** + * @return 0 to 2**32 - 1 + */ + public long getReqID() { + return _reqID; + } + + /** + * @return ms 1 to 2**32 - 1 + */ + public long getTimeout() { + return _timeout; + } + + /** + * @return 0 (hash) or 1 (host) + */ + public int getLookupType() { + return _lookupType; + } + + /** + * @return only valid if lookup type == 0 + */ + public Hash getHash() { + return _hash; + } + + /** + * @return only valid if lookup type == 1 + */ + public String getHostname() { + return _host; + } + + protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { + try { + _sessionId = new SessionId(); + _sessionId.readBytes(in); + _reqID = DataHelper.readLong(in, 4); + _timeout = DataHelper.readLong(in, 4); + _lookupType = (int) DataHelper.readLong(in, 1); + if (_lookupType == LOOKUP_HASH) { + _hash = Hash.create(in); + } else if (_lookupType == LOOKUP_HOST) { + _host = DataHelper.readString(in); + if (_host.length() == 0) + throw new I2CPMessageException("bad host"); + } else { + throw new I2CPMessageException("bad type"); + } + } catch (DataFormatException dfe) { + throw new I2CPMessageException("bad data", dfe); + } + } + + protected byte[] doWriteMessage() throws I2CPMessageException, IOException { + int len; + if (_lookupType == LOOKUP_HASH) { + if (_hash == null) + throw new I2CPMessageException("Unable to write out the message as there is not enough data"); + len = 11 + Hash.HASH_LENGTH; + } else if (_lookupType == LOOKUP_HOST) { + if (_host == null) + throw new I2CPMessageException("Unable to write out the message as there is not enough data"); + len = 12 + _host.length(); + } else { + throw new I2CPMessageException("bad type"); + } + ByteArrayOutputStream os = new ByteArrayOutputStream(len); + try { + _sessionId.writeBytes(os); + DataHelper.writeLong(os, 4, _reqID); + DataHelper.writeLong(os, 4, _timeout); + DataHelper.writeLong(os, 1, _lookupType); + if (_lookupType == LOOKUP_HASH) { + _hash.writeBytes(os); + } else { + DataHelper.writeString(os, _host); + } + } catch (DataFormatException dfe) { + throw new I2CPMessageException("bad data", dfe); + } + return os.toByteArray(); + } + + public int getType() { + return MESSAGE_TYPE; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("[HostLookupMessage: "); + buf.append("\n\t").append(_sessionId); + buf.append("\n\tReqID: ").append(_reqID); + buf.append("\n\tTimeout: ").append(_timeout); + if (_lookupType == LOOKUP_HASH) + buf.append("\n\tHash: ").append(_hash); + else if (_lookupType == LOOKUP_HOST) + buf.append("\n\tHost: ").append(_host); + buf.append("]"); + return buf.toString(); + } +} diff --git a/common/java/core/net/i2p/data/i2cp/HostReplyMessage.java b/common/java/core/net/i2p/data/i2cp/HostReplyMessage.java new file mode 100644 index 0000000..37faaa2 --- /dev/null +++ b/common/java/core/net/i2p/data/i2cp/HostReplyMessage.java @@ -0,0 +1,146 @@ +package net.i2p.data.i2cp; + +/* + * Released into the public domain + * with no warranty of any kind, either expressed or implied. + * + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.data.Destination; + +/** + * Response to HostLookupMessage. Replaces DestReplyMessage. + * + * @since 0.9.11 + */ +public class HostReplyMessage extends I2CPMessageImpl { + public final static int MESSAGE_TYPE = 39; + + private Destination _dest; + private long _reqID; + private int _code; + private SessionId _sessionId; + + public static final int RESULT_SUCCESS = 0; + /** generic fail, other codes TBD */ + public static final int RESULT_FAILURE = 1; + + private static final long MAX_INT = (1L << 32) - 1; + + public HostReplyMessage() {} + + /** + * A message with RESULT_SUCCESS and a non-null Destination. + * + * @param d non-null + * @param reqID 0 to 2**32 - 1 + */ + public HostReplyMessage(SessionId id, Destination d, long reqID) { + if (id == null || d == null) + throw new IllegalArgumentException(); + if (reqID < 0 || reqID > MAX_INT) + throw new IllegalArgumentException(); + _sessionId = id; + _dest = d; + _reqID = reqID; + } + + /** + * A message with a failure code and no Destination. + * + * @param failureCode 1-255 + * @param reqID from the HostLookup 0 to 2**32 - 1 + */ + public HostReplyMessage(SessionId id, int failureCode, long reqID) { + if (id == null) + throw new IllegalArgumentException(); + if (failureCode <= 0 || failureCode > 255) + throw new IllegalArgumentException(); + if (reqID < 0 || reqID > MAX_INT) + throw new IllegalArgumentException(); + _sessionId = id; + _code = failureCode; + _reqID = reqID; + } + + public SessionId getSessionId() { + return _sessionId; + } + + /** + * @return 0 to 2**32 - 1 + */ + public long getReqID() { + return _reqID; + } + + /** + * @return 0 on success, 1-255 on failure + */ + public int getResultCode() { + return _code; + } + + /** + * @return non-null only if result code is zero + */ + public Destination getDestination() { + return _dest; + } + + protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { + try { + _sessionId = new SessionId(); + _sessionId.readBytes(in); + _reqID = DataHelper.readLong(in, 4); + _code = (int) DataHelper.readLong(in, 1); + if (_code == RESULT_SUCCESS) + _dest = Destination.create(in); + } catch (DataFormatException dfe) { + throw new I2CPMessageException("bad data", dfe); + } + } + + protected byte[] doWriteMessage() throws I2CPMessageException, IOException { + int len = 7; + if (_code == RESULT_SUCCESS) { + if (_dest == null) + throw new I2CPMessageException("Unable to write out the message as there is not enough data"); + len += _dest.size(); + } + ByteArrayOutputStream os = new ByteArrayOutputStream(len); + try { + _sessionId.writeBytes(os); + DataHelper.writeLong(os, 4, _reqID); + DataHelper.writeLong(os, 1, _code); + if (_code == RESULT_SUCCESS) + _dest.writeBytes(os); + } catch (DataFormatException dfe) { + throw new I2CPMessageException("bad data", dfe); + } + return os.toByteArray(); + } + + public int getType() { + return MESSAGE_TYPE; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("[HostReplyMessage: "); + buf.append("\n\t").append(_sessionId); + buf.append("\n\tReqID: ").append(_reqID); + buf.append("\n\tResult: ").append(_code); + if (_code == RESULT_SUCCESS) + buf.append("\n\tDestination: ").append(_dest); + buf.append("]"); + return buf.toString(); + } +} diff --git a/common/java/core/net/i2p/data/i2cp/I2CPMessageHandler.java b/common/java/core/net/i2p/data/i2cp/I2CPMessageHandler.java index 0a62416..915757f 100644 --- a/common/java/core/net/i2p/data/i2cp/I2CPMessageHandler.java +++ b/common/java/core/net/i2p/data/i2cp/I2CPMessageHandler.java @@ -21,6 +21,12 @@ */ public class I2CPMessageHandler { + /** + * This is huge. Mainly to catch a completly bogus response, possibly not an I2CP socket. + * @since 0.9.11 + */ + public static final int MAX_LENGTH = 128*1024; + /** * Read an I2CPMessage from the stream and return the fully populated object. * @@ -31,16 +37,21 @@ public class I2CPMessageHandler { * message - if it is an unknown type or has improper formatting, etc. */ public static I2CPMessage readMessage(InputStream in) throws IOException, I2CPMessageException { - int length = -1; + int length; try { length = (int) DataHelper.readLong(in, 4); } catch (DataFormatException dfe) { throw new IOException("Connection closed"); } + if (length > MAX_LENGTH) + throw new I2CPMessageException("Invalid message length specified"); try { - if (length < 0) throw new I2CPMessageException("Invalid message length specified"); int type = (int) DataHelper.readLong(in, 1); I2CPMessage msg = createMessage(type); + // Note that the readMessage() calls don't, in general, read and discard + // extra data, so we can't add new fields to the end of messages + // in a compatible way. And the readers could read beyond the length too. + // To fix this we'd have to read into a BAOS/BAIS or use a filter input stream msg.readMessage(in, length, type); return msg; } catch (DataFormatException dfe) { @@ -52,7 +63,7 @@ public static I2CPMessage readMessage(InputStream in) throws IOException, I2CPMe * Yes, this is fairly ugly, but its the only place it ever happens. * */ - private static I2CPMessage createMessage(int type) throws IOException, + private static I2CPMessage createMessage(int type) throws I2CPMessageException { switch (type) { case CreateLeaseSetMessage.MESSAGE_TYPE: @@ -97,6 +108,10 @@ private static I2CPMessage createMessage(int type) throws IOException, return new GetBandwidthLimitsMessage(); case BandwidthLimitsMessage.MESSAGE_TYPE: return new BandwidthLimitsMessage(); + case HostLookupMessage.MESSAGE_TYPE: + return new HostLookupMessage(); + case HostReplyMessage.MESSAGE_TYPE: + return new HostReplyMessage(); default: throw new I2CPMessageException("The type " + type + " is an unknown I2CP message"); } diff --git a/common/java/core/net/i2p/data/i2cp/I2CPMessageReader.java b/common/java/core/net/i2p/data/i2cp/I2CPMessageReader.java index 7dd702c..72148a5 100644 --- a/common/java/core/net/i2p/data/i2cp/I2CPMessageReader.java +++ b/common/java/core/net/i2p/data/i2cp/I2CPMessageReader.java @@ -110,7 +110,7 @@ public static interface I2CPMessageEventListener { * reader * * @param reader I2CPMessageReader to notify - * @param error Exception that was thrown + * @param error Exception that was thrown, non-null */ public void readError(I2CPMessageReader reader, Exception error); diff --git a/common/java/core/net/i2p/data/i2cp/SessionConfig.java b/common/java/core/net/i2p/data/i2cp/SessionConfig.java index ee5f898..7e12857 100644 --- a/common/java/core/net/i2p/data/i2cp/SessionConfig.java +++ b/common/java/core/net/i2p/data/i2cp/SessionConfig.java @@ -89,7 +89,8 @@ public Properties getOptions() { } /** - * Configure the session with the given options + * Configure the session with the given options; + * keys and values 255 bytes (not chars) max each * * @param options Properties for this session */ @@ -225,7 +226,7 @@ public String toString() { buf.append("\n\tOptions: #: ").append(_options.size()); Properties sorted = new OrderedProperties(); sorted.putAll(_options); - for (Map.Entry e : sorted.entrySet()) { + for (Map.Entry e : sorted.entrySet()) { String key = (String) e.getKey(); String val = (String) e.getValue(); buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); diff --git a/common/java/core/net/i2p/data/i2cp/SessionId.java b/common/java/core/net/i2p/data/i2cp/SessionId.java index 68f8c1a..2cacd63 100644 --- a/common/java/core/net/i2p/data/i2cp/SessionId.java +++ b/common/java/core/net/i2p/data/i2cp/SessionId.java @@ -29,12 +29,24 @@ public SessionId() { _sessionId = -1; } + /** + * @param id 0-65535 + * @since 0.9.11 + */ + public SessionId(int id) { + if (id < 0 || id > 65535) + throw new IllegalArgumentException(); + _sessionId = id; + } + public int getSessionId() { return _sessionId; } /** @param id 0-65535 */ public void setSessionId(int id) { + if (id < 0 || id > 65535) + throw new IllegalArgumentException(); _sessionId = id; } diff --git a/common/java/apps/net/i2p/kademlia/KBucket.java b/common/java/core/net/i2p/kademlia/KBucket.java similarity index 97% rename from common/java/apps/net/i2p/kademlia/KBucket.java rename to common/java/core/net/i2p/kademlia/KBucket.java index 8c3b85f..0e86df3 100644 --- a/common/java/apps/net/i2p/kademlia/KBucket.java +++ b/common/java/core/net/i2p/kademlia/KBucket.java @@ -17,7 +17,7 @@ * a local key, using XOR as the distance metric * * Refactored from net.i2p.router.networkdb.kademlia - * @since 0.9.2 + * @since 0.9.2 in i2psnark, moved to core in 0.9.10 */ public interface KBucket { diff --git a/common/java/apps/net/i2p/kademlia/KBucketImpl.java b/common/java/core/net/i2p/kademlia/KBucketImpl.java similarity index 98% rename from common/java/apps/net/i2p/kademlia/KBucketImpl.java rename to common/java/core/net/i2p/kademlia/KBucketImpl.java index e3c72c0..ac804b9 100644 --- a/common/java/apps/net/i2p/kademlia/KBucketImpl.java +++ b/common/java/core/net/i2p/kademlia/KBucketImpl.java @@ -41,7 +41,7 @@ * removing entries, this KBucket will exceed the max size. * * Refactored from net.i2p.router.networkdb.kademlia - * @since 0.9.2 + * @since 0.9.2 in i2psnark, moved to core in 0.9.10 */ class KBucketImpl implements KBucket { /** diff --git a/common/java/apps/net/i2p/kademlia/KBucketSet.java b/common/java/core/net/i2p/kademlia/KBucketSet.java similarity index 99% rename from common/java/apps/net/i2p/kademlia/KBucketSet.java rename to common/java/core/net/i2p/kademlia/KBucketSet.java index 542e300..e19d2e5 100644 --- a/common/java/apps/net/i2p/kademlia/KBucketSet.java +++ b/common/java/core/net/i2p/kademlia/KBucketSet.java @@ -35,7 +35,7 @@ * times 2**(B-1) for Kademlia value B. * * Refactored from net.i2p.router.networkdb.kademlia - * @since 0.9.2 + * @since 0.9.2 in i2psnark, moved to core in 0.9.10 */ public class KBucketSet { private final Log _log; @@ -276,6 +276,8 @@ public boolean remove(T entry) { try { kbucket = getBucket(entry); } finally { releaseReadLock(); } + if (kbucket == null) // us + return false; boolean removed = kbucket.remove(entry); return removed; } diff --git a/common/java/apps/net/i2p/kademlia/KBucketTrimmer.java b/common/java/core/net/i2p/kademlia/KBucketTrimmer.java similarity index 91% rename from common/java/apps/net/i2p/kademlia/KBucketTrimmer.java rename to common/java/core/net/i2p/kademlia/KBucketTrimmer.java index b33f85d..fb73737 100644 --- a/common/java/apps/net/i2p/kademlia/KBucketTrimmer.java +++ b/common/java/core/net/i2p/kademlia/KBucketTrimmer.java @@ -4,7 +4,7 @@ /** * Called when a kbucket can no longer be split and is too big - * @since 0.9.2 + * @since 0.9.2 in i2psnark, moved to core in 0.9.10 */ public interface KBucketTrimmer { /** diff --git a/common/java/apps/net/i2p/kademlia/RandomIfOldTrimmer.java b/common/java/core/net/i2p/kademlia/RandomIfOldTrimmer.java similarity index 91% rename from common/java/apps/net/i2p/kademlia/RandomIfOldTrimmer.java rename to common/java/core/net/i2p/kademlia/RandomIfOldTrimmer.java index ade28ce..dc50c8a 100644 --- a/common/java/apps/net/i2p/kademlia/RandomIfOldTrimmer.java +++ b/common/java/core/net/i2p/kademlia/RandomIfOldTrimmer.java @@ -5,7 +5,7 @@ /** * Removes a random element, but only if the bucket hasn't changed in 5 minutes. - * @since 0.9.2 + * @since 0.9.2 in i2psnark, moved to core in 0.9.10 */ public class RandomIfOldTrimmer extends RandomTrimmer { diff --git a/common/java/apps/net/i2p/kademlia/RandomTrimmer.java b/common/java/core/net/i2p/kademlia/RandomTrimmer.java similarity index 87% rename from common/java/apps/net/i2p/kademlia/RandomTrimmer.java rename to common/java/core/net/i2p/kademlia/RandomTrimmer.java index c1efff2..dd65410 100644 --- a/common/java/apps/net/i2p/kademlia/RandomTrimmer.java +++ b/common/java/core/net/i2p/kademlia/RandomTrimmer.java @@ -8,7 +8,7 @@ /** * Removes a random element. Not resistant to flooding. - * @since 0.9.2 + * @since 0.9.2 in i2psnark, moved to core in 0.9.10 */ public class RandomTrimmer implements KBucketTrimmer { protected final I2PAppContext _ctx; @@ -26,6 +26,7 @@ public boolean trim(KBucket kbucket, T toAdd) { if (sz < _max) return true; T toRemove = e.get(_ctx.random().nextInt(sz)); - return kbucket.remove(toRemove); + kbucket.remove(toRemove); + return true; } } diff --git a/common/java/apps/net/i2p/kademlia/RejectTrimmer.java b/common/java/core/net/i2p/kademlia/RejectTrimmer.java similarity index 85% rename from common/java/apps/net/i2p/kademlia/RejectTrimmer.java rename to common/java/core/net/i2p/kademlia/RejectTrimmer.java index 2e29f28..5704541 100644 --- a/common/java/apps/net/i2p/kademlia/RejectTrimmer.java +++ b/common/java/core/net/i2p/kademlia/RejectTrimmer.java @@ -4,7 +4,7 @@ /** * Removes nothing and always rejects the add. Flood resistant.. - * @since 0.9.2 + * @since 0.9.2 in i2psnark, moved to core in 0.9.10 */ public class RejectTrimmer implements KBucketTrimmer { public boolean trim(KBucket kbucket, T toAdd) { diff --git a/common/java/apps/net/i2p/kademlia/SelectionCollector.java b/common/java/core/net/i2p/kademlia/SelectionCollector.java similarity index 80% rename from common/java/apps/net/i2p/kademlia/SelectionCollector.java rename to common/java/core/net/i2p/kademlia/SelectionCollector.java index e4cb770..06a6f09 100644 --- a/common/java/apps/net/i2p/kademlia/SelectionCollector.java +++ b/common/java/core/net/i2p/kademlia/SelectionCollector.java @@ -4,7 +4,7 @@ /** * Visit kbuckets, gathering matches - * @since 0.9.2 + * @since 0.9.2 in i2psnark, moved to core in 0.9.10 */ public interface SelectionCollector { public void add(T entry); diff --git a/common/java/apps/net/i2p/kademlia/XORComparator.java b/common/java/core/net/i2p/kademlia/XORComparator.java similarity index 88% rename from common/java/apps/net/i2p/kademlia/XORComparator.java rename to common/java/core/net/i2p/kademlia/XORComparator.java index 2ac5017..5763a7b 100644 --- a/common/java/apps/net/i2p/kademlia/XORComparator.java +++ b/common/java/core/net/i2p/kademlia/XORComparator.java @@ -7,9 +7,9 @@ /** * Help sort Hashes in relation to a base key using the XOR metric * - * @since 0.9.2 + * @since 0.9.2 in i2psnark, moved to core in 0.9.10 */ -class XORComparator implements Comparator { +public class XORComparator implements Comparator { private final byte[] _base; /** diff --git a/common/java/apps/net/i2p/kademlia/package.html b/common/java/core/net/i2p/kademlia/package.html similarity index 60% rename from common/java/apps/net/i2p/kademlia/package.html rename to common/java/core/net/i2p/kademlia/package.html index fe1a24f..f517b24 100644 --- a/common/java/apps/net/i2p/kademlia/package.html +++ b/common/java/core/net/i2p/kademlia/package.html @@ -1,6 +1,6 @@

    This is a major rewrite of KBucket, KBucketSet, and KBucketImpl from net.i2p.router.networkdb.kademlia. The classes are now generic to support SHA1. SHA256, or other key lengths. -The long-term goal is to prove out this new implementation in i2psnark, -then move it to core, then convert the network database to use it. +Packaged in i2psnark since 0.9.2, and moved to core in 0.9.10 +so the network database can use it.

    diff --git a/common/java/core/net/i2p/stat/StatManager.java b/common/java/core/net/i2p/stat/StatManager.java index d148bbe..b8cf6c4 100644 --- a/common/java/core/net/i2p/stat/StatManager.java +++ b/common/java/core/net/i2p/stat/StatManager.java @@ -2,7 +2,6 @@ import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedSet; @@ -63,8 +62,7 @@ public void shutdown() { public StatLog getStatLog() { return _statLog; } public void setStatLog(StatLog log) { _statLog = log; - for (Iterator iter = _rateStats.values().iterator(); iter.hasNext(); ) { - RateStat rs = iter.next(); + for (RateStat rs : _rateStats.values()) { rs.setStatLog(log); } } @@ -168,8 +166,7 @@ public void coalesceStats() { } } } - for (Iterator iter = _rateStats.values().iterator(); iter.hasNext();) { - RateStat stat = iter.next(); + for (RateStat stat : _rateStats.values()) { if (stat != null) { stat.coalesceStats(); } diff --git a/common/java/core/net/i2p/util/ByteCache.java b/common/java/core/net/i2p/util/ByteCache.java index 303ebb2..5f64e96 100644 --- a/common/java/core/net/i2p/util/ByteCache.java +++ b/common/java/core/net/i2p/util/ByteCache.java @@ -104,10 +104,10 @@ public static void clearAll() { } /** list of available and available entries */ - private Queue _available; + private volatile Queue _available; private int _maxCached; private final int _entrySize; - private long _lastOverflow; + private volatile long _lastOverflow; /** do we actually want to cache? Warning - setting to false may NPE, this should be fixed or removed */ private static final boolean _cache = true; diff --git a/common/java/core/net/i2p/util/Clock.java b/common/java/core/net/i2p/util/Clock.java index 3efbbb7..294d3ff 100644 --- a/common/java/core/net/i2p/util/Clock.java +++ b/common/java/core/net/i2p/util/Clock.java @@ -68,7 +68,6 @@ public void setOffset(long offsetMs) { * @param offsetMs the delta from System.currentTimeMillis() (NOT the delta from now()) */ public synchronized void setOffset(long offsetMs, boolean force) { - if (false) return; long delta = offsetMs - _offset; if (!force) { if ((offsetMs > MAX_OFFSET) || (offsetMs < 0 - MAX_OFFSET)) { @@ -113,7 +112,7 @@ else if (getLog().shouldLog(Log.INFO)) /* * @return the current delta from System.currentTimeMillis() in milliseconds */ - public long getOffset() { + public synchronized long getOffset() { return _offset; } diff --git a/common/java/core/net/i2p/util/EepGet.java b/common/java/core/net/i2p/util/EepGet.java index 45dc5a2..97d84f4 100644 --- a/common/java/core/net/i2p/util/EepGet.java +++ b/common/java/core/net/i2p/util/EepGet.java @@ -384,6 +384,7 @@ public void bytesTransferred(long alreadyTransferred, int currentWrite, long byt fmt.format("%7.2f", Double.valueOf(lifetimeKBps)); buf.append(" KBps"); System.out.println(buf.toString()); + fmt.close(); } _lastComplete = now; } @@ -1155,14 +1156,18 @@ protected String getRequest() throws IOException { // we don't want to transparently gunzip it and save it as a .gz file. (!path.endsWith(".gz")) && (!path.endsWith(".tgz"))) buf.append("gzip"); - buf.append("\r\nUser-Agent: " + USER_AGENT + "\r\n" + - "Connection: close\r\n"); + buf.append("\r\n"); + boolean uaOverridden = false; if (_extraHeaders != null) { for (String hdr : _extraHeaders) { + if (hdr.toLowerCase(Locale.US).startsWith("user-agent: ")) + uaOverridden = true; buf.append(hdr).append("\r\n"); } } - buf.append("\r\n"); + if(!uaOverridden) + buf.append("User-Agent: " + USER_AGENT + "\r\n"); + buf.append("Connection: close\r\n\r\n"); if (post) buf.append(_postData); if (_log.shouldLog(Log.DEBUG)) @@ -1236,6 +1241,9 @@ public void setWriteErrorToOutput() { /** * Add an extra header to the request. * Must be called before fetch(). + * Not supported by EepHead. + * As of 0.9.10, If name is User-Agent, this will replace the default User-Agent header. + * Note that headers may be subsequently modified or removed in the I2PTunnel HTTP Client proxy. * * @since 0.8.8 */ @@ -1249,6 +1257,7 @@ public void addHeader(String name, String value) { * Add basic authorization header for the proxy. * Only added if the request is going through a proxy. * Must be called before fetch(). + * Not supported by EepHead. * * @since 0.8.9 */ diff --git a/common/java/core/net/i2p/util/FileUtil.java b/common/java/core/net/i2p/util/FileUtil.java index 382be54..fd43d8f 100644 --- a/common/java/core/net/i2p/util/FileUtil.java +++ b/common/java/core/net/i2p/util/FileUtil.java @@ -295,7 +295,7 @@ private static void unpack(InputStream in, JarOutputStream out) throws Exception //Pack200.newUnpacker().unpack(in, out); if (!_failedOracle) { try { - Class p200 = Class.forName("java.util.jar.Pack200", true, ClassLoader.getSystemClassLoader()); + Class p200 = Class.forName("java.util.jar.Pack200", true, ClassLoader.getSystemClassLoader()); Method newUnpacker = p200.getMethod("newUnpacker", (Class[]) null); Object unpacker = newUnpacker.invoke(null,(Object[]) null); Method unpack = unpacker.getClass().getMethod("unpack", new Class[] {InputStream.class, JarOutputStream.class}); @@ -316,8 +316,8 @@ private static void unpack(InputStream in, JarOutputStream out) throws Exception //(new Archive(in, out)).unpack(); if (!_failedApache) { try { - Class p200 = Class.forName("org.apache.harmony.unpack200.Archive", true, ClassLoader.getSystemClassLoader()); - Constructor newUnpacker = p200.getConstructor(new Class[] {InputStream.class, JarOutputStream.class}); + Class p200 = Class.forName("org.apache.harmony.unpack200.Archive", true, ClassLoader.getSystemClassLoader()); + Constructor newUnpacker = p200.getConstructor(new Class[] {InputStream.class, JarOutputStream.class}); Object unpacker = newUnpacker.newInstance(new Object[] {in, out}); Method unpack = unpacker.getClass().getMethod("unpack", (Class[]) null); // throws IOException or Pack200Exception @@ -370,8 +370,9 @@ public static String readTextFile(String filename, int maxNumLines, boolean star } } StringBuilder buf = new StringBuilder(lines.size() * 80); - for (int i = 0; i < lines.size(); i++) - buf.append((String)lines.get(i)).append('\n'); + for (int i = 0; i < lines.size(); i++) { + buf.append(lines.get(i)).append('\n'); + } return buf.toString(); } catch (IOException ioe) { return null; diff --git a/common/java/core/net/i2p/util/Log.java b/common/java/core/net/i2p/util/Log.java index 42f54b0..5f6d06e 100644 --- a/common/java/core/net/i2p/util/Log.java +++ b/common/java/core/net/i2p/util/Log.java @@ -25,7 +25,7 @@ * @author jrandom */ public class Log { - private final Class _class; + private final Class _class; private final String _className; private final String _name; private int _minPriority; @@ -75,7 +75,7 @@ public static String toLevelString(int level) { * Warning - not recommended. * Use I2PAppContext.getGlobalContext().logManager().getLog(cls) */ - public Log(Class cls) { + public Log(Class cls) { this(I2PAppContext.getGlobalContext().logManager(), cls, null); _manager.addLog(this); } @@ -89,7 +89,7 @@ public Log(String name) { _manager.addLog(this); } - Log(LogManager manager, Class cls) { + Log(LogManager manager, Class cls) { this(manager, cls, null); } @@ -97,7 +97,7 @@ public Log(String name) { this(manager, null, name); } - Log(LogManager manager, Class cls, String name) { + Log(LogManager manager, Class cls, String name) { _manager = manager; _class = cls; _className = cls != null ? cls.getName() : null; @@ -229,7 +229,7 @@ public String getName() { /** @return the LogScope (private class) */ public Object getScope() { return _scope; } - static String getScope(String name, Class cls) { + static String getScope(String name, Class cls) { if ( (name == null) && (cls == null) ) return "f00"; if (cls == null) return name; if (name == null) return cls.getName(); @@ -239,7 +239,7 @@ static String getScope(String name, Class cls) { private static final class LogScope { private final String _scopeCache; - public LogScope(String name, Class cls) { + public LogScope(String name, Class cls) { _scopeCache = getScope(name, cls); } diff --git a/common/java/core/net/i2p/util/LogManager.java b/common/java/core/net/i2p/util/LogManager.java index bb34822..871037b 100644 --- a/common/java/core/net/i2p/util/LogManager.java +++ b/common/java/core/net/i2p/util/LogManager.java @@ -166,9 +166,9 @@ private synchronized void startLogWriter() { t.start(); } - public Log getLog(Class cls) { return getLog(cls, null); } + public Log getLog(Class cls) { return getLog(cls, null); } public Log getLog(String name) { return getLog(null, name); } - public Log getLog(Class cls, String name) { + public Log getLog(Class cls, String name) { String scope = Log.getScope(name, cls); boolean isNew = false; Log rv = _logs.get(scope); @@ -186,7 +186,7 @@ public Log getLog(Class cls, String name) { /** now used by ConfigLogingHelper */ public List getLogs() { - return new ArrayList(_logs.values()); + return new ArrayList(_logs.values()); } /** @@ -407,7 +407,7 @@ private void parseLimits(Properties config) { private void parseLimits(Properties config, String recordPrefix) { _limits.clear(); if (config != null) { - for (Map.Entry e : config.entrySet()) { + for (Map.Entry e : config.entrySet()) { String key = (String) e.getKey(); // if we're filtering the records (e.g. logger.record.*) then diff --git a/common/java/core/net/i2p/util/LogRecord.java b/common/java/core/net/i2p/util/LogRecord.java index cf6b347..5d45314 100644 --- a/common/java/core/net/i2p/util/LogRecord.java +++ b/common/java/core/net/i2p/util/LogRecord.java @@ -15,14 +15,14 @@ */ class LogRecord { private final long _date; - private final Class _source; + private final Class _source; private final String _name; private final String _threadName; private final int _priority; private final String _message; private final Throwable _throwable; - public LogRecord(Class src, String name, String threadName, int priority, String msg, Throwable t) { + public LogRecord(Class src, String name, String threadName, int priority, String msg, Throwable t) { _date = Clock.getInstance().now(); _source = src; _name = name; @@ -36,7 +36,7 @@ public long getDate() { return _date; } - public Class getSource() { + public Class getSource() { return _source; } diff --git a/common/java/core/net/i2p/util/OrderedProperties.java b/common/java/core/net/i2p/util/OrderedProperties.java index 56323f2..cce7b0e 100644 --- a/common/java/core/net/i2p/util/OrderedProperties.java +++ b/common/java/core/net/i2p/util/OrderedProperties.java @@ -35,19 +35,19 @@ public OrderedProperties() { } @Override - public Set keySet() { - return Collections.unmodifiableSortedSet(new TreeSet(super.keySet())); + public Set keySet() { + return Collections.unmodifiableSortedSet(new TreeSet(super.keySet())); } @Override public Set> entrySet() { - TreeSet> rv = new TreeSet(new EntryComparator()); + TreeSet> rv = new TreeSet>(new EntryComparator()); rv.addAll(super.entrySet()); return Collections.unmodifiableSortedSet(rv); } - private static class EntryComparator implements Comparator { - public int compare(Map.Entry l, Map.Entry r) { + private static class EntryComparator implements Comparator> { + public int compare(Map.Entry l, Map.Entry r) { return ((String)l.getKey()).compareTo(((String)r.getKey())); } } diff --git a/common/java/core/net/i2p/util/PartialEepGet.java b/common/java/core/net/i2p/util/PartialEepGet.java index cc93fbb..4f3cf95 100644 --- a/common/java/core/net/i2p/util/PartialEepGet.java +++ b/common/java/core/net/i2p/util/PartialEepGet.java @@ -5,6 +5,7 @@ import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; +import java.util.Locale; import net.i2p.I2PAppContext; @@ -143,16 +144,20 @@ protected String getRequest() throws IOException { buf.append("\r\n"); buf.append("Cache-control: no-cache\r\n" + - "Pragma: no-cache\r\n"); - // This will be replaced if we are going through I2PTunnelHTTPClient - buf.append("User-Agent: " + USER_AGENT + "\r\n" + + "Pragma: no-cache\r\n" + "Accept-Encoding: \r\n" + "Connection: close\r\n"); + boolean uaOverridden = false; if (_extraHeaders != null) { for (String hdr : _extraHeaders) { + if (hdr.toLowerCase(Locale.US).startsWith("user-agent: ")) + uaOverridden = true; buf.append(hdr).append("\r\n"); } } + // This will be replaced if we are going through I2PTunnelHTTPClient + if(!uaOverridden) + buf.append("User-Agent: " + USER_AGENT + "\r\n"); buf.append("\r\n"); if (_log.shouldLog(Log.DEBUG)) diff --git a/common/java/core/net/i2p/util/SimpleTimer.java b/common/java/core/net/i2p/util/SimpleTimer.java index d4b537e..a5ad981 100644 --- a/common/java/core/net/i2p/util/SimpleTimer.java +++ b/common/java/core/net/i2p/util/SimpleTimer.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -155,8 +154,7 @@ public void addEvent(TimedEvent event, long timeoutMs, boolean useEarliestTime) if ( (_events.size() != _eventTimes.size()) ) { _log.error("Skewed events: " + _events.size() + " for " + _eventTimes.size()); - for (Iterator iter = _eventTimes.keySet().iterator(); iter.hasNext(); ) { - TimedEvent evt = iter.next(); + for (TimedEvent evt : _eventTimes.keySet()) { Long when = _eventTimes.get(evt); TimedEvent cur = _events.get(when); if (cur != evt) { diff --git a/common/java/core/net/i2p/util/SystemVersion.java b/common/java/core/net/i2p/util/SystemVersion.java index 80114d0..639bab0 100644 --- a/common/java/core/net/i2p/util/SystemVersion.java +++ b/common/java/core/net/i2p/util/SystemVersion.java @@ -47,7 +47,7 @@ public abstract class SystemVersion { int sdk = 0; if (_isAndroid) { try { - Class ver = Class.forName("android.os.Build.VERSION", true, ClassLoader.getSystemClassLoader()); + Class ver = Class.forName("android.os.Build.VERSION", true, ClassLoader.getSystemClassLoader()); Field field = ver.getField("SDK_INT"); sdk = field.getInt(null); } catch (Exception e) {} diff --git a/common/java/core/net/i2p/util/Translate.java b/common/java/core/net/i2p/util/Translate.java index 86e9c4e..632658c 100644 --- a/common/java/core/net/i2p/util/Translate.java +++ b/common/java/core/net/i2p/util/Translate.java @@ -25,7 +25,12 @@ */ public abstract class Translate { public static final String PROP_LANG = "routerconsole.lang"; + /** @since 0.9.10 */ + public static final String PROP_COUNTRY = "routerconsole.country"; + /** non-null, two-letter lower case, may be "" */ private static final String _localeLang = Locale.getDefault().getLanguage(); + /** non-null, two-letter upper case, may be "" */ + private static final String _localeCountry = Locale.getDefault().getCountry(); private static final Map _bundles = new ConcurrentHashMap(16); private static final Set _missing = new ConcurrentHashSet(16); /** use to look for untagged strings */ @@ -42,7 +47,7 @@ else if (lang.equals(TEST_LANG)) // shouldnt happen but dont dump the po headers if it does if (key.equals("")) return key; - ResourceBundle bundle = findBundle(bun, lang); + ResourceBundle bundle = findBundle(bun, lang, getCountry(ctx)); if (bundle == null) return key; try { @@ -110,7 +115,7 @@ public static String getString(int n, String s, String p, I2PAppContext ctx, Str return TEST_STRING + '(' + n + ')' + TEST_STRING; ResourceBundle bundle = null; if (!lang.equals("en")) - bundle = findBundle(bun, lang); + bundle = findBundle(bun, lang, getCountry(ctx)); String x; if (bundle == null) x = n == 1 ? s : p; @@ -129,7 +134,10 @@ public static String getString(int n, String s, String p, I2PAppContext ctx, Str } } - /** @return lang in routerconsole.lang property, else current locale */ + /** + * Two-letter lower case + * @return lang in routerconsole.lang property, else current locale + */ public static String getLanguage(I2PAppContext ctx) { String lang = ctx.getProperty(PROP_LANG); if (lang == null || lang.length() <= 0) @@ -137,14 +145,39 @@ public static String getLanguage(I2PAppContext ctx) { return lang; } - /** cache both found and not found for speed */ - private static ResourceBundle findBundle(String bun, String lang) { - String key = bun + '-' + lang; + /** + * Two-letter upper case or "" + * @return country in routerconsole.country property, else current locale + * @since 0.9.10 + */ + public static String getCountry(I2PAppContext ctx) { + // property may be empty so we don't have a non-default + // language and a default country + return ctx.getProperty(PROP_COUNTRY, _localeCountry); + } + + /** + * cache both found and not found for speed + * @param lang non-null, if "" returns null + * @param country non-null, may be "" + * @return null if not found + */ + private static ResourceBundle findBundle(String bun, String lang, String country) { + String key = bun + '-' + lang + '-' + country; ResourceBundle rv = _bundles.get(key); if (rv == null && !_missing.contains(key)) { + if ("".equals(lang)) { + _missing.add(key); + return null; + } try { + Locale loc; + if ("".equals(country)) + loc = new Locale(lang); + else + loc = new Locale(lang, country); // We must specify the class loader so that a webapp can find the bundle in the .war - rv = ResourceBundle.getBundle(bun, new Locale(lang), Thread.currentThread().getContextClassLoader()); + rv = ResourceBundle.getBundle(bun, loc, Thread.currentThread().getContextClassLoader()); if (rv != null) _bundles.put(key, rv); } catch (MissingResourceException e) { diff --git a/common/java/router/net/i2p/router/Blocklist.java b/common/java/router/net/i2p/router/Blocklist.java index 4c3b840..9e71101 100644 --- a/common/java/router/net/i2p/router/Blocklist.java +++ b/common/java/router/net/i2p/router/Blocklist.java @@ -137,8 +137,7 @@ public void runJob() { return; } } - for (Iterator iter = _peerBlocklist.keySet().iterator(); iter.hasNext(); ) { - Hash peer = iter.next(); + for (Hash peer : _peerBlocklist.keySet()) { String reason; String comment = _peerBlocklist.get(peer); if (comment != null) @@ -215,10 +214,10 @@ private void readBlocklistFile(String file) { int badcount = 0; int peercount = 0; long ipcount = 0; - FileInputStream in = null; + BufferedReader br = null; try { - in = new FileInputStream(BLFile); - BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + br = new BufferedReader(new InputStreamReader( + new FileInputStream(BLFile), "UTF-8")); String buf = null; while ((buf = br.readLine()) != null && count < maxSize) { Entry e = parse(buf, true); @@ -247,7 +246,7 @@ private void readBlocklistFile(String file) { _log.log(Log.CRIT, "OOM reading the blocklist"); return; } finally { - if (in != null) try { in.close(); } catch (IOException ioe) {} + if (br != null) try { br.close(); } catch (IOException ioe) {} } if (_wrapSave != null) { @@ -405,10 +404,10 @@ private Entry parse(String buf, boolean shouldLog) { private int getSize(File BLFile) { if ( (!BLFile.exists()) || (BLFile.length() <= 0) ) return 0; int lines = 0; - FileInputStream in = null; + BufferedReader br = null; try { - in = new FileInputStream(BLFile); - BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); + br = new BufferedReader(new InputStreamReader( + new FileInputStream(BLFile), "ISO-8859-1")); while (br.readLine() != null) { lines++; } @@ -417,7 +416,7 @@ private int getSize(File BLFile) { _log.warn("Error reading the BLFile", ioe); return 0; } finally { - if (in != null) try { in.close(); } catch (IOException ioe) {} + if (br != null) try { br.close(); } catch (IOException ioe) {} } return lines; } @@ -770,10 +769,10 @@ private synchronized void banlistForever(Hash peer, List ips) { for (Iterator iter = ips.iterator(); iter.hasNext(); ) { byte ip[] = iter.next(); int ipint = toInt(ip); - FileInputStream in = null; + BufferedReader br = null; try { - in = new FileInputStream(BLFile); - BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + br = new BufferedReader(new InputStreamReader( + new FileInputStream(BLFile), "UTF-8")); String buf = null; // assume the file is unsorted, so go through the whole thing while ((buf = br.readLine()) != null) { @@ -782,7 +781,7 @@ private synchronized void banlistForever(Hash peer, List ips) { continue; } if (match(ipint, toEntry(e.ip1, e.ip2))) { - try { in.close(); } catch (IOException ioe) {} + try { br.close(); } catch (IOException ioe) {} String reason = _x("IP banned by blocklist.txt entry {0}"); // only one translate parameter for now //for (int i = 0; i < 4; i++) { @@ -801,7 +800,7 @@ private synchronized void banlistForever(Hash peer, List ips) { if (_log.shouldLog(Log.WARN)) _log.warn("Error reading the BLFile", ioe); } finally { - if (in != null) try { in.close(); } catch (IOException ioe) {} + if (br != null) try { br.close(); } catch (IOException ioe) {} } } // We already banlisted in banlist(peer), that's good enough diff --git a/common/java/router/net/i2p/router/ClientTunnelSettings.java b/common/java/router/net/i2p/router/ClientTunnelSettings.java index ac8139e..110e2e9 100644 --- a/common/java/router/net/i2p/router/ClientTunnelSettings.java +++ b/common/java/router/net/i2p/router/ClientTunnelSettings.java @@ -8,7 +8,7 @@ * */ -import java.util.Iterator; +import java.util.Map; import java.util.Properties; import net.i2p.data.Hash; @@ -49,9 +49,9 @@ public String toString() { writeToProperties(p); buf.append("Client tunnel settings:\n"); buf.append("====================================\n"); - for (Iterator iter = p.keySet().iterator(); iter.hasNext(); ) { - String name = (String)iter.next(); - String val = p.getProperty(name); + for (Map.Entry entry : p.entrySet()) { + String name = (String) entry.getKey(); + String val = (String) entry.getValue(); buf.append(name).append(" = [").append(val).append("]\n"); } buf.append("====================================\n"); diff --git a/common/java/router/net/i2p/router/JobQueue.java b/common/java/router/net/i2p/router/JobQueue.java index ffe7472..f5578cc 100644 --- a/common/java/router/net/i2p/router/JobQueue.java +++ b/common/java/router/net/i2p/router/JobQueue.java @@ -277,7 +277,7 @@ private boolean shouldDrop(Job job, long numReady) { if (_maxWaitingJobs <= 0) return false; // dont ever drop jobs if (!_allowParallelOperation) return false; // dont drop during startup [duh] if (numReady > _maxWaitingJobs) { - Class cls = job.getClass(); + Class cls = job.getClass(); // lets not try to drop too many tunnel messages... //if (cls == HandleTunnelMessageJob.class) // return true; diff --git a/common/java/router/net/i2p/router/MultiRouter.java b/common/java/router/net/i2p/router/MultiRouter.java index 8d5f381..6e43468 100644 --- a/common/java/router/net/i2p/router/MultiRouter.java +++ b/common/java/router/net/i2p/router/MultiRouter.java @@ -67,13 +67,16 @@ public static void main(String args[]) { Scanner scan = new Scanner(args[0]); if (!scan.hasNextInt()) { usage(); + scan.close(); return; } nbrRouters = scan.nextInt(); if (nbrRouters < 0) { usage(); + scan.close(); return; } + scan.close(); _out = System.out; diff --git a/common/java/router/net/i2p/router/NetworkDatabaseFacade.java b/common/java/router/net/i2p/router/NetworkDatabaseFacade.java index be0ce34..7fc3c50 100644 --- a/common/java/router/net/i2p/router/NetworkDatabaseFacade.java +++ b/common/java/router/net/i2p/router/NetworkDatabaseFacade.java @@ -40,6 +40,14 @@ public abstract class NetworkDatabaseFacade implements Service { */ public abstract DatabaseEntry lookupLocally(Hash key); public abstract void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs); + + /** + * Lookup using the client's tunnels + * @param fromLocalDest use these tunnels for the lookup, or null for exploratory + * @since 0.9.10 + */ + public abstract void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, Hash fromLocalDest); + public abstract LeaseSet lookupLeaseSetLocally(Hash key); public abstract void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs); public abstract RouterInfo lookupRouterInfoLocally(Hash key); @@ -74,7 +82,8 @@ public abstract class NetworkDatabaseFacade implements Service { public int getKnownLeaseSets() { return 0; } public boolean isInitialized() { return true; } public void rescan() {} - /** @deprecated moved to router console */ + + /** Debug only - all user info moved to NetDbRenderer in router console */ public void renderStatusHTML(Writer out) throws IOException {} /** public for NetDbRenderer in routerconsole */ public Set getLeases() { return Collections.emptySet(); } diff --git a/common/java/router/net/i2p/router/OutNetMessage.java b/common/java/router/net/i2p/router/OutNetMessage.java index eb9815c..f1aa551 100644 --- a/common/java/router/net/i2p/router/OutNetMessage.java +++ b/common/java/router/net/i2p/router/OutNetMessage.java @@ -57,7 +57,6 @@ public class OutNetMessage implements CDPQEntry { * (some JVMs have less than 10ms resolution, so the Long above doesn't guarantee order) */ private List _timestampOrder; - private Object _preparationBuf; /** * Priorities, higher is higher priority. @@ -151,7 +150,7 @@ public Map getTimestamps() { if (_log.shouldLog(Log.INFO)) { synchronized (this) { locked_initTimestamps(); - return (Map)_timestamps.clone(); + return new HashMap(_timestamps); } } return Collections.emptyMap(); @@ -292,16 +291,6 @@ public synchronized Set getFailedTransports() { public void beginSend() { _sendBegin = _context.clock().now(); } - public void prepared(Object buf) { - _preparationBuf = buf; - } - - public Object releasePreparationBuffer() { - Object rv = _preparationBuf; - _preparationBuf = null; - return rv; - } - public long getCreated() { return _created; } /** time since the message was created */ diff --git a/common/java/router/net/i2p/router/PersistentKeyRing.java b/common/java/router/net/i2p/router/PersistentKeyRing.java index 1a8ec46..771d204 100644 --- a/common/java/router/net/i2p/router/PersistentKeyRing.java +++ b/common/java/router/net/i2p/router/PersistentKeyRing.java @@ -3,8 +3,6 @@ import java.io.IOException; import java.io.Writer; -import java.util.Iterator; - import net.i2p.data.DataFormatException; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -43,8 +41,7 @@ public SessionKey remove(Hash h) { } private void addFromProperties() { - for (Iterator iter = _ctx.getPropertyNames().iterator(); iter.hasNext(); ) { - String prop = (String) iter.next(); + for (String prop : _ctx.getPropertyNames()) { if (!prop.startsWith(PROP_PFX)) continue; String key = _ctx.getProperty(prop); diff --git a/common/java/router/net/i2p/router/Router.java b/common/java/router/net/i2p/router/Router.java index 4d10f93..ebdcdc1 100644 --- a/common/java/router/net/i2p/router/Router.java +++ b/common/java/router/net/i2p/router/Router.java @@ -229,13 +229,28 @@ public Router(String configFilename, Properties envProps) { // for the ping file // Check for other router but do not start a thread yet so the update doesn't cause // a NCDFE - if (!isOnlyRouterRunning()) { - _eventLog.addEvent(EventLog.ABORTED, "Another router running"); - System.err.println("ERROR: There appears to be another router already running!"); - System.err.println(" Please make sure to shut down old instances before starting up"); - System.err.println(" a new one. If you are positive that no other instance is running,"); - System.err.println(" please delete the file " + getPingFile().getAbsolutePath()); - System.exit(-1); + for (int i = 0; i < 14; i++) { + // Wrapper can start us up too quickly after a crash, the ping file + // may still be less than LIVELINESS_DELAY (60s) old. + // So wait at least 60s to be sure. + if (isOnlyRouterRunning()) { + if (i > 0) + System.err.println("INFO: No, there wasn't another router already running. Proceeding with startup."); + break; + } + if (i < 13) { + if (i == 0) + System.err.println("WARN: There may be another router already running. Waiting a while to be sure..."); + // yes this is ugly to sleep in the constructor. + try { Thread.sleep(5000); } catch (InterruptedException ie) {} + } else { + _eventLog.addEvent(EventLog.ABORTED, "Another router running"); + System.err.println("ERROR: There appears to be another router already running!"); + System.err.println(" Please make sure to shut down old instances before starting up"); + System.err.println(" a new one. If you are positive that no other instance is running,"); + System.err.println(" please delete the file " + getPingFile().getAbsolutePath()); + System.exit(-1); + } } if (_config.get("router.firstVersion") == null) { diff --git a/common/java/router/net/i2p/router/RouterContext.java b/common/java/router/net/i2p/router/RouterContext.java index 6af37e5..ba837cc 100644 --- a/common/java/router/net/i2p/router/RouterContext.java +++ b/common/java/router/net/i2p/router/RouterContext.java @@ -8,6 +8,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import net.i2p.I2PAppContext; +import net.i2p.app.ClientAppManager; import net.i2p.data.Hash; import net.i2p.data.RouterInfo; import net.i2p.internal.InternalClientManager; @@ -586,7 +587,18 @@ public void unregisterUpdateManager(UpdateManager mgr) { * @return the manager * @since 0.9.4 */ - public RouterAppManager clientAppManager() { + @Override + public ClientAppManager clientAppManager() { + return _appManager; + } + + /** + * The RouterAppManager. + * For convenience, same as clientAppManager(), no cast required + * @return the manager + * @since 0.9.11 + */ + public RouterAppManager routerAppManager() { return _appManager; } } diff --git a/common/java/router/net/i2p/router/StatisticsManager.java b/common/java/router/net/i2p/router/StatisticsManager.java index 6af874f..b485672 100644 --- a/common/java/router/net/i2p/router/StatisticsManager.java +++ b/common/java/router/net/i2p/router/StatisticsManager.java @@ -31,7 +31,7 @@ public class StatisticsManager implements Service { public final static String PROP_PUBLISH_RANKINGS = "router.publishPeerRankings"; /** enhance anonymity by only including build stats one out of this many times */ - private static final int RANDOM_INCLUDE_STATS = 12; + private static final int RANDOM_INCLUDE_STATS = 16; private final DecimalFormat _fmt; private final DecimalFormat _pct; diff --git a/common/java/router/net/i2p/router/TunnelPoolSettings.java b/common/java/router/net/i2p/router/TunnelPoolSettings.java index 1c52e3f..bc9e73c 100644 --- a/common/java/router/net/i2p/router/TunnelPoolSettings.java +++ b/common/java/router/net/i2p/router/TunnelPoolSettings.java @@ -5,6 +5,7 @@ import java.util.Properties; import net.i2p.data.Hash; +import net.i2p.util.NativeBigInteger; import net.i2p.util.RandomSource; import net.i2p.util.SystemVersion; @@ -56,8 +57,22 @@ public class TunnelPoolSettings { // public static final int DEFAULT_REBUILD_PERIOD = 60*1000; public static final int DEFAULT_DURATION = 10*60*1000; //public static final int DEFAULT_LENGTH = SystemVersion.isAndroid() ? 2 : 3; - public static final int DEFAULT_LENGTH = 2; - public static final int DEFAULT_LENGTH_VARIANCE = 0; + + private static final boolean isSlow = SystemVersion.isGNU() || + SystemVersion.isARM() || + SystemVersion.isApache() || + !NativeBigInteger.isNative(); + + /** client only */ + private static final int DEFAULT_IB_LENGTH = 3; + private static final int DEFAULT_OB_LENGTH = 3; + private static final int DEFAULT_LENGTH_VARIANCE = 0; + /** expl only */ + private static final int DEFAULT_IB_EXPL_LENGTH = 2; + private static final int DEFAULT_OB_EXPL_LENGTH = 2; + private static final int DEFAULT_IB_EXPL_LENGTH_VARIANCE = isSlow ? 0 : 1; + private static final int DEFAULT_OB_EXPL_LENGTH_VARIANCE = isSlow ? 0 : 1; + public static final boolean DEFAULT_ALLOW_ZERO_HOP = true; public static final int DEFAULT_IP_RESTRICTION = 2; // class B (/16) private static final int MIN_PRIORITY = -25; @@ -72,8 +87,13 @@ public TunnelPoolSettings(Hash dest, boolean isExploratory, boolean isInbound) { _backupQuantity = DEFAULT_BACKUP_QUANTITY; // _rebuildPeriod = DEFAULT_REBUILD_PERIOD; //_duration = DEFAULT_DURATION; - _length = DEFAULT_LENGTH; - _lengthVariance = DEFAULT_LENGTH_VARIANCE; + if (isInbound) { + _length = isExploratory ? DEFAULT_IB_EXPL_LENGTH : DEFAULT_IB_LENGTH; + _lengthVariance = isExploratory ? DEFAULT_IB_EXPL_LENGTH_VARIANCE : DEFAULT_LENGTH_VARIANCE; + } else { + _length = isExploratory ? DEFAULT_OB_EXPL_LENGTH : DEFAULT_OB_LENGTH; + _lengthVariance = isExploratory ? DEFAULT_OB_EXPL_LENGTH_VARIANCE : DEFAULT_LENGTH_VARIANCE; + } _lengthOverride = -1; if (isExploratory) _allowZeroHop = true; @@ -201,7 +221,7 @@ public void setAllowZeroHop(boolean ok) { * @param prefix non-null */ public void readFromProperties(String prefix, Map props) { - for (Map.Entry e : props.entrySet()) { + for (Map.Entry e : props.entrySet()) { String name = (String) e.getKey(); String value = (String) e.getValue(); if (name.startsWith(prefix)) { @@ -212,9 +232,15 @@ else if (name.equalsIgnoreCase(prefix + PROP_BACKUP_QUANTITY)) //else if (name.equalsIgnoreCase(prefix + PROP_DURATION)) // _duration = getInt(value, DEFAULT_DURATION); else if (name.equalsIgnoreCase(prefix + PROP_LENGTH)) - _length = getInt(value, DEFAULT_LENGTH); + _length = getInt(value, _isInbound ? + (_isExploratory ? DEFAULT_IB_EXPL_LENGTH : DEFAULT_IB_LENGTH) : + (_isExploratory ? DEFAULT_OB_EXPL_LENGTH : DEFAULT_OB_LENGTH)); else if (name.equalsIgnoreCase(prefix + PROP_LENGTH_VARIANCE)) - _lengthVariance = getInt(value, DEFAULT_LENGTH_VARIANCE); + _lengthVariance = getInt(value, _isExploratory ? + (_isInbound ? + DEFAULT_IB_EXPL_LENGTH_VARIANCE : + DEFAULT_OB_EXPL_LENGTH_VARIANCE) : + DEFAULT_LENGTH_VARIANCE); else if (name.equalsIgnoreCase(prefix + PROP_QUANTITY)) _quantity = getInt(value, DEFAULT_QUANTITY); // else if (name.equalsIgnoreCase(prefix + PROP_REBUILD_PERIOD)) @@ -250,7 +276,7 @@ public void writeToProperties(String prefix, Properties props) { props.setProperty(prefix + PROP_IP_RESTRICTION, ""+_IPRestriction); if (!_isInbound) props.setProperty(prefix + PROP_PRIORITY, Integer.toString(_priority)); - for (Map.Entry e : _unknownOptions.entrySet()) { + for (Map.Entry e : _unknownOptions.entrySet()) { String name = (String) e.getKey(); String val = (String) e.getValue(); props.setProperty(prefix + name, val); @@ -264,7 +290,7 @@ public String toString() { writeToProperties("", p); buf.append("Tunnel pool settings:\n"); buf.append("====================================\n"); - for (Map.Entry e : p.entrySet()) { + for (Map.Entry e : p.entrySet()) { String name = (String) e.getKey(); String val = (String) e.getValue(); buf.append(name).append(" = [").append(val).append("]\n"); diff --git a/common/java/router/net/i2p/router/client/ClientConnectionRunner.java b/common/java/router/net/i2p/router/client/ClientConnectionRunner.java index e73041d..5fa987d 100644 --- a/common/java/router/net/i2p/router/client/ClientConnectionRunner.java +++ b/common/java/router/net/i2p/router/client/ClientConnectionRunner.java @@ -182,7 +182,10 @@ public synchronized void stopRunning() { //_manager = null; } - /** current client's config */ + /** + * Current client's config, + * will be null before session is established + */ public SessionConfig getConfig() { return _config; } /** @@ -209,6 +212,10 @@ public String getClientVersion() { public LeaseSet getLeaseSet() { return _currentLeaseSet; } void setLeaseSet(LeaseSet ls) { _currentLeaseSet = ls; } + /** + * Equivalent to getConfig().getDestination().calculateHash(); + * will be null before session is established + */ public Hash getDestHash() { return _destHashCache; } /** current client's sessionId */ @@ -335,12 +342,14 @@ void leaseSetCreated(LeaseSet ls) { * This is always bad. * See ClientMessageEventListener.handleCreateSession() * for why we don't send a SessionStatusMessage when we do this. + * @param reason will be truncated to 255 bytes */ void disconnectClient(String reason) { disconnectClient(reason, Log.ERROR); } /** + * @param reason will be truncated to 255 bytes * @param logLevel e.g. Log.WARN * @since 0.8.2 */ @@ -351,6 +360,8 @@ void disconnectClient(String reason, int logLevel) { + " config: " + _config); DisconnectMessage msg = new DisconnectMessage(); + if (reason.length() > 255) + reason = reason.substring(0, 255); msg.setReason(reason); try { doSend(msg); diff --git a/common/java/router/net/i2p/router/client/ClientManager.java b/common/java/router/net/i2p/router/client/ClientManager.java index 5ad4527..02b60d3 100644 --- a/common/java/router/net/i2p/router/client/ClientManager.java +++ b/common/java/router/net/i2p/router/client/ClientManager.java @@ -12,7 +12,6 @@ import java.io.Writer; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; @@ -125,19 +124,16 @@ public synchronized void shutdown(String msg) { _listener.stopListening(); Set runners = new HashSet(); synchronized (_runners) { - for (Iterator iter = _runners.values().iterator(); iter.hasNext();) { - ClientConnectionRunner runner = iter.next(); + for (ClientConnectionRunner runner : _runners.values()) { runners.add(runner); } } synchronized (_pendingRunners) { - for (Iterator iter = _pendingRunners.iterator(); iter.hasNext();) { - ClientConnectionRunner runner = iter.next(); + for (ClientConnectionRunner runner : _pendingRunners) { runners.add(runner); } } - for (Iterator iter = runners.iterator(); iter.hasNext(); ) { - ClientConnectionRunner runner = iter.next(); + for (ClientConnectionRunner runner : runners) { runner.disconnectClient(msg, Log.WARN); } _runnersByHash.clear(); diff --git a/common/java/router/net/i2p/router/client/ClientMessageEventListener.java b/common/java/router/net/i2p/router/client/ClientMessageEventListener.java index 4c6b5dd..7707813 100644 --- a/common/java/router/net/i2p/router/client/ClientMessageEventListener.java +++ b/common/java/router/net/i2p/router/client/ClientMessageEventListener.java @@ -20,6 +20,7 @@ import net.i2p.data.i2cp.DestroySessionMessage; import net.i2p.data.i2cp.GetBandwidthLimitsMessage; import net.i2p.data.i2cp.GetDateMessage; +import net.i2p.data.i2cp.HostLookupMessage; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageException; import net.i2p.data.i2cp.I2CPMessageReader; @@ -50,8 +51,11 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi protected final RouterContext _context; protected final ClientConnectionRunner _runner; private final boolean _enforceAuth; + private volatile boolean _authorized; private static final String PROP_AUTH = "i2cp.auth"; + /** if true, user/pw must be in GetDateMessage */ + private static final String PROP_AUTH_STRICT = "i2cp.strictAuth"; /** * @param enforceAuth set false for in-JVM, true for socket access @@ -61,6 +65,8 @@ public ClientMessageEventListener(RouterContext context, ClientConnectionRunner _log = _context.logManager().getLog(ClientMessageEventListener.class); _runner = runner; _enforceAuth = enforceAuth; + if ((!_enforceAuth) || !_context.getBooleanProperty(PROP_AUTH)) + _authorized = true; _context.statManager().createRateStat("client.distributeTime", "How long it took to inject the client message into the router", "ClientMessages", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); } @@ -72,6 +78,20 @@ public void messageReceived(I2CPMessageReader reader, I2CPMessage message) { if (_runner.isDead()) return; if (_log.shouldLog(Log.DEBUG)) _log.debug("Message received: \n" + message); + int type = message.getType(); + if (!_authorized) { + // TODO change to default true + boolean strict = _context.getBooleanProperty(PROP_AUTH_STRICT); + if ((strict && type != GetDateMessage.MESSAGE_TYPE) || + (type != CreateSessionMessage.MESSAGE_TYPE && + type != GetDateMessage.MESSAGE_TYPE && + type != DestLookupMessage.MESSAGE_TYPE && + type != GetBandwidthLimitsMessage.MESSAGE_TYPE)) { + _log.error("Received message type " + type + " without required authentication"); + _runner.disconnectClient("Authorization required"); + return; + } + } switch (message.getType()) { case GetDateMessage.MESSAGE_TYPE: handleGetDate((GetDateMessage)message); @@ -103,6 +123,9 @@ public void messageReceived(I2CPMessageReader reader, I2CPMessage message) { case DestLookupMessage.MESSAGE_TYPE: handleDestLookup((DestLookupMessage)message); break; + case HostLookupMessage.MESSAGE_TYPE: + handleHostLookup((HostLookupMessage)message); + break; case ReconfigureSessionMessage.MESSAGE_TYPE: handleReconfigureSession((ReconfigureSessionMessage)message); break; @@ -124,6 +147,8 @@ public void readError(I2CPMessageReader reader, Exception error) { if (_log.shouldLog(Log.ERROR)) _log.error("Error occurred", error); // Is this is a little drastic for an unknown message type? + // Send the whole exception string over for diagnostics + _runner.disconnectClient(error.toString()); _runner.stopRunning(); } @@ -137,6 +162,9 @@ private void handleGetDate(GetDateMessage message) { String clientVersion = message.getVersion(); if (clientVersion != null) _runner.setClientVersion(clientVersion); + Properties props = message.getOptions(); + if (!checkAuth(props)) + return; try { // only send version if the client can handle it (0.8.7 or greater) _runner.doSend(new SetDateMessage(clientVersion != null ? CoreVersion.VERSION : null)); @@ -174,24 +202,9 @@ private void handleCreateSession(CreateSessionMessage message) { } // Auth, since 0.8.2 - if (_enforceAuth && _context.getBooleanProperty(PROP_AUTH)) { - Properties props = in.getOptions(); - String user = props.getProperty("i2cp.username"); - String pw = props.getProperty("i2cp.password"); - if (user == null || user.length() == 0 || pw == null || pw.length() == 0) { - _log.error("I2CP auth failed for client: " + props.getProperty("inbound.nickname")); - _runner.disconnectClient("Authorization required to create session, specify i2cp.username and i2cp.password in session options"); - return; - } - PasswordManager mgr = new PasswordManager(_context); - if (!mgr.checkHash(PROP_AUTH, user, pw)) { - _log.error("I2CP auth failed for client: " + props.getProperty("inbound.nickname") + " user: " + user); - _runner.disconnectClient("Authorization failed for Create Session, user = " + user); - return; - } - if (_log.shouldLog(Log.INFO)) - _log.info("I2CP auth success for client: " + props.getProperty("inbound.nickname") + " user: " + user); - } + Properties inProps = in.getOptions(); + if (!checkAuth(inProps)) + return; SessionId sessionId = new SessionId(); sessionId.setSessionId(getNextSessionId()); @@ -209,10 +222,48 @@ private void handleCreateSession(CreateSessionMessage message) { _runner.sessionEstablished(cfg); if (_log.shouldLog(Log.DEBUG)) - _log.debug("after sessionEstablished for " + message.getSessionConfig().getDestination().calculateHash().toBase64()); + _log.debug("after sessionEstablished for " + _runner.getDestHash()); startCreateSessionJob(); } + /** + * Side effect - sets _authorized. + * Side effect - disconnects session if not authorized. + * + * @param props contains i2cp.username and i2cp.password, may be null + * @return success + * @since 0.9.11 + */ + private boolean checkAuth(Properties props) { + if (_authorized) + return true; + if (_enforceAuth && _context.getBooleanProperty(PROP_AUTH)) { + String user = null; + String pw = null; + if (props != null) { + user = props.getProperty("i2cp.username"); + pw = props.getProperty("i2cp.password"); + } + if (user == null || user.length() == 0 || pw == null || pw.length() == 0) { + _log.error("I2CP auth failed"); + _runner.disconnectClient("Authorization required, specify i2cp.username and i2cp.password in options"); + _authorized = false; + return false; + } + PasswordManager mgr = new PasswordManager(_context); + if (!mgr.checkHash(PROP_AUTH, user, pw)) { + _log.error("I2CP auth failed user: " + user); + _runner.disconnectClient("Authorization failed, user = " + user); + _authorized = false; + return false; + } + if (_log.shouldLog(Log.INFO)) + _log.info("I2CP auth success user: " + user); + } + _authorized = true; + return true; + } + /** * Override for testing * @since 0.9.8 @@ -289,14 +340,22 @@ protected void handleCreateLeaseSet(CreateLeaseSetMessage message) { if ( (message.getLeaseSet() == null) || (message.getPrivateKey() == null) || (message.getSigningPrivateKey() == null) ) { if (_log.shouldLog(Log.ERROR)) _log.error("Null lease set granted: " + message); + _runner.disconnectClient("Invalid CreateLeaseSetMessage"); return; } + _context.keyManager().registerKeys(message.getLeaseSet().getDestination(), message.getSigningPrivateKey(), message.getPrivateKey()); + try { + _context.netDb().publish(message.getLeaseSet()); + } catch (IllegalArgumentException iae) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Invalid leaseset from client", iae); + _runner.disconnectClient("Invalid leaseset: " + iae); + return; + } if (_log.shouldLog(Log.INFO)) _log.info("New lease set granted for destination " - + message.getLeaseSet().getDestination().calculateHash().toBase64()); - _context.keyManager().registerKeys(message.getLeaseSet().getDestination(), message.getSigningPrivateKey(), message.getPrivateKey()); - _context.netDb().publish(message.getLeaseSet()); + + _runner.getDestHash()); // leaseSetCreated takes care of all the LeaseRequestState stuff (including firing any jobs) _runner.leaseSetCreated(message.getLeaseSet()); @@ -304,7 +363,19 @@ protected void handleCreateLeaseSet(CreateLeaseSetMessage message) { /** override for testing */ protected void handleDestLookup(DestLookupMessage message) { - _context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getHash())); + _context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getHash(), + _runner.getDestHash())); + } + + /** + * override for testing + * @since 0.9.11 + */ + protected void handleHostLookup(HostLookupMessage message) { + _context.jobQueue().addJob(new LookupDestJob(_context, _runner, message.getReqID(), + message.getTimeout(), message.getSessionId(), + message.getHash(), message.getHostname(), + _runner.getDestHash())); } /** @@ -325,7 +396,7 @@ private void handleReconfigureSession(ReconfigureSessionMessage message) { return; } _runner.getConfig().getOptions().putAll(message.getSessionConfig().getOptions()); - Hash dest = _runner.getConfig().getDestination().calculateHash(); + Hash dest = _runner.getDestHash(); ClientTunnelSettings settings = new ClientTunnelSettings(dest); Properties props = new Properties(); props.putAll(_runner.getConfig().getOptions()); diff --git a/common/java/router/net/i2p/router/client/LookupDestJob.java b/common/java/router/net/i2p/router/client/LookupDestJob.java index 0174015..be08388 100644 --- a/common/java/router/net/i2p/router/client/LookupDestJob.java +++ b/common/java/router/net/i2p/router/client/LookupDestJob.java @@ -4,32 +4,92 @@ */ package net.i2p.router.client; +import java.util.Locale; + +import net.i2p.data.Base32; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; import net.i2p.data.i2cp.DestReplyMessage; +import net.i2p.data.i2cp.HostReplyMessage; +import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageException; +import net.i2p.data.i2cp.SessionId; import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; /** - * Look up the lease of a hash, to convert it to a Destination for the client + * Look up the lease of a hash, to convert it to a Destination for the client. + * Or, since 0.9.11, lookup a host name in the naming service. */ class LookupDestJob extends JobImpl { private final ClientConnectionRunner _runner; + private final long _reqID; + private final long _timeout; private final Hash _hash; + private final String _name; + private final SessionId _sessID; + private final Hash _fromLocalDest; + + private static final long DEFAULT_TIMEOUT = 15*1000; + + public LookupDestJob(RouterContext context, ClientConnectionRunner runner, Hash h, Hash fromLocalDest) { + this(context, runner, -1, DEFAULT_TIMEOUT, null, h, null, fromLocalDest); + } - public LookupDestJob(RouterContext context, ClientConnectionRunner runner, Hash h) { + /** + * One of h or name non-null + * @param reqID must be >= 0 if name != null + * @param sessID must non-null if reqID >= 0 + * @param fromLocalDest use these tunnels for the lookup, or null for exploratory + * @since 0.9.11 + */ + public LookupDestJob(RouterContext context, ClientConnectionRunner runner, + long reqID, long timeout, SessionId sessID, Hash h, String name, + Hash fromLocalDest) { super(context); + if ((h == null && name == null) || + (h != null && name != null) || + (reqID >= 0 && sessID == null) || + (reqID < 0 && name != null)) + throw new IllegalArgumentException(); _runner = runner; + _reqID = reqID; + _timeout = timeout; + _sessID = sessID; + _fromLocalDest = fromLocalDest; + if (name != null && name.length() == 60) { + // convert a b32 lookup to a hash lookup + String nlc = name.toLowerCase(Locale.US); + if (nlc.endsWith(".b32.i2p")) { + byte[] b = Base32.decode(nlc.substring(0, 52)); + if (b != null && b.length == Hash.HASH_LENGTH) { + h = Hash.create(b); + name = null; + } + } + } _hash = h; + _name = name; } - public String getName() { return "LeaseSet Lookup for Client"; } + public String getName() { return _name != null ? + "HostName Lookup for Client" : + "LeaseSet Lookup for Client"; + } + public void runJob() { - DoneJob done = new DoneJob(getContext()); - // TODO add support for specifying the timeout in the lookup message - getContext().netDb().lookupLeaseSet(_hash, done, done, 15*1000); + if (_name != null) { + // inline, ignore timeout + Destination d = getContext().namingService().lookup(_name); + if (d != null) + returnDest(d); + else + returnFail(); + } else { + DoneJob done = new DoneJob(getContext()); + getContext().netDb().lookupLeaseSet(_hash, done, done, _timeout, _fromLocalDest); + } } private class DoneJob extends JobImpl { @@ -42,12 +102,16 @@ public void runJob() { if (ls != null) returnDest(ls.getDestination()); else - returnHash(_hash); + returnFail(); } } private void returnDest(Destination d) { - DestReplyMessage msg = new DestReplyMessage(d); + I2CPMessage msg; + if (_reqID >= 0) + msg = new HostReplyMessage(_sessID, d, _reqID); + else + msg = new DestReplyMessage(d); try { _runner.doSend(msg); } catch (I2CPMessageException ime) {} @@ -57,8 +121,12 @@ private void returnDest(Destination d) { * Return the failed hash so the client can correlate replies with requests * @since 0.8.3 */ - private void returnHash(Hash h) { - DestReplyMessage msg = new DestReplyMessage(h); + private void returnFail() { + I2CPMessage msg; + if (_reqID >= 0) + msg = new HostReplyMessage(_sessID, HostReplyMessage.RESULT_FAILURE, _reqID); + else + msg = new DestReplyMessage(_hash); try { _runner.doSend(msg); } catch (I2CPMessageException ime) {} diff --git a/common/java/router/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java b/common/java/router/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java index 2005578..a17cee9 100644 --- a/common/java/router/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java +++ b/common/java/router/net/i2p/router/dummy/DummyNetworkDatabaseFacade.java @@ -40,6 +40,7 @@ public void startup() { public DatabaseEntry lookupLocally(Hash key) { return null; } public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {} + public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, Hash fromLocalDest) {} public LeaseSet lookupLeaseSetLocally(Hash key) { return null; } public void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) { RouterInfo info = lookupRouterInfoLocally(key); diff --git a/common/java/router/net/i2p/router/message/OutboundClientMessageOneShotJob.java b/common/java/router/net/i2p/router/message/OutboundClientMessageOneShotJob.java index 78ef6fa..cb4f6da 100644 --- a/common/java/router/net/i2p/router/message/OutboundClientMessageOneShotJob.java +++ b/common/java/router/net/i2p/router/message/OutboundClientMessageOneShotJob.java @@ -219,7 +219,7 @@ public void runJob() { if (_log.shouldLog(Log.DEBUG)) _log.debug(getJobId() + ": Send outbound client message - sending off leaseSet lookup job for " + _toString); LookupLeaseSetFailedJob failed = new LookupLeaseSetFailedJob(getContext()); - getContext().netDb().lookupLeaseSet(key, success, failed, timeoutMs); + getContext().netDb().lookupLeaseSet(key, success, failed, timeoutMs, _from.calculateHash()); } } diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/ExpireRoutersJob.java b/common/java/router/net/i2p/router/networkdb/kademlia/ExpireRoutersJob.java index d84914e..c2ea790 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/ExpireRoutersJob.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/ExpireRoutersJob.java @@ -13,6 +13,7 @@ import net.i2p.data.DatabaseEntry; import net.i2p.data.Hash; import net.i2p.data.RouterInfo; +import net.i2p.router.CommSystemFacade; import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; import net.i2p.util.Log; @@ -42,9 +43,11 @@ public ExpireRoutersJob(RouterContext ctx, KademliaNetworkDatabaseFacade facade) public String getName() { return "Expire Routers Job"; } public void runJob() { - int removed = expireKeys(); - if (_log.shouldLog(Log.INFO)) - _log.info("Routers expired: " + removed); + if (getContext().commSystem().getReachabilityStatus() != CommSystemFacade.STATUS_DISCONNECTED) { + int removed = expireKeys(); + if (_log.shouldLog(Log.INFO)) + _log.info("Routers expired: " + removed); + } requeue(RERUN_DELAY_MS); } @@ -59,6 +62,8 @@ public void runJob() { private int expireKeys() { Set keys = _facade.getAllRouters(); keys.remove(getContext().routerHash()); + if (keys.size() < 150) + return 0; int removed = 0; for (Hash key : keys) { // Don't expire anybody we are connected to diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/ExploreJob.java b/common/java/router/net/i2p/router/networkdb/kademlia/ExploreJob.java index 0515442..88122c0 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/ExploreJob.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/ExploreJob.java @@ -15,6 +15,7 @@ import net.i2p.data.Hash; import net.i2p.data.TunnelId; import net.i2p.data.i2np.DatabaseLookupMessage; +import net.i2p.kademlia.KBucketSet; import net.i2p.router.RouterContext; import net.i2p.util.Log; @@ -97,7 +98,7 @@ protected DatabaseLookupMessage buildMessage(TunnelId replyTunnelId, Hash replyG available--; } - KBucketSet ks = _facade.getKBuckets(); + KBucketSet ks = _facade.getKBuckets(); Hash rkey = getContext().routingKeyGenerator().getRoutingKey(getState().getTarget()); // in a few releases, we can (and should) remove this, // as routers will honor the above flag, and we want the table to include diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/ExploreKeySelectorJob.java b/common/java/router/net/i2p/router/networkdb/kademlia/ExploreKeySelectorJob.java index 2c8af25..d3fe019 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/ExploreKeySelectorJob.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/ExploreKeySelectorJob.java @@ -8,10 +8,13 @@ * */ +import java.util.Collection; import java.util.HashSet; import java.util.Set; import net.i2p.data.Hash; +import net.i2p.kademlia.KBucket; +import net.i2p.kademlia.KBucketSet; import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; import net.i2p.util.Log; @@ -28,6 +31,7 @@ class ExploreKeySelectorJob extends JobImpl { private KademliaNetworkDatabaseFacade _facade; private final static long RERUN_DELAY_MS = 60*1000; + private final static long OLD_BUCKET_TIME = 15*60*1000; public ExploreKeySelectorJob(RouterContext context, KademliaNetworkDatabaseFacade facade) { super(context); @@ -41,7 +45,7 @@ public void runJob() { requeue(30*RERUN_DELAY_MS); return; } - Set toExplore = selectKeysToExplore(); + Collection toExplore = selectKeysToExplore(); _log.info("Filling the explorer pool with: " + toExplore); if (toExplore != null) _facade.queueForExploration(toExplore); @@ -53,32 +57,11 @@ public void runJob() { * for it, with a maximum number of keys limited by the exploration pool size * */ - private Set selectKeysToExplore() { + private Collection selectKeysToExplore() { Set alreadyQueued = _facade.getExploreKeys(); - if (alreadyQueued.size() > KBucketSet.NUM_BUCKETS) return null; - Set toExplore = new HashSet(KBucketSet.NUM_BUCKETS - alreadyQueued.size()); - for (int i = 0; i < KBucketSet.NUM_BUCKETS; i++) { - KBucket bucket = _facade.getKBuckets().getBucket(i); - if (bucket.getKeyCount() < KBucketSet.BUCKET_SIZE) { - boolean already = false; - for (Hash key : alreadyQueued) { - if (bucket.shouldContain(key)) { - already = true; - _log.debug("Bucket " + i + " is already queued for exploration \t" + key); - break; - } - } - if (!already) { - // no keys are queued for exploring this still-too-small bucket yet - Hash key = bucket.generateRandomKey(); - _log.debug("Bucket " + i + " is NOT queued for exploration, and it only has " + bucket.getKeyCount() + " keys, so explore with \t" + key); - toExplore.add(key); - } - } else { - _log.debug("Bucket " + i + " already has enough keys (" + bucket.getKeyCount() + "), no need to explore further"); - } - } - return toExplore; + if (alreadyQueued.size() > KademliaNetworkDatabaseFacade.MAX_EXPLORE_QUEUE) + return null; + return _facade.getKBuckets().getExploreKeys(OLD_BUCKET_TIME); } } diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/FloodOnlySearchJob.java b/common/java/router/net/i2p/router/networkdb/kademlia/FloodOnlySearchJob.java index c07f3de..5eb970a 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/FloodOnlySearchJob.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/FloodOnlySearchJob.java @@ -3,11 +3,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import net.i2p.data.Hash; import net.i2p.data.i2np.DatabaseLookupMessage; +import net.i2p.kademlia.KBucketSet; import net.i2p.router.Job; import net.i2p.router.MessageSelector; import net.i2p.router.OutNetMessage; @@ -70,7 +70,7 @@ public void runJob() { //List floodfillPeers = _facade.getFloodfillPeers(); // new List floodfillPeers; - KBucketSet ks = _facade.getKBuckets(); + KBucketSet ks = _facade.getKBuckets(); if (ks != null) { Hash rkey = getContext().routingKeyGenerator().getRoutingKey(_key); // Ideally we would add the key to an exclude list, so we don't try to query a ff peer for itself, @@ -216,8 +216,8 @@ void failed() { _log.info(getJobId() + ": Floodfill search for " + _key + " failed with " + timeRemaining + " remaining after " + time); } synchronized(_unheardFrom) { - for (Iterator iter = _unheardFrom.iterator(); iter.hasNext(); ) - getContext().profileManager().dbLookupFailed(iter.next()); + for (Hash h : _unheardFrom) + getContext().profileManager().dbLookupFailed(h); } _facade.complete(_key); getContext().statManager().addRateData("netDb.failedTime", time, 0); diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillMonitorJob.java b/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillMonitorJob.java index 282c11e..c61ed40 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillMonitorJob.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillMonitorJob.java @@ -129,7 +129,7 @@ private boolean shouldBeFloodfill() { // Only if we're pretty well integrated... happy = happy && _facade.getKnownRouters() >= 200; happy = happy && getContext().commSystem().countActivePeers() >= 50; - happy = happy && getContext().tunnelManager().getParticipatingCount() >= 50; + happy = happy && getContext().tunnelManager().getParticipatingCount() >= 35; happy = happy && Math.abs(getContext().clock().getOffset()) < 10*1000; // We need an address and no introducers if (happy) { diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java b/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java index e8cae58..4661f5c 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillNetworkDatabaseFacade.java @@ -43,6 +43,9 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad private static final int FLOOD_PRIORITY = OutNetMessage.PRIORITY_NETDB_FLOOD; private static final int FLOOD_TIMEOUT = 30*1000; + private static final long NEXT_RKEY_RI_ADVANCE_TIME = 45*60*1000; + private static final long NEXT_RKEY_LS_ADVANCE_TIME = 10*60*1000; + private static final int NEXT_FLOOD_QTY = 2; public FloodfillNetworkDatabaseFacade(RouterContext context) { super(context); @@ -197,6 +200,23 @@ public void flood(DatabaseEntry ds) { Hash rkey = _context.routingKeyGenerator().getRoutingKey(key); FloodfillPeerSelector sel = (FloodfillPeerSelector)getPeerSelector(); List peers = sel.selectFloodfillParticipants(rkey, MAX_TO_FLOOD, getKBuckets()); + long until = _context.routingKeyGenerator().getTimeTillMidnight(); + if (until < NEXT_RKEY_LS_ADVANCE_TIME || + (ds.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO && until < NEXT_RKEY_RI_ADVANCE_TIME)) { + // to avoid lookup failures after midnight, also flood to some closest to the + // next routing key for a period of time before midnight. + Hash nkey = _context.routingKeyGenerator().getNextRoutingKey(key); + List nextPeers = sel.selectFloodfillParticipants(nkey, NEXT_FLOOD_QTY, getKBuckets()); + int i = 0; + for (Hash h : nextPeers) { + if (!peers.contains(h)) { + peers.add(h); + i++; + } + } + if (i > 0 && _log.shouldLog(Log.INFO)) + _log.info("Flooding the entry for " + key + " to " + i + " more, just before midnight"); + } int flooded = 0; for (int i = 0; i < peers.size(); i++) { Hash peer = peers.get(i); @@ -279,6 +299,8 @@ public List getKnownRouterData() { } /** + * Lookup using exploratory tunnels + * * Begin a kademlia style search for the key specified, which can take up to timeoutMs and * will fire the appropriate jobs on success or timeout (or if the kademlia search completes * without any match) @@ -287,6 +309,17 @@ public List getKnownRouterData() { */ @Override SearchJob search(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, boolean isLease) { + return search(key, onFindJob, onFailedLookupJob, timeoutMs, isLease, null); + } + + /** + * Lookup using the client's tunnels + * @param fromLocalDest use these tunnels for the lookup, or null for exploratory + * @return null always + * @since 0.9.10 + */ + SearchJob search(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, boolean isLease, + Hash fromLocalDest) { //if (true) return super.search(key, onFindJob, onFailedLookupJob, timeoutMs, isLease); if (key == null) throw new IllegalArgumentException("searchin for nothin, eh?"); boolean isNew = false; @@ -296,7 +329,8 @@ SearchJob search(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, if (searchJob == null) { //if (SearchJob.onlyQueryFloodfillPeers(_context)) { //searchJob = new FloodOnlySearchJob(_context, this, key, onFindJob, onFailedLookupJob, (int)timeoutMs, isLease); - searchJob = new IterativeSearchJob(_context, this, key, onFindJob, onFailedLookupJob, (int)timeoutMs, isLease); + searchJob = new IterativeSearchJob(_context, this, key, onFindJob, onFailedLookupJob, (int)timeoutMs, + isLease, fromLocalDest); //} else { // searchJob = new FloodSearchJob(_context, this, key, onFindJob, onFailedLookupJob, (int)timeoutMs, isLease); //} diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java b/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java index 3e054ed..fdf47bf 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/FloodfillPeerSelector.java @@ -20,6 +20,9 @@ import net.i2p.data.Hash; import net.i2p.data.RouterAddress; import net.i2p.data.RouterInfo; +import net.i2p.kademlia.KBucketSet; +import net.i2p.kademlia.SelectionCollector; +import net.i2p.kademlia.XORComparator; import net.i2p.router.RouterContext; import net.i2p.router.peermanager.PeerProfile; import net.i2p.router.util.RandomIterator; @@ -53,7 +56,7 @@ public FloodfillPeerSelector(RouterContext ctx) { * @return List of Hash for the peers selected */ @Override - List selectMostReliablePeers(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { + List selectMostReliablePeers(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { return selectNearestExplicitThin(key, maxNumRouters, peersToIgnore, kbuckets, true); } @@ -68,7 +71,7 @@ List selectMostReliablePeers(Hash key, int maxNumRouters, Set peersT * @return List of Hash for the peers selected */ @Override - List selectNearestExplicitThin(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { + List selectNearestExplicitThin(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { return selectNearestExplicitThin(key, maxNumRouters, peersToIgnore, kbuckets, false); } @@ -81,7 +84,7 @@ List selectNearestExplicitThin(Hash key, int maxNumRouters, Set peer * @param peersToIgnore can be null * @return List of Hash for the peers selected */ - List selectNearestExplicitThin(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets, boolean preferConnected) { + List selectNearestExplicitThin(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets, boolean preferConnected) { if (peersToIgnore == null) peersToIgnore = Collections.singleton(_context.routerHash()); else @@ -104,7 +107,7 @@ List selectNearestExplicitThin(Hash key, int maxNumRouters, Set peer * List will not include our own hash. * List is not sorted and not shuffled. */ - List selectFloodfillParticipants(KBucketSet kbuckets) { + List selectFloodfillParticipants(KBucketSet kbuckets) { Set ignore = Collections.singleton(_context.routerHash()); return selectFloodfillParticipants(ignore, kbuckets); } @@ -116,7 +119,7 @@ List selectFloodfillParticipants(KBucketSet kbuckets) { * List MAY INCLUDE our own hash. * List is not sorted and not shuffled. */ - private List selectFloodfillParticipants(Set toIgnore, KBucketSet kbuckets) { + private List selectFloodfillParticipants(Set toIgnore, KBucketSet kbuckets) { /***** if (kbuckets == null) return Collections.EMPTY_LIST; // TODO this is very slow - use profile getPeersByCapability('f') instead @@ -155,7 +158,7 @@ private List selectFloodfillParticipants(Set toIgnore, KBucketSet kb * success newer than failure * Group 3: All others */ - List selectFloodfillParticipants(Hash key, int maxNumRouters, KBucketSet kbuckets) { + List selectFloodfillParticipants(Hash key, int maxNumRouters, KBucketSet kbuckets) { Set ignore = Collections.singleton(_context.routerHash()); return selectFloodfillParticipants(key, maxNumRouters, ignore, kbuckets); } @@ -175,7 +178,7 @@ List selectFloodfillParticipants(Hash key, int maxNumRouters, KBucketSet k * @param toIgnore can be null * @param kbuckets now unused */ - List selectFloodfillParticipants(Hash key, int howMany, Set toIgnore, KBucketSet kbuckets) { + List selectFloodfillParticipants(Hash key, int howMany, Set toIgnore, KBucketSet kbuckets) { if (toIgnore == null) { toIgnore = Collections.singleton(_context.routerHash()); } else if (!toIgnore.contains(_context.routerHash())) { @@ -193,9 +196,9 @@ List selectFloodfillParticipants(Hash key, int howMany, Set toIgnore * @param toIgnore can be null * @param kbuckets now unused */ - private List selectFloodfillParticipantsIncludingUs(Hash key, int howMany, Set toIgnore, KBucketSet kbuckets) { + private List selectFloodfillParticipantsIncludingUs(Hash key, int howMany, Set toIgnore, KBucketSet kbuckets) { List ffs = selectFloodfillParticipants(toIgnore, kbuckets); - TreeSet sorted = new TreeSet(new XORComparator(key)); + TreeSet sorted = new TreeSet(new XORComparator(key)); sorted.addAll(ffs); List rv = new ArrayList(howMany); @@ -339,7 +342,7 @@ private static Integer maskedIP(byte[] ip, int mask) { return Integer.valueOf(rv); } - private class FloodfillSelectionCollector implements SelectionCollector { + private class FloodfillSelectionCollector implements SelectionCollector { private final TreeSet _sorted; private final List _floodfillMatches; private final Hash _key; @@ -354,7 +357,7 @@ private class FloodfillSelectionCollector implements SelectionCollector { */ public FloodfillSelectionCollector(Hash key, Set toIgnore, int wanted) { _key = key; - _sorted = new TreeSet(new XORComparator(key)); + _sorted = new TreeSet(new XORComparator(key)); _floodfillMatches = new ArrayList(8); _toIgnore = toIgnore; _wanted = wanted; @@ -475,7 +478,7 @@ public List get(int howMany, boolean preferConnected) { * @return List of Hash for the peers selected, ordered */ @Override - List selectNearest(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { + List selectNearest(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { Hash rkey = _context.routingKeyGenerator().getRoutingKey(key); if (peersToIgnore != null && peersToIgnore.contains(Hash.FAKE_HASH)) { // return non-ff diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java b/common/java/router/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java index 868f0d0..23fd34b 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java @@ -16,6 +16,8 @@ import net.i2p.data.RouterInfo; import net.i2p.data.i2np.DatabaseLookupMessage; import net.i2p.data.i2np.I2NPMessage; +import net.i2p.kademlia.KBucketSet; +import net.i2p.kademlia.XORComparator; import net.i2p.router.CommSystemFacade; import net.i2p.router.Job; import net.i2p.router.MessageSelector; @@ -23,6 +25,7 @@ import net.i2p.router.ReplyJob; import net.i2p.router.RouterContext; import net.i2p.router.TunnelInfo; +import net.i2p.router.TunnelManagerFacade; import net.i2p.router.util.RandomIterator; import net.i2p.util.Log; @@ -61,6 +64,7 @@ class IterativeSearchJob extends FloodSearchJob { private final Hash _rkey; /** this is a marker to register with the MessageRegistry, it is never sent */ private OutNetMessage _out; + private final Hash _fromLocalDest; /** testing */ private static Hash _alwaysQueryHash; @@ -87,16 +91,31 @@ class IterativeSearchJob extends FloodSearchJob { /** testing */ private static final String PROP_ENCRYPT_RI = "router.encryptRouterLookups"; - public IterativeSearchJob(RouterContext ctx, FloodfillNetworkDatabaseFacade facade, Hash key, Job onFind, Job onFailed, int timeoutMs, boolean isLease) { + /** + * Lookup using exploratory tunnels + */ + public IterativeSearchJob(RouterContext ctx, FloodfillNetworkDatabaseFacade facade, Hash key, + Job onFind, Job onFailed, int timeoutMs, boolean isLease) { + this(ctx, facade, key, onFind, onFailed, timeoutMs, isLease, null); + } + + /** + * Lookup using the client's tunnels + * @param fromLocalDest use these tunnels for the lookup, or null for exploratory + * @since 0.9.10 + */ + public IterativeSearchJob(RouterContext ctx, FloodfillNetworkDatabaseFacade facade, Hash key, + Job onFind, Job onFailed, int timeoutMs, boolean isLease, Hash fromLocalDest) { super(ctx, facade, key, onFind, onFailed, timeoutMs, isLease); // these override the settings in super _timeoutMs = Math.min(timeoutMs, MAX_SEARCH_TIME); _expiration = _timeoutMs + ctx.clock().now(); _rkey = ctx.routingKeyGenerator().getRoutingKey(key); - _toTry = new TreeSet(new XORComparator(_rkey)); + _toTry = new TreeSet(new XORComparator(_rkey)); _unheardFrom = new HashSet(CONCURRENT_SEARCHES); _failedPeers = new HashSet(TOTAL_SEARCH_LIMIT); _sentTime = new ConcurrentHashMap(TOTAL_SEARCH_LIMIT); + _fromLocalDest = fromLocalDest; } @Override @@ -109,7 +128,7 @@ public void runJob() { } // pick some floodfill peers and send out the searches List floodfillPeers; - KBucketSet ks = _facade.getKBuckets(); + KBucketSet ks = _facade.getKBuckets(); if (ks != null) { // Ideally we would add the key to an exclude list, so we don't try to query a ff peer for itself, // but we're passing the rkey not the key, so we do it below instead in certain cases. @@ -230,8 +249,23 @@ private void retry() { */ private void sendQuery(Hash peer) { DatabaseLookupMessage dlm = new DatabaseLookupMessage(getContext(), true); - TunnelInfo replyTunnel = getContext().tunnelManager().selectInboundExploratoryTunnel(peer); - TunnelInfo outTunnel = getContext().tunnelManager().selectOutboundExploratoryTunnel(peer); + TunnelManagerFacade tm = getContext().tunnelManager(); + TunnelInfo outTunnel; + TunnelInfo replyTunnel; + boolean isClientReplyTunnel; + if (_fromLocalDest != null) { + outTunnel = tm.selectOutboundTunnel(_fromLocalDest, peer); + if (outTunnel == null) + outTunnel = tm.selectOutboundExploratoryTunnel(peer); + replyTunnel = tm.selectInboundTunnel(_fromLocalDest, peer); + isClientReplyTunnel = replyTunnel != null; + if (!isClientReplyTunnel) + replyTunnel = tm.selectInboundExploratoryTunnel(peer); + } else { + outTunnel = tm.selectOutboundExploratoryTunnel(peer); + replyTunnel = tm.selectInboundExploratoryTunnel(peer); + isClientReplyTunnel = false; + } if ( (replyTunnel == null) || (outTunnel == null) ) { failed(); return; @@ -258,7 +292,10 @@ private void sendQuery(Hash peer) { synchronized(this) { tries = _unheardFrom.size() + _failedPeers.size(); } - _log.info(getJobId() + ": ISJ try " + tries + " for " + _key + " to " + peer); + _log.info(getJobId() + ": ISJ try " + tries + " for " + + (_isLease ? "LS " : "RI ") + + _key + " to " + peer + + " reply via client tunnel? " + isClientReplyTunnel); } long now = getContext().clock().now(); _sentTime.put(peer, Long.valueOf(now)); @@ -271,10 +308,16 @@ private void sendQuery(Hash peer) { if (ri != null) { // request encrypted reply if (DatabaseLookupMessage.supportsEncryptedReplies(ri)) { - MessageWrapper.OneTimeSession sess = MessageWrapper.generateSession(getContext()); - if (_log.shouldLog(Log.INFO)) - _log.info(getJobId() + ": Requesting encrypted reply from " + peer + ' ' + sess.key + ' ' + sess.tag); - dlm.setReplySession(sess.key, sess.tag); + MessageWrapper.OneTimeSession sess; + if (isClientReplyTunnel) + sess = MessageWrapper.generateSession(getContext(), _fromLocalDest); + else + sess = MessageWrapper.generateSession(getContext()); + if (sess != null) { + if (_log.shouldLog(Log.INFO)) + _log.info(getJobId() + ": Requesting encrypted reply from " + peer + ' ' + sess.key + ' ' + sess.tag); + dlm.setReplySession(sess.key, sess.tag); + } // else client went away, but send it anyway } outMsg = MessageWrapper.wrap(getContext(), dlm, ri); // ElG can take a while so do a final check before we send it, @@ -383,8 +426,8 @@ void failed() { synchronized(this) { tries = _unheardFrom.size() + _failedPeers.size(); // blame the unheard-from (others already blamed in failed() above) - for (Iterator iter = _unheardFrom.iterator(); iter.hasNext(); ) - getContext().profileManager().dbLookupFailed(iter.next()); + for (Hash h : _unheardFrom) + getContext().profileManager().dbLookupFailed(h); } long time = System.currentTimeMillis() - _created; if (_log.shouldLog(Log.INFO)) { diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/KBucket.java b/common/java/router/net/i2p/router/networkdb/kademlia/KBucket.java deleted file mode 100644 index b76b948..0000000 --- a/common/java/router/net/i2p/router/networkdb/kademlia/KBucket.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.i2p.router.networkdb.kademlia; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.util.Set; - -import net.i2p.data.Hash; - -/** - * Group, without inherent ordering, a set of keys a certain distance away from - * a local key, using XOR as the distance metric - * - */ -interface KBucket { - /** - * lowest order high bit for difference keys - */ - public int getRangeBegin(); - /** - * highest high bit for the difference keys - * - */ - public int getRangeEnd(); - /** - * Set the range low and high bits for difference keys - */ - public void setRange(int lowOrderBitLimit, int highOrderBitLimit); - /** - * Number of keys already contained in this kbuckey - */ - public int getKeyCount(); - /** - * whether or not the key qualifies as part of this bucket - * - */ - public boolean shouldContain(Hash key); - /** - * Add the peer to the bucket - * - * @return number of keys in the bucket after the addition - */ - public int add(Hash key); - /** - * Remove the key from the bucket - * @return true if the key existed in the bucket before removing it, else false - */ - public boolean remove(Hash key); - - /** - * Retrieve all routing table entries stored in the bucket - * @return set of Hash structures - */ - public Set getEntries(); - - /** - * Retrieve hashes stored in the bucket, excluding the ones specified - * @return set of Hash structures - * @deprecated makes a copy, remove toIgnore in KBS instead - */ - public Set getEntries(Set toIgnoreHashes); - - public void getEntries(SelectionCollector collector); - - /** - * Fill the bucket with entries - * @param entries set of Hash structures - */ - public void setEntries(Set entries); - - /** - * Generate a random key that would go inside this bucket - * - */ - public Hash generateRandomKey(); - - public LocalHash getLocal(); -} diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/KBucketImpl.java b/common/java/router/net/i2p/router/networkdb/kademlia/KBucketImpl.java deleted file mode 100644 index 5b61899..0000000 --- a/common/java/router/net/i2p/router/networkdb/kademlia/KBucketImpl.java +++ /dev/null @@ -1,474 +0,0 @@ -package net.i2p.router.networkdb.kademlia; -/* - * free (adj.): unencumbered; not under the control of others - * Written by jrandom in 2003 and released into the public domain - * with no warranty of any kind, either expressed or implied. - * It probably won't make your computer catch on fire, or eat - * your children, but it might. Use at your own risk. - * - */ - -import java.math.BigInteger; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import net.i2p.I2PAppContext; -import net.i2p.data.DataHelper; -import net.i2p.data.Hash; -import net.i2p.util.ConcurrentHashSet; -import net.i2p.util.Log; -import net.i2p.util.RandomSource; - -class KBucketImpl implements KBucket { - private Log _log; - /** - * set of Hash objects for the peers in the kbucketx - * - * jrandom switched from a HashSet to an ArrayList with this change: - * 2005-08-27 jrandom - * * Minor logging and optimization tweaks in the router and SDK - * - * Now we switch back to a ConcurrentHashSet and remove all the - * synchronization, which may or may not be faster than - * a synchronized ArrayList, with checks for existence before - * adding a Hash. But the other benefit is it removes one - * cause of profileMangager/netDb deadlock. - */ - private final Set _entries; - /** we center the kbucket set on the given hash, and derive distances from this */ - private LocalHash _local; - /** include if any bits equal or higher to this bit (in big endian order) */ - private int _begin; - /** include if no bits higher than this bit (inclusive) are set */ - private int _end; - /** when did we last shake things up */ - private long _lastShuffle; - private I2PAppContext _context; - - public KBucketImpl(I2PAppContext context, LocalHash local) { - _context = context; - _log = context.logManager().getLog(KBucketImpl.class); - _entries = new ConcurrentHashSet(2); //all but the last 1 or 2 buckets will be empty - _lastShuffle = context.clock().now(); - setLocal(local); - } - - /** for testing - use above constructor for production to get common caching */ - public KBucketImpl(I2PAppContext context, Hash local) { - this(context, new LocalHash(local)); - } - - public int getRangeBegin() { return _begin; } - public int getRangeEnd() { return _end; } - public void setRange(int lowOrderBitLimit, int highOrderBitLimit) { - _begin = lowOrderBitLimit; - _end = highOrderBitLimit; - } - public int getKeyCount() { - return _entries.size(); - } - - public LocalHash getLocal() { return _local; } - private void setLocal(LocalHash local) { - _local = local; - // we want to make sure we've got the cache in place before calling cachedXor - _local.prepareCache(); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Local hash reset to " + DataHelper.toHexString(local.getData())); - } - - private byte[] distanceFromLocal(Hash key) { - if (key == null) - throw new IllegalArgumentException("Null key for distanceFromLocal?"); - return _local.cachedXor(key); - } - - public boolean shouldContain(Hash key) { - byte distance[] = distanceFromLocal(key); - // rather than use a BigInteger and compare, we do it manually by - // checking the bits - boolean tooLarge = distanceIsTooLarge(distance); - if (tooLarge) { - if (false && _log.shouldLog(Log.DEBUG)) - _log.debug("too large [" + _begin + "-->" + _end + "] " - + "\nLow: " + BigInteger.ZERO.setBit(_begin).toString(16) - + "\nCur: " + DataHelper.toHexString(distance) - + "\nHigh: " + BigInteger.ZERO.setBit(_end).toString(16)); - return false; - } - boolean tooSmall = distanceIsTooSmall(distance); - if (tooSmall) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("too small [" + _begin + "-->" + _end + "] distance: " + DataHelper.toHexString(distance)); - return false; - } - // this bed is juuuuust right - return true; - - /* - // woohah, incredibly excessive object creation! whee! - BigInteger kv = new BigInteger(1, distanceFromLocal(key)); - int lowComp = kv.compareTo(_lowerBounds); - int highComp = kv.compareTo(_upperBounds); - - //_log.debug("kv.compareTo(low) = " + lowComp + " kv.compareTo(high) " + highComp); - - if ( (lowComp >= 0) && (highComp < 0) ) return true; - return false; - */ - } - - private final boolean distanceIsTooLarge(byte distance[]) { - int upperLimitBit = Hash.HASH_LENGTH*8 - _end; - // It is too large if there are any bits set before the upperLimitBit - int upperLimitByte = upperLimitBit > 0 ? upperLimitBit / 8 : 0; - - if (upperLimitBit <= 0) - return false; - - for (int i = 0; i < distance.length; i++) { - if (i < upperLimitByte) { - if (distance[i] != 0x00) { - // outright too large - return true; - } - } else if (i == upperLimitByte) { - if (distance[i] == 0x00) { - // no bits set through the high bit - return false; - } else { - int upperVal = 1 << (upperLimitBit % 8); - if (distance[i] > upperVal) { - // still too large, but close - return true; - } else if (distance[i] == upperVal) { - // ok, it *may* equal the upper limit, - // if the rest of the bytes are 0 - for (int j = i+1; j < distance.length; j++) { - if (distance[j] != 0x00) { - // nope - return true; - } - } - // w00t, the rest is made of 0x00 bytes, so it - // exactly matches the upper limit. kooky, very improbable, - // but possible - return false; - } - } - } else if (i > upperLimitByte) { - // no bits set before or at the upper limit, so its - // definitely not too large - return false; - } - } - _log.log(Log.CRIT, "wtf, gravity broke: distance=" + DataHelper.toHexString(distance) - + ", end=" + _end, new Exception("moo")); - return true; - } - - /** - * Is the distance too small? - * - */ - private final boolean distanceIsTooSmall(byte distance[]) { - int beginBit = Hash.HASH_LENGTH*8 - _begin; - // It is too small if there are no bits set before the beginBit - int beginByte = beginBit > 0 ? beginBit / 8 : 0; - - if (beginByte >= distance.length) { - if (_begin == 0) - return false; - else - return true; - } - - for (int i = 0; i < distance.length; i++) { - if ( (i < beginByte) && (distance[i] != 0x00) ) { - return false; - } else { - if (i != beginByte) { - // zero value and too early... keep going - continue; - } else { - int beginVal = 1 << (_begin % 8); - if (distance[i] >= beginVal) { - return false; - } else { - // no bits set prior to the beginVal - return true; - } - } - } - } - _log.log(Log.CRIT, "wtf, gravity broke! distance=" + DataHelper.toHexString(distance) - + " begin=" + _begin - + " beginBit=" + beginBit - + " beginByte=" + beginByte, new Exception("moo")); - return true; - } - - /** - * @return unmodifiable view - */ - public Set getEntries() { - return Collections.unmodifiableSet(_entries); - } - - /** - * @deprecated makes a copy, remove toIgnore in KBS instead - */ - public Set getEntries(Set toIgnoreHashes) { - Set entries = new HashSet(_entries); - entries.removeAll(toIgnoreHashes); - return entries; - } - - public void getEntries(SelectionCollector collector) { - for (Hash h : _entries) { - collector.add(h); - } - } - - public void setEntries(Set entries) { - _entries.clear(); - _entries.addAll(entries); - } - - /** - * Todo: shuffling here is a hack and doesn't work since - * we switched back to a HashSet implementation - */ - public int add(Hash peer) { - _entries.add(peer); -/********** - // Randomize the bucket every once in a while if we are floodfill, so that - // exploration will return better results. See FloodfillPeerSelector.add(Hash). - if (_lastShuffle + SHUFFLE_DELAY < _context.clock().now() && - !SearchJob.onlyQueryFloodfillPeers((RouterContext)_context)) { - Collections.shuffle(_entries, _context.random()); - _lastShuffle = _context.clock().now(); - } -***********/ - return _entries.size(); - } - - public boolean remove(Hash peer) { - return _entries.remove(peer); - } - - /** - * Generate a random key to go within this bucket - * - * WARNING - Something is seriously broken here. testRand2() fails right away. - * ExploreKeySelectorJob is now disabled, ExploreJob just searches for a random - * key instead. - */ - public Hash generateRandomKey() { - BigInteger variance = new BigInteger((_end-_begin)-1, _context.random()); - variance = variance.setBit(_begin); - //_log.debug("Random variance for " + _size + " bits: " + variance); - byte data[] = variance.toByteArray(); - byte hash[] = new byte[Hash.HASH_LENGTH]; - if (data.length <= Hash.HASH_LENGTH) { - System.arraycopy(data, 0, hash, hash.length - data.length, data.length); - } else { - System.arraycopy(data, data.length - hash.length, hash, 0, hash.length); - } - Hash key = new Hash(hash); - data = distanceFromLocal(key); - hash = new byte[Hash.HASH_LENGTH]; - if (data.length <= Hash.HASH_LENGTH) { - System.arraycopy(data, 0, hash, hash.length - data.length, data.length); - } else { - System.arraycopy(data, data.length - hash.length, hash, 0, hash.length); - } - key = new Hash(hash); - return key; - } - - public Hash getRangeBeginKey() { - BigInteger lowerBounds = getLowerBounds(); - if ( (_local != null) && (_local.getData() != null) ) { - lowerBounds = lowerBounds.xor(new BigInteger(1, _local.getData())); - } - - byte data[] = lowerBounds.toByteArray(); - byte hash[] = new byte[Hash.HASH_LENGTH]; - if (data.length <= Hash.HASH_LENGTH) { - System.arraycopy(data, 0, hash, hash.length - data.length, data.length); - } else { - System.arraycopy(data, data.length - hash.length, hash, 0, hash.length); - } - Hash key = new Hash(hash); - return key; - } - - public Hash getRangeEndKey() { - BigInteger upperBounds = getUpperBounds(); - if ( (_local != null) && (_local.getData() != null) ) { - upperBounds = upperBounds.xor(new BigInteger(1, _local.getData())); - } - byte data[] = upperBounds.toByteArray(); - byte hash[] = new byte[Hash.HASH_LENGTH]; - if (data.length <= Hash.HASH_LENGTH) { - System.arraycopy(data, 0, hash, hash.length - data.length, data.length); - } else { - System.arraycopy(data, data.length - hash.length, hash, 0, hash.length); - } - Hash key = new Hash(hash); - return key; - } - - private BigInteger getUpperBounds() { - return BigInteger.ZERO.setBit(_end); - } - private BigInteger getLowerBounds() { - if (_begin == 0) - return BigInteger.ZERO; - else - return BigInteger.ZERO.setBit(_begin); - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(1024); - buf.append("KBucketImpl: "); - buf.append(_entries.toString()).append("\n"); - buf.append("Low bit: ").append(_begin).append(" high bit: ").append(_end).append('\n'); - buf.append("Local key: \n"); - if ( (_local != null) && (_local.getData() != null) ) - buf.append(toString(_local.getData())).append('\n'); - else - buf.append("[undefined]\n"); - buf.append("Low and high keys:\n"); - buf.append(toString(getRangeBeginKey().getData())).append('\n'); - buf.append(toString(getRangeEndKey().getData())).append('\n'); - buf.append("Low and high deltas:\n"); - buf.append(getLowerBounds().toString(2)).append('\n'); - buf.append(getUpperBounds().toString(2)).append('\n'); - return buf.toString(); - } - - /** - * Test harness to make sure its assigning keys to the right buckets - * - * WARNING - Something is seriously broken here. testRand2() fails right away. - */ - public static void main(String args[]) { - testRand2(); - testRand(); - testLimits(); - - try { Thread.sleep(10000); } catch (InterruptedException ie) {} - } - - private static void testLimits() { - int low = 1; - int high = 3; - Log log = I2PAppContext.getGlobalContext().logManager().getLog(KBucketImpl.class); - KBucketImpl bucket = new KBucketImpl(I2PAppContext.getGlobalContext(), Hash.FAKE_HASH); - bucket.setRange(low, high); - Hash lowerBoundKey = bucket.getRangeBeginKey(); - Hash upperBoundKey = bucket.getRangeEndKey(); - boolean okLow = bucket.shouldContain(lowerBoundKey); - boolean okHigh = bucket.shouldContain(upperBoundKey); - if (okLow && okHigh) - log.debug("Limit test ok"); - else - log.error("Limit test failed! ok low? " + okLow + " ok high? " + okHigh); - } - - private static void testRand() { - //StringBuilder buf = new StringBuilder(2048); - int low = 1; - int high = 3; - Log log = I2PAppContext.getGlobalContext().logManager().getLog(KBucketImpl.class); - LocalHash local = new LocalHash(Hash.FAKE_HASH); - local.prepareCache(); - KBucketImpl bucket = new KBucketImpl(I2PAppContext.getGlobalContext(), local); - bucket.setRange(low, high); - //Hash lowerBoundKey = bucket.getRangeBeginKey(); - //Hash upperBoundKey = bucket.getRangeEndKey(); - for (int i = 0; i < 100000; i++) { - Hash rnd = bucket.generateRandomKey(); - //buf.append(toString(rnd.getData())).append('\n'); - boolean ok = bucket.shouldContain(rnd); - if (!ok) { - //byte diff[] = bucket.getLocal().cachedXor(rnd); - //BigInteger dv = new BigInteger(1, diff); - //log.error("WTF! bucket doesn't want: \n" + toString(rnd.getData()) - // + "\nDelta: \n" + toString(diff) + "\nDelta val: \n" + dv.toString(2) - // + "\nBucket: \n"+bucket, new Exception("WTF")); - log.error("wtf, bucket doesnt want a key that it generated. i == " + i); - log.error("\nLow: " + DataHelper.toHexString(bucket.getRangeBeginKey().getData()) - + "\nVal: " + DataHelper.toHexString(rnd.getData()) - + "\nHigh:" + DataHelper.toHexString(bucket.getRangeEndKey().getData())); - try { Thread.sleep(1000); } catch (InterruptedException e) {} - System.exit(0); - } else { - //_log.debug("Ok, bucket wants: \n" + toString(rnd.getData())); - } - //_log.info("Low/High:\n" + toString(lowBounds.toByteArray()) + "\n" + toString(highBounds.toByteArray())); - } - log.info("Passed 100,000 random key generations against the null hash"); - } - - private static void testRand2() { - Log log = I2PAppContext.getGlobalContext().logManager().getLog(KBucketImpl.class); - int low = 1; - int high = 200; - byte hash[] = new byte[Hash.HASH_LENGTH]; - RandomSource.getInstance().nextBytes(hash); - LocalHash local = new LocalHash(hash); - local.prepareCache(); - KBucketImpl bucket = new KBucketImpl(I2PAppContext.getGlobalContext(), local); - bucket.setRange(low, high); - //Hash lowerBoundKey = bucket.getRangeBeginKey(); - //Hash upperBoundKey = bucket.getRangeEndKey(); - for (int i = 0; i < 100000; i++) { - Hash rnd = bucket.generateRandomKey(); - //buf.append(toString(rnd.getData())).append('\n'); - boolean ok = bucket.shouldContain(rnd); - if (!ok) { - //byte diff[] = bucket.getLocal().cachedXor(rnd); - //BigInteger dv = new BigInteger(1, diff); - //log.error("WTF! bucket doesn't want: \n" + toString(rnd.getData()) - // + "\nDelta: \n" + toString(diff) + "\nDelta val: \n" + dv.toString(2) - // + "\nBucket: \n"+bucket, new Exception("WTF")); - log.error("wtf, bucket doesnt want a key that it generated. i == " + i); - log.error("\nLow: " + DataHelper.toHexString(bucket.getRangeBeginKey().getData()) - + "\nVal: " + DataHelper.toHexString(rnd.getData()) - + "\nHigh:" + DataHelper.toHexString(bucket.getRangeEndKey().getData())); - try { Thread.sleep(1000); } catch (InterruptedException e) {} - System.exit(0); - } else { - //_log.debug("Ok, bucket wants: \n" + toString(rnd.getData())); - } - } - log.info("Passed 100,000 random key generations against a random hash"); - } - - private final static String toString(byte b[]) { - if (true) return DataHelper.toHexString(b); - StringBuilder buf = new StringBuilder(b.length); - for (int i = 0; i < b.length; i++) { - buf.append(toString(b[i])); - buf.append(" "); - } - return buf.toString(); - } - - private final static String toString(byte b) { - StringBuilder buf = new StringBuilder(8); - for (int i = 7; i >= 0; i--) { - boolean bb = (0 != (b & (1<= 0) { - int oldSize = _buckets[bucket].getKeyCount(); - int numInBucket = _buckets[bucket].add(peer); - if (numInBucket != oldSize) - _size.incrementAndGet(); - if (numInBucket > BUCKET_SIZE) { - // perhaps queue up coalesce job? naaahh.. lets let 'er grow for now - } - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Peer " + peer + " added to bucket " + bucket); - return oldSize != numInBucket; - } else { - throw new IllegalArgumentException("Unable to pick a bucket. wtf!"); - } - } - - /** - * Not an exact count (due to concurrency issues) but generally correct - * - */ - public int size() { - return _size.get(); - /* - int size = 0; - for (int i = 0; i < _buckets.length; i++) - size += _buckets[i].getKeyCount(); - return size; - */ - } - - public boolean remove(Hash entry) { - int bucket = pickBucket(entry); - KBucket kbucket = getBucket(bucket); - boolean removed = kbucket.remove(entry); - if (removed) - _size.decrementAndGet(); - return removed; - } - - /** @since 0.8.8 */ - public void clear() { - for (int i = 0; i < _buckets.length; i++) { - _buckets[i].setEntries(Collections. emptySet()); - } - _size.set(0); - _us.clearXorCache(); - } - - public Set getAll() { return getAll(Collections. emptySet()); }; - - public Set getAll(Set toIgnore) { - Set all = new HashSet(1024); - for (int i = 0; i < _buckets.length; i++) { - all.addAll(_buckets[i].getEntries()); - } - all.removeAll(toIgnore); - return all; - } - - public void getAll(SelectionCollector collector) { - long start = _context.clock().now(); - for (int i = 0; i < _buckets.length; i++) - _buckets[i].getEntries(collector); - _context.statManager().addRateData("netDb.KBSGetAllTime", _context.clock().now() - start, 0); - } - - public int pickBucket(Hash key) { - for (int i = 0; i < NUM_BUCKETS; i++) { - if (_buckets[i].shouldContain(key)) - return i; - } - _log.error("Key does not fit in any bucket?! WTF!\nKey : [" - + DataHelper.toHexString(key.getData()) + "]" - + "\nUs : [" + toString(_us.getData()) + "]" - + "\nDelta: [" - + DataHelper.toHexString(DataHelper.xor(_us.getData(), key.getData())) - + "]", new Exception("WTF")); - displayBuckets(); - return -1; - } - - public KBucket getBucket(int bucket) { return _buckets[bucket]; } - - protected KBucket[] createBuckets() { - KBucket[] buckets = new KBucket[NUM_BUCKETS]; - for (int i = 0; i < NUM_BUCKETS-1; i++) { - buckets[i] = createBucket(i*BASE, (i+1)*BASE); - } - buckets[NUM_BUCKETS-1] = createBucket(BASE*(NUM_BUCKETS-1), BASE*(NUM_BUCKETS) + 1); - return buckets; - } - - protected KBucket createBucket(int start, int end) { - KBucket bucket = new KBucketImpl(_context, _us); - bucket.setRange(start, end); - _log.debug("Creating a bucket from " + start + " to " + (end)); - return bucket; - } - - public void displayBuckets() { - _log.info(toString()); - } - - @Override - public String toString() { - BigInteger us = new BigInteger(1, _us.getData()); - StringBuilder buf = new StringBuilder(1024); - buf.append("Bucket set rooted on: ").append(us.toString()).append(" (aka ").append(us.toString(2)).append("): \n"); - for (int i = 0; i < NUM_BUCKETS; i++) { - buf.append("* Bucket ").append(i).append("/").append(NUM_BUCKETS-1).append(": )\n"); - buf.append("Start: ").append("2^").append(_buckets[i].getRangeBegin()).append(")\n"); - buf.append("End: ").append("2^").append(_buckets[i].getRangeEnd()).append(")\n"); - buf.append("Contents:").append(_buckets[i].toString()).append("\n"); - } - - return buf.toString(); - } - - final static String toString(byte b[]) { - byte val[] = new byte[Hash.HASH_LENGTH]; - if (b.length < 32) - System.arraycopy(b, 0, val, Hash.HASH_LENGTH-b.length-1, b.length); - else - System.arraycopy(b, Hash.HASH_LENGTH-b.length, val, 0, val.length); - StringBuilder buf = new StringBuilder(KEYSIZE_BITS); - for (int i = 0; i < val.length; i++) { - for (int j = 7; j >= 0; j--) { - boolean bb = (0 != (val[i] & (1< _kb; // peer hashes sorted into kbuckets, but within kbuckets, unsorted private DataStore _ds; // hash to DataStructure mapping, persisted when necessary /** where the data store is pushing the data */ private String _dbDir; @@ -132,7 +137,14 @@ void searchComplete(Hash key) { */ protected final static long PUBLISH_JOB_DELAY = 5*60*1000l; - private static final int MAX_EXPLORE_QUEUE = 128; + static final int MAX_EXPLORE_QUEUE = 128; + + /** + * kad K + * Was 500 in old implementation but that was with B ~= -8! + */ + private static final int BUCKET_SIZE = 24; + private static final int KAD_B = 4; public KademliaNetworkDatabaseFacade(RouterContext context) { _context = context; @@ -168,7 +180,7 @@ public ReseedChecker reseedChecker() { return _reseedChecker; } - KBucketSet getKBuckets() { return _kb; } + KBucketSet getKBuckets() { return _kb; } DataStore getDataStore() { return _ds; } long getLastExploreNewDate() { return _lastExploreNew; } @@ -185,13 +197,13 @@ public Set getExploreKeys() { return Collections.unmodifiableSet(_exploreKeys); } - public void removeFromExploreKeys(Set toRemove) { + public void removeFromExploreKeys(Collection toRemove) { if (!_initialized) return; _exploreKeys.removeAll(toRemove); _context.statManager().addRateData("netDb.exploreKeySet", _exploreKeys.size(), 0); } - public void queueForExploration(Set keys) { + public void queueForExploration(Collection keys) { if (!_initialized) return; for (Iterator iter = keys.iterator(); iter.hasNext() && _exploreKeys.size() < MAX_EXPLORE_QUEUE; ) { _exploreKeys.add(iter.next()); @@ -240,7 +252,8 @@ public synchronized void startup() { _log.info("Starting up the kademlia network database"); RouterInfo ri = _context.router().getRouterInfo(); String dbDir = _context.getProperty(PROP_DB_DIR, DEFAULT_DB_DIR); - _kb = new KBucketSet(_context, ri.getIdentity().getHash()); + _kb = new KBucketSet(_context, ri.getIdentity().getHash(), + BUCKET_SIZE, KAD_B, new RejectTrimmer()); try { _ds = new PersistentDataStore(_context, dbDir, this); } catch (IOException ioe) { @@ -262,9 +275,11 @@ public synchronized void startup() { //// expire some routers // Don't run until after RefreshRoutersJob has run, and after validate() will return invalid for old routers. - Job erj = new ExpireRoutersJob(_context, this); - erj.getTiming().setStartAfter(_context.clock().now() + ROUTER_INFO_EXPIRATION_FLOODFILL + 10*60*1000); - _context.jobQueue().addJob(erj); + if (!_context.commSystem().isDummy()) { + Job erj = new ExpireRoutersJob(_context, this); + erj.getTiming().setStartAfter(_context.clock().now() + ROUTER_INFO_EXPIRATION_FLOODFILL + 10*60*1000); + _context.jobQueue().addJob(erj); + } if (!QUIET) { // fill the search queue with random keys in buckets that are too small @@ -360,15 +375,31 @@ public Set getAllRouters() { return rv; } + /** + * This used to return the number of routers that were in + * both the kbuckets AND the data store, which was fine when the kbuckets held everything. + * But now that is probably not what you want. + * Just return the count in the data store. + */ @Override public int getKnownRouters() { +/**** if (_kb == null) return 0; CountRouters count = new CountRouters(); _kb.getAll(count); return count.size(); +****/ + if (_ds == null) return 0; + int rv = 0; + for (DatabaseEntry ds : _ds.getEntries()) { + if (ds.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) + rv++; + } + return rv; } - private class CountRouters implements SelectionCollector { +/**** + private class CountRouters implements SelectionCollector { private int _count; public int size() { return _count; } public void add(Hash entry) { @@ -378,6 +409,7 @@ public void add(Hash entry) { _count++; } } +****/ /** * This is only used by StatisticsManager to publish @@ -403,6 +435,7 @@ public int getKnownLeaseSets() { * This is fast and doesn't use synchronization, * but it includes both routerinfos and leasesets. * Use it to avoid deadlocks. + * No - not true - the KBS contains RIs only. */ protected int getKBucketSetSize() { if (_kb == null) return 0; @@ -446,7 +479,20 @@ DatabaseEntry lookupLocallyWithoutValidation(Hash key) { return _ds.get(key); } + /** + * Lookup using exploratory tunnels + */ public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) { + lookupLeaseSet(key, onFindJob, onFailedLookupJob, timeoutMs, null); + } + + /** + * Lookup using the client's tunnels + * @param fromLocalDest use these tunnels for the lookup, or null for exploratory + * @since 0.9.10 + */ + public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, + long timeoutMs, Hash fromLocalDest) { if (!_initialized) return; LeaseSet ls = lookupLeaseSetLocally(key); if (ls != null) { @@ -457,7 +503,7 @@ public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("leaseSet not found locally, running search"); - search(key, onFindJob, onFailedLookupJob, timeoutMs, true); + search(key, onFindJob, onFailedLookupJob, timeoutMs, true, fromLocalDest); } if (_log.shouldLog(Log.DEBUG)) _log.debug("after lookupLeaseSet"); @@ -527,14 +573,17 @@ public RouterInfo lookupRouterInfoLocally(Hash key) { private static final long PUBLISH_DELAY = 3*1000; - public void publish(LeaseSet localLeaseSet) { + /** + * @throws IllegalArgumentException if the leaseSet is not valid + */ + public void publish(LeaseSet localLeaseSet) throws IllegalArgumentException { if (!_initialized) return; Hash h = localLeaseSet.getDestination().calculateHash(); try { store(h, localLeaseSet); } catch (IllegalArgumentException iae) { _log.error("wtf, locally published leaseSet is not valid?", iae); - return; + throw iae; } if (!_context.clientManager().shouldPublishLeaseSet(h)) return; @@ -984,6 +1033,16 @@ SearchJob search(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, return searchJob; } + /** + * Unused - see FNDF + * @throws UnsupportedOperationException always + * @since 0.9.10 + */ + SearchJob search(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, boolean isLease, + Hash fromLocalDest) { + throw new UnsupportedOperationException(); + } + /** public for NetDbRenderer in routerconsole */ @Override public Set getLeases() { @@ -1042,4 +1101,13 @@ public void sendStore(Hash key, DatabaseEntry ds, Job onSuccess, Job onFailure, } _context.jobQueue().addJob(new StoreJob(_context, this, key, ds, onSuccess, onFailure, sendTimeout, toIgnore)); } + + /** + * Debug info, HTML formatted + * @since 0.9.10 + */ + @Override + public void renderStatusHTML(Writer out) throws IOException { + out.write(_kb.toString().replace("\n", "
    \n")); + } } diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/PeerSelector.java b/common/java/router/net/i2p/router/networkdb/kademlia/PeerSelector.java index 1dc2de7..f73eb00 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/PeerSelector.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/PeerSelector.java @@ -17,6 +17,8 @@ import net.i2p.data.Hash; import net.i2p.data.RouterInfo; +import net.i2p.kademlia.KBucketSet; +import net.i2p.kademlia.SelectionCollector; import net.i2p.router.RouterContext; import net.i2p.router.util.HashDistance; import net.i2p.util.Log; @@ -41,7 +43,7 @@ public PeerSelector(RouterContext ctx) { * * @return ordered list of Hash objects */ - List selectMostReliablePeers(Hash key, int numClosest, Set alreadyChecked, KBucketSet kbuckets) { + List selectMostReliablePeers(Hash key, int numClosest, Set alreadyChecked, KBucketSet kbuckets) { // get the peers closest to the key return selectNearestExplicit(key, numClosest, alreadyChecked, kbuckets); } @@ -54,7 +56,7 @@ List selectMostReliablePeers(Hash key, int numClosest, Set alreadyCh * * @return List of Hash for the peers selected, ordered by bucket (but intra bucket order is not defined) */ - List selectNearestExplicit(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { + List selectNearestExplicit(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { //if (true) return selectNearestExplicitThin(key, maxNumRouters, peersToIgnore, kbuckets); @@ -94,7 +96,7 @@ List selectNearestExplicit(Hash key, int maxNumRouters, Set peersToI * * @return List of Hash for the peers selected, ordered by bucket (but intra bucket order is not defined) */ - List selectNearestExplicitThin(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { + List selectNearestExplicitThin(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { if (peersToIgnore == null) peersToIgnore = new HashSet(1); peersToIgnore.add(_context.routerHash()); @@ -109,7 +111,7 @@ List selectNearestExplicitThin(Hash key, int maxNumRouters, Set peer } /** UNUSED */ - private class MatchSelectionCollector implements SelectionCollector { + private class MatchSelectionCollector implements SelectionCollector { private TreeMap _sorted; private Hash _key; private Set _toIgnore; @@ -200,7 +202,7 @@ private void removeFailingPeers(Set peerHashes) { * @param peersToIgnore can be null * @return List of Hash for the peers selected, ordered by bucket (but intra bucket order is not defined) */ - List selectNearest(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { + List selectNearest(Hash key, int maxNumRouters, Set peersToIgnore, KBucketSet kbuckets) { // sure, this may not be exactly correct per kademlia (peers on the border of a kbucket in strict kademlia // would behave differently) but I can see no reason to keep around an /additional/ more complicated algorithm. // later if/when selectNearestExplicit gets costly, we may revisit this (since kbuckets let us cache the distance() diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/common/java/router/net/i2p/router/networkdb/kademlia/PersistentDataStore.java index 3074bfa..e187d27 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/PersistentDataStore.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/PersistentDataStore.java @@ -16,7 +16,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; @@ -398,6 +401,10 @@ private void readFiles() { // move all new RIs to subdirs, then scan those if (routerInfoFiles != null) migrate(_dbDir, routerInfoFiles); + // Loading the files in-order causes clumping in the kbuckets, + // and bias on early peer selection, so first collect all the files, + // then shuffle and load. + List toRead = new ArrayList(2048); for (int j = 0; j < B64.length(); j++) { File subdir = new File(_dbDir, DIR_PREFIX + B64.charAt(j)); File[] files = subdir.listFiles(RouterInfoFilter.getInstance()); @@ -410,11 +417,15 @@ private void readFiles() { if (lastMod <= _lastModified) continue; for (int i = 0; i < files.length; i++) { - Hash key = getRouterInfoHash(files[i].getName()); - if (key != null && !isKnown(key)) - (new ReadRouterJob(files[i], key)).runJob(); + toRead.add(files[i]); } } + Collections.shuffle(toRead, _context.random()); + for (File file : toRead) { + Hash key = getRouterInfoHash(file.getName()); + if (key != null && !isKnown(key)) + (new ReadRouterJob(file, key)).runJob(); + } } if (!_initialized) { @@ -467,7 +478,7 @@ public void runJob() { if (!shouldRead()) return; if (_log.shouldLog(Log.DEBUG)) _log.debug("Reading " + _routerFile); - try { + InputStream fis = null; boolean corrupt = false; try { @@ -508,14 +519,14 @@ public void runJob() { if (_log.shouldLog(Log.INFO)) _log.info("Error reading the routerInfo from " + _routerFile.getName(), dfe); corrupt = true; + } catch (IOException ioe) { + if (_log.shouldLog(Log.INFO)) + _log.info("Unable to read the router reference in " + _routerFile.getName(), ioe); + corrupt = true; } finally { if (fis != null) try { fis.close(); } catch (IOException ioe) {} } if (corrupt) _routerFile.delete(); - } catch (IOException ioe) { - if (_log.shouldLog(Log.INFO)) - _log.info("Unable to read the router reference in " + _routerFile.getName(), ioe); - } } } diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/SearchState.java b/common/java/router/net/i2p/router/networkdb/kademlia/SearchState.java index 9a4f99c..106c8ad 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/SearchState.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/SearchState.java @@ -10,6 +10,7 @@ import java.util.TreeSet; import net.i2p.data.Hash; +import net.i2p.kademlia.XORComparator; import net.i2p.router.RouterContext; /** @@ -44,12 +45,12 @@ public SearchState(RouterContext context, Hash key) { public Hash getTarget() { return _searchKey; } public Set getPending() { synchronized (_pendingPeers) { - return (Set)_pendingPeers.clone(); + return new HashSet(_pendingPeers); } } public Set getAttempted() { synchronized (_attemptedPeers) { - return (Set)_attemptedPeers.clone(); + return new HashSet(_attemptedPeers); } } public Set getClosestAttempted(int max) { @@ -61,7 +62,7 @@ public Set getClosestAttempted(int max) { private Set locked_getClosest(Set peers, int max, Hash target) { if (_attemptedPeers.size() <= max) return new HashSet(_attemptedPeers); - TreeSet closest = new TreeSet(new XORComparator(target)); + TreeSet closest = new TreeSet(new XORComparator(target)); closest.addAll(_attemptedPeers); Set rv = new HashSet(max); int i = 0; @@ -78,12 +79,12 @@ public boolean wasAttempted(Hash peer) { } public Set getSuccessful() { synchronized (_successfulPeers) { - return (Set)_successfulPeers.clone(); + return new HashSet(_successfulPeers); } } public Set getFailed() { synchronized (_failedPeers) { - return (Set)_failedPeers.clone(); + return new HashSet(_failedPeers); } } public boolean completed() { return _completed != -1; } @@ -155,7 +156,7 @@ public long replyFound(Hash peer) { } } - public Set getRepliedPeers() { synchronized (_repliedPeers) { return (Set)_repliedPeers.clone(); } } + public Set getRepliedPeers() { synchronized (_repliedPeers) { return new HashSet(_repliedPeers); } } public void replyTimeout(Hash peer) { synchronized (_pendingPeers) { diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/SelectionCollector.java b/common/java/router/net/i2p/router/networkdb/kademlia/SelectionCollector.java deleted file mode 100644 index 020da4d..0000000 --- a/common/java/router/net/i2p/router/networkdb/kademlia/SelectionCollector.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.i2p.router.networkdb.kademlia; - -import net.i2p.data.Hash; - -/** - * Visit kbuckets, gathering matches - */ -interface SelectionCollector { - public void add(Hash entry); -} diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/StoreJob.java b/common/java/router/net/i2p/router/networkdb/kademlia/StoreJob.java index 72f6315..d837c1f 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/StoreJob.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/StoreJob.java @@ -9,7 +9,6 @@ */ import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.Set; @@ -19,6 +18,7 @@ import net.i2p.data.TunnelId; import net.i2p.data.i2np.DatabaseStoreMessage; import net.i2p.data.i2np.I2NPMessage; +import net.i2p.kademlia.KBucketSet; import net.i2p.router.Job; import net.i2p.router.JobImpl; import net.i2p.router.OutNetMessage; @@ -157,8 +157,7 @@ private synchronized void continueSending() { //_state.addPending(closestHashes); if (_log.shouldLog(Log.INFO)) _log.info(getJobId() + ": Continue sending key " + _state.getTarget() + " after " + _state.getAttempted().size() + " tries to " + closestHashes); - for (Iterator iter = closestHashes.iterator(); iter.hasNext(); ) { - Hash peer = iter.next(); + for (Hash peer : closestHashes) { DatabaseEntry ds = _facade.getDataStore().get(peer); if ( (ds == null) || !(ds.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) ) { if (_log.shouldLog(Log.INFO)) @@ -233,7 +232,7 @@ private List getMostReliableRouters(Hash key, int numClosest, Set al private List getClosestFloodfillRouters(Hash key, int numClosest, Set alreadyChecked) { Hash rkey = getContext().routingKeyGenerator().getRoutingKey(key); - KBucketSet ks = _facade.getKBuckets(); + KBucketSet ks = _facade.getKBuckets(); if (ks == null) return new ArrayList(); return ((FloodfillPeerSelector)_peerSelector).selectFloodfillParticipants(rkey, numClosest, alreadyChecked, ks); } diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/StoreState.java b/common/java/router/net/i2p/router/networkdb/kademlia/StoreState.java index b599186..cbe5037 100644 --- a/common/java/router/net/i2p/router/networkdb/kademlia/StoreState.java +++ b/common/java/router/net/i2p/router/networkdb/kademlia/StoreState.java @@ -4,7 +4,6 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -58,17 +57,17 @@ public StoreState(RouterContext ctx, Hash key, DatabaseEntry data, Set toS public DatabaseEntry getData() { return _data; } public Set getPending() { synchronized (_pendingPeers) { - return (Set)_pendingPeers.clone(); + return new HashSet(_pendingPeers); } } public Set getAttempted() { synchronized (_attemptedPeers) { - return (Set)_attemptedPeers.clone(); + return new HashSet(_attemptedPeers); } } public Set getSuccessful() { synchronized (_successfulPeers) { - return (Set)_successfulPeers.clone(); + return new HashSet(_successfulPeers); } } /** unused */ @@ -82,7 +81,7 @@ public Set getSuccessfulExploratory() { public Set getFailed() { synchronized (_failedPeers) { - return (Set)_failedPeers.clone(); + return new HashSet(_failedPeers); } } public boolean completed() { return _completed != -1; } @@ -124,8 +123,8 @@ public void addPending(Hash peer) { public void addPending(Collection pending) { synchronized (_pendingPeers) { _pendingPeers.addAll(pending); - for (Iterator iter = pending.iterator(); iter.hasNext(); ) - _pendingPeerTimes.put(iter.next(), Long.valueOf(_context.clock().now())); + for (Hash peer : pending) + _pendingPeerTimes.put(peer, Long.valueOf(_context.clock().now())); } synchronized (_attemptedPeers) { _attemptedPeers.addAll(pending); @@ -191,32 +190,28 @@ public String toString() { buf.append(" Attempted: "); synchronized (_attemptedPeers) { buf.append(_attemptedPeers.size()).append(' '); - for (Iterator iter = _attemptedPeers.iterator(); iter.hasNext(); ) { - Hash peer = iter.next(); + for (Hash peer : _attemptedPeers) { buf.append(peer.toBase64()).append(" "); } } buf.append(" Pending: "); synchronized (_pendingPeers) { buf.append(_pendingPeers.size()).append(' '); - for (Iterator iter = _pendingPeers.iterator(); iter.hasNext(); ) { - Hash peer = iter.next(); + for (Hash peer : _pendingPeers) { buf.append(peer.toBase64()).append(" "); } } buf.append(" Failed: "); synchronized (_failedPeers) { buf.append(_failedPeers.size()).append(' '); - for (Iterator iter = _failedPeers.iterator(); iter.hasNext(); ) { - Hash peer = iter.next(); + for (Hash peer : _failedPeers) { buf.append(peer.toBase64()).append(" "); } } buf.append(" Successful: "); synchronized (_successfulPeers) { buf.append(_successfulPeers.size()).append(' '); - for (Iterator iter = _successfulPeers.iterator(); iter.hasNext(); ) { - Hash peer = iter.next(); + for (Hash peer : _successfulPeers) { buf.append(peer.toBase64()).append(" "); } } diff --git a/common/java/router/net/i2p/router/networkdb/kademlia/XORComparator.java b/common/java/router/net/i2p/router/networkdb/kademlia/XORComparator.java deleted file mode 100644 index cd734b6..0000000 --- a/common/java/router/net/i2p/router/networkdb/kademlia/XORComparator.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.i2p.router.networkdb.kademlia; - -import java.util.Comparator; - -import net.i2p.data.Hash; - -/** - * Help sort Hashes in relation to a base key using the XOR metric. - */ -class XORComparator implements Comparator { - private final byte[] _base; - - /** - * @param target key to compare distances with - */ - public XORComparator(Hash target) { - _base = target.getData(); - } - - /** - * getData() of args must be non-null - */ - public int compare(Hash lhs, Hash rhs) { - byte lhsb[] = lhs.getData(); - byte rhsb[] = rhs.getData(); - for (int i = 0; i < _base.length; i++) { - int ld = (lhsb[i] ^ _base[i]) & 0xff; - int rd = (rhsb[i] ^ _base[i]) & 0xff; - if (ld < rd) - return -1; - if (ld > rd) - return 1; - } - return 0; - } -} diff --git a/common/java/router/net/i2p/router/networkdb/reseed/Reseeder.java b/common/java/router/net/i2p/router/networkdb/reseed/Reseeder.java index b696fa8..3620352 100644 --- a/common/java/router/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/common/java/router/net/i2p/router/networkdb/reseed/Reseeder.java @@ -61,7 +61,7 @@ public class Reseeder { * URLs are constructed, and because SSLEepGet doesn't follow redirects. */ public static final String DEFAULT_SEED_URL = - "http://netdb.i2p2.de/" + "," + + //http://netdb.i2p2.de/" + "," + "http://reseed.i2p-projekt.de/" + "," + //"http://euve5653.vserver.de/netDb/" + "," + "http://cowpuncher.drollette.com/netdb/" + "," + @@ -70,13 +70,15 @@ public class Reseeder { "http://netdb.i2p2.no/" + "," + "http://reseed.info/" + "," + "http://reseed.pkol.de/" + "," + + "http://uk.reseed.i2p2.no/" + "," + + "http://i2p-netdb.innovatio.no/" + "," + "http://ieb9oopo.mooo.com"; // Temp disabled since h2ik have been AWOL since 06-03-2013 //"http://i2p.feared.eu/"; /** @since 0.8.2 */ public static final String DEFAULT_SSL_SEED_URL = - "https://netdb.i2p2.de/" + "," + + //"https://netdb.i2p2.de/" + "," + "https://reseed.i2p-projekt.de/" + "," + //"https://euve5653.vserver.de/netDb/" + "," + "https://cowpuncher.drollette.com/netdb/" + "," + @@ -85,6 +87,8 @@ public class Reseeder { "https://netdb.i2p2.no/" + "," + "https://reseed.info/" + "," + "https://reseed.pkol.de/" + "," + + "https://uk.reseed.i2p2.no/" + "," + + "https://i2p-netdb.innovatio.no/" + "," + "https://ieb9oopo.mooo.com"; // Temp disabled since h2ik have been AWOL since 06-03-2013 //"https://i2p.feared.eu/"; diff --git a/common/java/router/net/i2p/router/peermanager/CapacityCalculator.java b/common/java/router/net/i2p/router/peermanager/CapacityCalculator.java index 2ff8650..0979f5d 100644 --- a/common/java/router/net/i2p/router/peermanager/CapacityCalculator.java +++ b/common/java/router/net/i2p/router/peermanager/CapacityCalculator.java @@ -67,6 +67,7 @@ else if (profile.getTunnelHistory().getLastRejectedProbabalistic() > now - 5*60* // boost connected peers if (profile.isEstablished()) capacity += BONUS_ESTABLISHED; + /**** // boost same country if (profile.isSameCountry()) { double bonus = BONUS_SAME_COUNTRY; @@ -78,6 +79,7 @@ else if (profile.getTunnelHistory().getLastRejectedProbabalistic() > now - 5*60* } capacity += bonus; } + ****/ // penalize unreachable peers if (profile.wasUnreachable()) capacity -= PENALTY_UNREACHABLE; diff --git a/common/java/router/net/i2p/router/peermanager/PeerManager.java b/common/java/router/net/i2p/router/peermanager/PeerManager.java index 904ca8b..ef7b1de 100644 --- a/common/java/router/net/i2p/router/peermanager/PeerManager.java +++ b/common/java/router/net/i2p/router/peermanager/PeerManager.java @@ -25,6 +25,7 @@ import net.i2p.router.RouterContext; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.util.ConcurrentHashSet; +import net.i2p.util.I2PThread; import net.i2p.util.Log; import net.i2p.util.SimpleTimer2; @@ -89,6 +90,24 @@ public Reorg() { super(_context.simpleTimer2(), REORGANIZE_TIME); } public void timeReached() { + (new ReorgThread(this)).start(); + } + } + + /** + * This takes too long to run on the SimpleTimer2 queue + * @since 0.9.10 + */ + private class ReorgThread extends I2PThread { + private SimpleTimer2.TimedEvent _event; + + public ReorgThread(SimpleTimer2.TimedEvent event) { + super("PeerManager Reorg"); + setDaemon(true); + _event = event; + } + + public void run() { long start = System.currentTimeMillis(); try { _organizer.reorganize(true); @@ -104,7 +123,7 @@ else if (uptime > 10*60*1000) delay = REORGANIZE_TIME_MEDIUM; else delay = REORGANIZE_TIME; - schedule(delay); + _event.schedule(delay); } } diff --git a/common/java/router/net/i2p/router/peermanager/ProfileOrganizer.java b/common/java/router/net/i2p/router/peermanager/ProfileOrganizer.java index 4d5a654..1edfbe4 100644 --- a/common/java/router/net/i2p/router/peermanager/ProfileOrganizer.java +++ b/common/java/router/net/i2p/router/peermanager/ProfileOrganizer.java @@ -236,15 +236,13 @@ public int countActivePeers() { getReadLock(); try { - for (Iterator iter = _failingPeers.values().iterator(); iter.hasNext(); ) { - PeerProfile profile = iter.next(); + for (PeerProfile profile : _failingPeers.values()) { if (profile.getLastSendSuccessful() >= hideBefore) activePeers++; else if (profile.getLastHeardFrom() >= hideBefore) activePeers++; } - for (Iterator iter = _notFailingPeers.values().iterator(); iter.hasNext(); ) { - PeerProfile profile = iter.next(); + for (PeerProfile profile : _notFailingPeers.values()) { if (profile.getLastSendSuccessful() >= hideBefore) activePeers++; else if (profile.getLastHeardFrom() >= hideBefore) @@ -539,8 +537,7 @@ public void selectActiveNotFailingPeers(int howMany, Set exclude, Set iter = _notFailingPeers.keySet().iterator(); iter.hasNext(); ) { - Hash peer = iter.next(); + for (Hash peer : _notFailingPeers.keySet()) { if (!_context.commSystem().isEstablished(peer)) exclude.add(peer); } @@ -567,8 +564,7 @@ private void selectActiveNotFailingPeers2(int howMany, Set exclude, Set activePeers = new HashMap(); getReadLock(); try { - for (Iterator> iter = _notFailingPeers.entrySet().iterator(); iter.hasNext(); ) { - Map.Entry e = iter.next(); + for (Map.Entry e : _notFailingPeers.entrySet()) { if (_context.commSystem().isEstablished(e.getKey())) activePeers.put(e.getKey(), e.getValue()); } @@ -666,8 +662,7 @@ public List selectPeersLocallyUnreachable() { n = new ArrayList(_notFailingPeers.keySet()); } finally { releaseReadLock(); } List l = new ArrayList(count / 4); - for (Iterator iter = n.iterator(); iter.hasNext(); ) { - Hash peer = iter.next(); + for (Hash peer : n) { if (_context.commSystem().wasUnreachable(peer)) l.add(peer); else { @@ -717,8 +712,7 @@ public List selectPeersRecentlyRejecting() { long cutoff = _context.clock().now() - (20*1000); int count = _notFailingPeers.size(); List l = new ArrayList(count / 128); - for (Iterator iter = _notFailingPeers.values().iterator(); iter.hasNext(); ) { - PeerProfile prof = iter.next(); + for (PeerProfile prof : _notFailingPeers.values()) { if (prof.getTunnelHistory().getLastRejectedBandwidth() > cutoff) l.add(prof.getPeer()); } @@ -779,8 +773,7 @@ public void reorganize(boolean shouldCoalesce) { if (shouldCoalesce) { getReadLock(); try { - for (Iterator iter = _strictCapacityOrder.iterator(); iter.hasNext(); ) { - PeerProfile prof = iter.next(); + for (PeerProfile prof : _strictCapacityOrder) { if ( (expireOlderThan > 0) && (prof.getLastSendSuccessful() <= expireOlderThan) ) { continue; } @@ -887,8 +880,7 @@ private void locked_promoteFastAsNecessary() { if (numToPromote > 0) { if (_log.shouldLog(Log.INFO)) _log.info("Need to explicitly promote " + numToPromote + " peers to the fast group"); - for (Iterator iter = _strictCapacityOrder.iterator(); iter.hasNext(); ) { - PeerProfile cur = iter.next(); + for (PeerProfile cur : _strictCapacityOrder) { if ( (!_fastPeers.containsKey(cur.getPeer())) && (!cur.getIsFailing()) ) { if (!isSelectable(cur.getPeer())) { // skip peers we dont have in the netDb @@ -990,8 +982,7 @@ private void locked_unfailAsNecessary() { int needToUnfail = MIN_NOT_FAILING_ACTIVE - notFailingActive; if (needToUnfail > 0) { int unfailed = 0; - for (Iterator iter = _strictCapacityOrder.iterator(); iter.hasNext(); ) { - PeerProfile best = iter.next(); + for (PeerProfile best : _strictCapacityOrder) { if ( (best.getIsActive()) && (best.getIsFailing()) ) { if (_log.shouldLog(Log.WARN)) _log.warn("All peers were failing, so we have overridden the failing flag for one of the most reliable active peers (" + best.getPeer().toBase64() + ")"); @@ -1022,9 +1013,7 @@ private void locked_calculateThresholds(Set allPeers) { double totalCapacity = 0; double totalIntegration = 0; Set reordered = new TreeSet(_comp); - for (Iterator iter = allPeers.iterator(); iter.hasNext(); ) { - PeerProfile profile = iter.next(); - + for (PeerProfile profile : allPeers) { if (_us.equals(profile.getPeer())) continue; // only take into account active peers that aren't failing @@ -1072,8 +1061,7 @@ private void locked_calculateCapacityThreshold(double totalCapacity, Set iter = reordered.iterator(); iter.hasNext(); ) { - PeerProfile profile = iter.next(); + for (PeerProfile profile : reordered) { double val = profile.getCapacityValue(); if (val > meanCapacity) numExceedingMean++; @@ -1164,8 +1152,7 @@ private void locked_calculateSpeedThreshold(Set reordered) { private void locked_calculateSpeedThresholdMean(Set reordered) { double total = 0; int count = 0; - for (Iterator iter = reordered.iterator(); iter.hasNext(); ) { - PeerProfile profile = iter.next(); + for (PeerProfile profile : reordered) { if (profile.getCapacityValue() >= _thresholdCapacityValue) { // duplicates being clobbered is fine by us total += profile.getSpeedValue(); @@ -1524,8 +1511,7 @@ public static void main(String args[]) { DecimalFormat fmt = new DecimalFormat("0,000.0"); fmt.setPositivePrefix("+"); - for (Iterator iter = organizer.selectAllPeers().iterator(); iter.hasNext(); ) { - Hash peer = iter.next(); + for (Hash peer : organizer.selectAllPeers()) { PeerProfile profile = organizer.getProfile(peer); if (!profile.getIsActive()) { System.out.println("Peer " + profile.getPeer().toBase64().substring(0,4) diff --git a/common/java/router/net/i2p/router/startup/LoadClientAppsJob.java b/common/java/router/net/i2p/router/startup/LoadClientAppsJob.java index e34a711..2724239 100644 --- a/common/java/router/net/i2p/router/startup/LoadClientAppsJob.java +++ b/common/java/router/net/i2p/router/startup/LoadClientAppsJob.java @@ -197,7 +197,7 @@ public static void runClientInline(String className, String clientName, String a log.info("Loading up the client application " + clientName + ": " + className + " " + Arrays.toString(args)); if (args == null) args = new String[0]; - Class cls = Class.forName(className, true, cl); + Class cls = Class.forName(className, true, cl); Method method = cls.getMethod("main", new Class[] { String[].class }); method.invoke(cls, new Object[] { args }); } @@ -264,16 +264,16 @@ public RunApp(String className, String appName, String args[], RouterContext ctx public void run() { try { - Class cls = Class.forName(_className, true, _cl); + Class cls = Class.forName(_className, true, _cl); if (isRouterApp(cls)) { - Constructor con = cls.getConstructor(RouterContext.class, ClientAppManager.class, String[].class); - RouterAppManager mgr = _ctx.clientAppManager(); + Constructor con = cls.getConstructor(RouterContext.class, ClientAppManager.class, String[].class); + RouterAppManager mgr = _ctx.routerAppManager(); Object[] conArgs = new Object[] {_ctx, _ctx.clientAppManager(), _args}; RouterApp app = (RouterApp) con.newInstance(conArgs); mgr.addAndStart(app, _args); } else if (isClientApp(cls)) { - Constructor con = cls.getConstructor(I2PAppContext.class, ClientAppManager.class, String[].class); - RouterAppManager mgr = _ctx.clientAppManager(); + Constructor con = cls.getConstructor(I2PAppContext.class, ClientAppManager.class, String[].class); + RouterAppManager mgr = _ctx.routerAppManager(); Object[] conArgs = new Object[] {_ctx, _ctx.clientAppManager(), _args}; ClientApp app = (ClientApp) con.newInstance(conArgs); mgr.addAndStart(app, _args); @@ -288,17 +288,17 @@ public void run() { _log.info("Done running client application " + _appName); } - private static boolean isRouterApp(Class cls) { + private static boolean isRouterApp(Class cls) { return isInterface(cls, RouterApp.class); } - private static boolean isClientApp(Class cls) { + private static boolean isClientApp(Class cls) { return isInterface(cls, ClientApp.class); } - private static boolean isInterface(Class cls, Class intfc) { + private static boolean isInterface(Class cls, Class intfc) { try { - Class[] intfcs = cls.getInterfaces(); + Class[] intfcs = cls.getInterfaces(); for (int i = 0; i < intfcs.length; i++) { if (intfcs[i] == intfc) return true; diff --git a/common/java/router/net/i2p/router/startup/RouterAppManager.java b/common/java/router/net/i2p/router/startup/RouterAppManager.java index 8f7ca21..229a2d3 100644 --- a/common/java/router/net/i2p/router/startup/RouterAppManager.java +++ b/common/java/router/net/i2p/router/startup/RouterAppManager.java @@ -138,8 +138,13 @@ public void notify(ClientApp app, ClientAppState state, String message, Exceptio * @return true if successful, false if duplicate name */ public boolean register(ClientApp app) { - if (!_clients.containsKey(app)) - return false; + if (!_clients.containsKey(app)) { + // Allow registration even if we didn't start it, + // useful for plugins + if (_log.shouldLog(Log.INFO)) + _log.info("Registering untracked client " + app.getName()); + //return false; + } if (_log.shouldLog(Log.INFO)) _log.info("Client " + app.getDisplayName() + " REGISTERED AS " + app.getName()); // TODO if old app in there is not running and != this app, allow replacement diff --git a/common/java/router/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java b/common/java/router/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java index ae46915..2952c2e 100644 --- a/common/java/router/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java +++ b/common/java/router/net/i2p/router/tasks/UpdateRoutingKeyModifierJob.java @@ -8,11 +8,7 @@ * */ -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; - +import net.i2p.data.RoutingKeyGenerator; import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; import net.i2p.util.Log; @@ -26,7 +22,6 @@ */ public class UpdateRoutingKeyModifierJob extends JobImpl { private final Log _log; - private final Calendar _cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); // Run every 15 minutes in case of time zone change, clock skew, etc. private static final long MAX_DELAY_FAILSAFE = 15*60*1000; @@ -38,29 +33,11 @@ public UpdateRoutingKeyModifierJob(RouterContext ctx) { public String getName() { return "Update Routing Key Modifier"; } public void runJob() { + RoutingKeyGenerator gen = getContext().routingKeyGenerator(); // make sure we requeue quickly if just before midnight - long delay = Math.min(MAX_DELAY_FAILSAFE, getTimeTillMidnight()); + long delay = Math.max(5, Math.min(MAX_DELAY_FAILSAFE, gen.getTimeTillMidnight())); // TODO tell netdb if mod data changed? - getContext().routingKeyGenerator().generateDateBasedModData(); + gen.generateDateBasedModData(); requeue(delay); } - - private long getTimeTillMidnight() { - long now = getContext().clock().now(); - _cal.setTime(new Date(now)); - _cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR)); // gcj <= 4.0 workaround - _cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround - _cal.add(Calendar.DATE, 1); - _cal.set(Calendar.HOUR_OF_DAY, 0); - _cal.set(Calendar.MINUTE, 0); - _cal.set(Calendar.SECOND, 0); - _cal.set(Calendar.MILLISECOND, 0); - long then = _cal.getTime().getTime(); - long howLong = then - now; - if (howLong < 0) // hi kaffe - howLong = 24*60*60*1000l + howLong; - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Time till midnight: " + howLong + "ms"); - return howLong; - } } diff --git a/common/java/router/net/i2p/router/time/NtpClient.java b/common/java/router/net/i2p/router/time/NtpClient.java index a30b92f..2b78263 100644 --- a/common/java/router/net/i2p/router/time/NtpClient.java +++ b/common/java/router/net/i2p/router/time/NtpClient.java @@ -172,6 +172,7 @@ private static long[] currentTimeAndStratum(String serverName) { } } +/****/ public static void main(String[] args) throws IOException { // Process command-line args if(args.length <= 0) { @@ -184,11 +185,6 @@ public static void main(String[] args) throws IOException { System.out.println("Current time: " + new java.util.Date(now)); } - - - /** - * Prints usage - */ static void printUsage() { System.out.println( "NtpClient - an NTP client for Java.\n" + @@ -207,4 +203,5 @@ static void printUsage() { "more details."); } +/****/ } diff --git a/common/java/router/net/i2p/router/transport/CommSystemFacadeImpl.java b/common/java/router/net/i2p/router/transport/CommSystemFacadeImpl.java index 88ad248..ab1f3ad 100644 --- a/common/java/router/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/common/java/router/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -12,7 +12,6 @@ import java.io.Writer; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Vector; @@ -26,6 +25,7 @@ import net.i2p.router.transport.udp.UDPTransport; import net.i2p.router.util.EventLog; import net.i2p.util.Addresses; +import net.i2p.util.I2PThread; import net.i2p.util.Log; import net.i2p.util.SimpleTimer; import net.i2p.util.SimpleTimer2; @@ -223,6 +223,7 @@ public void notifyReplaceAddress(RouterAddress udpAddr) { /* We hope the routerinfos are read in and things have settled down by now, but it's not required to be so */ private static final int START_DELAY = 5*60*1000; private static final int LOOKUP_TIME = 30*60*1000; + private void startGeoIP() { _context.simpleScheduler().addEvent(new QueueAll(), START_DELAY); } @@ -233,8 +234,8 @@ private void startGeoIP() { */ private class QueueAll implements SimpleTimer.TimedEvent { public void timeReached() { - for (Iterator iter = _context.netDb().getAllRouters().iterator(); iter.hasNext(); ) { - RouterInfo ri = _context.netDb().lookupRouterInfoLocally(iter.next()); + for (Hash h : _context.netDb().getAllRouters()) { + RouterInfo ri = _context.netDb().lookupRouterInfoLocally(h); if (ri == null) continue; byte[] ip = getIP(ri); @@ -248,7 +249,26 @@ public void timeReached() { private class Lookup implements SimpleTimer.TimedEvent { public void timeReached() { + (new LookupThread()).start(); + } + } + + /** + * This takes too long to run on the SimpleTimer2 queue + * @since 0.9.10 + */ + private class LookupThread extends I2PThread { + + public LookupThread() { + super("GeoIP Lookup"); + setDaemon(true); + } + + public void run() { + long start = System.currentTimeMillis(); _geoIP.blockingLookup(); + if (_log.shouldLog(Log.INFO)) + _log.info("GeoIP lookup took " + (System.currentTimeMillis() - start)); } } diff --git a/common/java/router/net/i2p/router/transport/GeoIP.java b/common/java/router/net/i2p/router/transport/GeoIP.java index cad3654..3f0990d 100644 --- a/common/java/router/net/i2p/router/transport/GeoIP.java +++ b/common/java/router/net/i2p/router/transport/GeoIP.java @@ -194,10 +194,10 @@ private void readCountryFile() { _log.warn("Country file not found: " + geoFile.getAbsolutePath()); return; } - FileInputStream in = null; + BufferedReader br = null; try { - in = new FileInputStream(geoFile); - BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + br = new BufferedReader(new InputStreamReader( + new FileInputStream(geoFile), "UTF-8")); String line = null; while ( (line = br.readLine()) != null) { try { @@ -215,7 +215,7 @@ private void readCountryFile() { if (_log.shouldLog(Log.ERROR)) _log.error("Error reading the Country File", ioe); } finally { - if (in != null) try { in.close(); } catch (IOException ioe) {} + if (br != null) try { br.close(); } catch (IOException ioe) {} } } @@ -256,11 +256,11 @@ private String[] readGeoIPFile(Long[] search) { String[] rv = new String[search.length]; int idx = 0; long start = _context.clock().now(); - FileInputStream in = null; + BufferedReader br = null; try { - in = new FileInputStream(geoFile); String buf = null; - BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); + br = new BufferedReader(new InputStreamReader( + new FileInputStream(geoFile), "ISO-8859-1")); while ((buf = br.readLine()) != null && idx < search.length) { try { if (buf.charAt(0) == '#') { @@ -288,7 +288,7 @@ private String[] readGeoIPFile(Long[] search) { if (_log.shouldLog(Log.ERROR)) _log.error("Error reading the geoFile", ioe); } finally { - if (in != null) try { in.close(); } catch (IOException ioe) {} + if (br != null) try { br.close(); } catch (IOException ioe) {} } if (_log.shouldLog(Log.INFO)) { diff --git a/common/java/router/net/i2p/router/transport/GeoIPv6.java b/common/java/router/net/i2p/router/transport/GeoIPv6.java index f26e33a..b93e3f0 100644 --- a/common/java/router/net/i2p/router/transport/GeoIPv6.java +++ b/common/java/router/net/i2p/router/transport/GeoIPv6.java @@ -158,12 +158,13 @@ private static boolean compressGeoIPv6CSVFiles(List inFiles, File outFile) for (File geoFile : inFiles) { int count = 0; InputStream in = null; + BufferedReader br = null; try { in = new BufferedInputStream(new FileInputStream(geoFile)); if (geoFile.getName().endsWith(".gz")) in = new GZIPInputStream(in); String buf = null; - BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); + br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); while ((buf = br.readLine()) != null) { try { if (buf.charAt(0) == '#') { @@ -191,6 +192,7 @@ private static boolean compressGeoIPv6CSVFiles(List inFiles, File outFile) return false; } finally { if (in != null) try { in.close(); } catch (IOException ioe) {} + if (br != null) try { br.close(); } catch (IOException ioe) {} } } Collections.sort(entries); diff --git a/common/java/router/net/i2p/router/transport/UPnP.java b/common/java/router/net/i2p/router/transport/UPnP.java index 3c69e4d..582f922 100644 --- a/common/java/router/net/i2p/router/transport/UPnP.java +++ b/common/java/router/net/i2p/router/transport/UPnP.java @@ -280,8 +280,7 @@ private void registerPortMappings() { */ private void discoverService() { synchronized (lock) { - for (Iterator iter = _router.getDeviceList().iterator();iter.hasNext();) { - Device current = (Device)iter.next(); + for (Device current : _router.getDeviceList()) { if (!current.getDeviceType().equals(WAN_DEVICE)) continue; diff --git a/common/java/router/net/i2p/router/transport/ntcp/EventPumper.java b/common/java/router/net/i2p/router/transport/ntcp/EventPumper.java index 063d5f1..43cdba3 100644 --- a/common/java/router/net/i2p/router/transport/ntcp/EventPumper.java +++ b/common/java/router/net/i2p/router/transport/ntcp/EventPumper.java @@ -211,7 +211,7 @@ public void run() { int failsafeInvalid = 0; // Increase allowed idle time if we are well under allowed connections, otherwise decrease - if (_transport.haveCapacity(60)) + if (_transport.haveCapacity(45)) _expireIdleWriteTime = Math.min(_expireIdleWriteTime + 1000, MAX_EXPIRE_IDLE_TIME); else _expireIdleWriteTime = Math.max(_expireIdleWriteTime - 3000, MIN_EXPIRE_IDLE_TIME); diff --git a/common/java/router/net/i2p/router/transport/ntcp/NTCPConnection.java b/common/java/router/net/i2p/router/transport/ntcp/NTCPConnection.java index d637901..1aab41d 100644 --- a/common/java/router/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/common/java/router/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -97,7 +97,7 @@ class NTCPConnection { //private final CoDelPriorityBlockingQueue _outbound; private final PriBlockingQueue _outbound; /** - * current prepared OutNetMessage, or null - synchronize on _outbound to modify + * current prepared OutNetMessage, or null - synchronize on _outbound to modify or read * FIXME why do we need this??? */ private OutNetMessage _currentOutbound; @@ -302,12 +302,21 @@ public long getUptime() { public long getMessagesSent() { return _messagesWritten; } public long getMessagesReceived() { return _messagesRead; } - public long getOutboundQueueSize() { - int queued = _outbound.size(); - if (_currentOutbound != null) - queued++; + public long getOutboundQueueSize() { + int queued; + synchronized(_outbound) { + queued = _outbound.size(); + if (getCurrentOutbound() != null) + queued++; + } return queued; } + + private OutNetMessage getCurrentOutbound() { + synchronized(_outbound) { + return _currentOutbound; + } + } /** @return milliseconds */ public long getTimeSinceSend() { return System.currentTimeMillis()-_lastSendTime; } @@ -383,20 +392,12 @@ private synchronized NTCPConnection locked_close(boolean allowRequeue) { List pending = new ArrayList(); //_outbound.drainAllTo(pending); _outbound.drainTo(pending); - for (OutNetMessage msg : pending) { - Object buf = msg.releasePreparationBuffer(); - if (buf != null) - releaseBuf((PrepBuffer)buf); + for (OutNetMessage msg : pending) _transport.afterSend(msg, false, allowRequeue, msg.getLifetime()); - } - OutNetMessage msg = _currentOutbound; - if (msg != null) { - Object buf = msg.releasePreparationBuffer(); - if (buf != null) - releaseBuf((PrepBuffer)buf); + OutNetMessage msg = getCurrentOutbound(); + if (msg != null) _transport.afterSend(msg, false, allowRequeue, msg.getLifetime()); - } return old; } @@ -429,12 +430,11 @@ public void send(OutNetMessage msg) { _consecutiveBacklog = 0; ****/ //if (FAST_LARGE) - bufferedPrepare(msg); _outbound.offer(msg); //int enqueued = _outbound.size(); // although stat description says ahead of this one, not including this one... //_context.statManager().addRateData("ntcp.sendQueueSize", enqueued); - boolean noOutbound = (_currentOutbound == null); + boolean noOutbound = (getCurrentOutbound() == null); //if (_log.shouldLog(Log.DEBUG)) _log.debug("messages enqueued on " + toString() + ": " + enqueued + " new one: " + msg.getMessageId() + " of " + msg.getMessageType()); if (isEstablished() && noOutbound) _transport.getWriter().wantsWrite(this, "enqueued"); @@ -468,7 +468,7 @@ public boolean tooBacklogged() { int size = _outbound.size(); if (_log.shouldLog(Log.WARN)) { int writeBufs = _writeBufs.size(); - boolean currentOutboundSet = _currentOutbound != null; + boolean currentOutboundSet = getCurrentOutbound() != null; try { _log.warn("Too backlogged: size is " + size + ", wantsWrite? " + (0 != (_conKey.interestOps()&SelectionKey.OP_WRITE)) @@ -595,11 +595,13 @@ public synchronized void finishOutboundEstablishment(SessionKey key, long clockS /** * prepare the next i2np message for transmission. this should be run from * the Writer thread pool. + * + * @param prep an instance of PrepBuffer to use as scratch space * */ - synchronized void prepareNextWrite() { + synchronized void prepareNextWrite(PrepBuffer prep) { //if (FAST_LARGE) - prepareNextWriteFast(); + prepareNextWriteFast(prep); //else // prepareNextWriteSmall(); } @@ -708,9 +710,10 @@ private void prepareNextWriteSmall() { * the Writer thread pool. * * Caller must synchronize. + * @param buf a PrepBuffer to use as scratch space * */ - private void prepareNextWriteFast() { + private void prepareNextWriteFast(PrepBuffer buf) { if (_closed.get()) return; //if (_log.shouldLog(Log.DEBUG)) @@ -771,14 +774,7 @@ private void prepareNextWriteFast() { } //long begin = System.currentTimeMillis(); - PrepBuffer buf = (PrepBuffer)msg.releasePreparationBuffer(); - if (buf == null) { - // race, see ticket #392 - //throw new RuntimeException("buf is null for " + msg); - if (_log.shouldLog(Log.WARN)) - _log.warn("Null prep buf for " + msg); - return; - } + bufferedPrepare(msg,buf); _context.aes().encrypt(buf.unencrypted, 0, buf.encrypted, 0, _sessionKey, _prevWriteEnd, 0, buf.unencryptedLength); System.arraycopy(buf.encrypted, buf.encrypted.length-16, _prevWriteEnd, 0, _prevWriteEnd.length); //long encryptedTime = System.currentTimeMillis(); @@ -788,7 +784,6 @@ private void prepareNextWriteFast() { // + Base64.encode(unencrypted, 0, 16) + "..." + "\nIV=" + Base64.encode(_prevWriteEnd, 0, 16)); _transport.getPumper().wantsWrite(this, buf.encrypted); //long wantsTime = System.currentTimeMillis(); - releaseBuf(buf); //long releaseTime = System.currentTimeMillis(); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("prepared outbound " + System.identityHashCode(msg) @@ -808,16 +803,15 @@ private void prepareNextWriteFast() { /** * Serialize the message/checksum/padding/etc for transmission, but leave off - * the encryption for the actual write process (when we will always have the - * end of the previous encrypted transmission to serve as our IV). with care, - * the encryption could be handled here too, as long as messages aren't expired - * in the queue and the establishment process takes that into account. + * the encryption. This should be called from a Writer thread + * + * @param msg message to send + * @param buf PrepBuffer to use as scratch space */ - private void bufferedPrepare(OutNetMessage msg) { + private void bufferedPrepare(OutNetMessage msg, PrepBuffer buf) { //if (!_isInbound && !_established) // return; //long begin = System.currentTimeMillis(); - PrepBuffer buf = acquireBuf(); //long alloc = System.currentTimeMillis(); I2NPMessage m = msg.getMessage(); @@ -854,39 +848,12 @@ private void bufferedPrepare(OutNetMessage msg) { buf.encrypted = new byte[buf.unencryptedLength]; //long crced = System.currentTimeMillis(); - msg.prepared(buf); //if (_log.shouldLog(Log.DEBUG)) // _log.debug("Buffered prepare took " + (crced-begin) + ", alloc=" + (alloc-begin) // + " serialize=" + (serialized-alloc) + " crc=" + (crced-serialized)); } - - private static final int MIN_BUFS = 4; - private static final int MAX_BUFS = 16; - private static int NUM_PREP_BUFS; - static { - long maxMemory = SystemVersion.getMaxMemory(); - NUM_PREP_BUFS = (int) Math.max(MIN_BUFS, Math.min(MAX_BUFS, 1 + (maxMemory / (16*1024*1024)))); - } - - private final static LinkedBlockingQueue _bufs = new LinkedBlockingQueue(NUM_PREP_BUFS); - - /** - * 32KB each - * @return initialized buffer - */ - private static PrepBuffer acquireBuf() { - PrepBuffer b = _bufs.poll(); - if (b == null) - b = new PrepBuffer(); - return b; - } - private static void releaseBuf(PrepBuffer buf) { - buf.init(); - _bufs.offer(buf); - } - - private static class PrepBuffer { + public static class PrepBuffer { final byte unencrypted[]; int unencryptedLength; final byte base[]; @@ -894,13 +861,13 @@ private static class PrepBuffer { final Adler32 crc; byte encrypted[]; - PrepBuffer() { + public PrepBuffer() { unencrypted = new byte[BUFFER_SIZE]; base = new byte[BUFFER_SIZE]; crc = new Adler32(); } - private void init() { + public void init() { unencryptedLength = 0; baseLength = 0; encrypted = null; @@ -1072,8 +1039,7 @@ public void removeWriteBuf(ByteBuffer buf) { _log.info("I2NP meta message sent completely"); } - boolean msgs = ((!_outbound.isEmpty()) || (_currentOutbound != null)); - if (msgs) // push through the bw limiter to reach _writeBufs + if (getOutboundQueueSize() > 0) // push through the bw limiter to reach _writeBufs _transport.getWriter().wantsWrite(this, "write completed"); // this is not necessary, EventPumper.processWrite() handles this @@ -1359,7 +1325,6 @@ private static void releaseReadBuf(ByteArray buf) { */ static void releaseResources() { _i2npHandlers.clear(); - _bufs.clear(); } /** diff --git a/common/java/router/net/i2p/router/transport/ntcp/NTCPSendFinisher.java b/common/java/router/net/i2p/router/transport/ntcp/NTCPSendFinisher.java index cdaec19..d8441cb 100644 --- a/common/java/router/net/i2p/router/transport/ntcp/NTCPSendFinisher.java +++ b/common/java/router/net/i2p/router/transport/ntcp/NTCPSendFinisher.java @@ -69,7 +69,7 @@ private static class CustomThreadPoolExecutor extends ThreadPoolExecutor { public CustomThreadPoolExecutor(int num) { // use unbounded queue, so maximumPoolSize and keepAliveTime have no effect super(num, num, 1000, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), new CustomThreadFactory()); + new LinkedBlockingQueue(), new CustomThreadFactory()); } } diff --git a/common/java/router/net/i2p/router/transport/ntcp/Writer.java b/common/java/router/net/i2p/router/transport/ntcp/Writer.java index 3a5eb18..3d01bfc 100644 --- a/common/java/router/net/i2p/router/transport/ntcp/Writer.java +++ b/common/java/router/net/i2p/router/transport/ntcp/Writer.java @@ -80,9 +80,15 @@ public void connectionClosed(NTCPConnection con) { } private class Runner implements Runnable { + + /** a scratch space to serialize and encrypt messages */ + private final NTCPConnection.PrepBuffer _prepBuffer; + private volatile boolean _stop; - public Runner() {} + public Runner() { + _prepBuffer = new NTCPConnection.PrepBuffer(); + } public void stop() { _stop = true; } @@ -119,7 +125,8 @@ public void run() { try { if (_log.shouldLog(Log.DEBUG)) _log.debug("Prepare next write on: " + con); - con.prepareNextWrite(); + _prepBuffer.init(); + con.prepareNextWrite(_prepBuffer); } catch (RuntimeException re) { _log.log(Log.CRIT, "Error in the ntcp writer", re); } diff --git a/common/java/router/net/i2p/router/transport/udp/OutboundMessageState.java b/common/java/router/net/i2p/router/transport/udp/OutboundMessageState.java index 98898e2..39746e7 100644 --- a/common/java/router/net/i2p/router/transport/udp/OutboundMessageState.java +++ b/common/java/router/net/i2p/router/transport/udp/OutboundMessageState.java @@ -20,6 +20,7 @@ class OutboundMessageState implements CDPQEntry { private final Log _log; /** may be null if we are part of the establishment */ private final OutNetMessage _message; + private final I2NPMessage _i2npMessage; private final long _messageId; /** will be null, unless we are part of the establishment */ private final PeerState _peer; @@ -28,7 +29,7 @@ class OutboundMessageState implements CDPQEntry { /** fixed fragment size across the message */ private int _fragmentSize; /** size of the I2NP message */ - private final int _totalSize; + private int _totalSize; /** sends[i] is how many times the fragment has been sent, or -1 if ACKed */ private short _fragmentSends[]; private final long _startedOn; @@ -89,13 +90,9 @@ private OutboundMessageState(I2PAppContext context, OutNetMessage m, I2NPMessage _context = context; _log = _context.logManager().getLog(OutboundMessageState.class); _message = m; + _i2npMessage = msg; _peer = peer; - int size = msg.getRawMessageSize(); - acquireBuf(size); - _totalSize = msg.toRawByteArray(_messageBuf.getData()); - _messageBuf.setValid(_totalSize); _messageId = msg.getUniqueId(); - _startedOn = _context.clock().now(); _nextSendTime = _startedOn; _expiration = _startedOn + EXPIRATION; @@ -105,6 +102,18 @@ private OutboundMessageState(I2PAppContext context, OutNetMessage m, I2NPMessage // _log.debug("Raw byte array for " + _messageId + ": " + Base64.encode(_messageBuf.getData(), 0, len)); } + /** + * lazily inits the message buffer unless already inited + */ + private synchronized void initBuf() { + if (_messageBuf != null) + return; + final int size = _i2npMessage.getRawMessageSize(); + acquireBuf(size); + _totalSize = _i2npMessage.toRawByteArray(_messageBuf.getData()); + _messageBuf.setValid(_totalSize); + } + /** * @throws IAE if too big * @since 0.9.3 @@ -175,7 +184,7 @@ public boolean isComplete() { return true; } - public int getUnackedSize() { + public synchronized int getUnackedSize() { short fragmentSends[] = _fragmentSends; ByteArray messageBuf = _messageBuf; int rv = 0; @@ -288,6 +297,7 @@ public void push() { public void fragment(int fragmentSize) { if (_fragmentSends != null) throw new IllegalStateException(); + initBuf(); int numFragments = _totalSize / fragmentSize; if (numFragments * fragmentSize < _totalSize) numFragments++; @@ -335,6 +345,11 @@ public int getFragmentCount() { */ public boolean shouldSend(int fragmentNum) { return _fragmentSends[fragmentNum] >= (short)0; } + /** + * This assumes fragment(int size) has been called + * @param fragmentNum the number of the fragment + * @return the size of the fragment specified by the number + */ public int fragmentSize(int fragmentNum) { if (_messageBuf == null) return -1; if (fragmentNum + 1 == _fragmentSends.length) { @@ -351,7 +366,8 @@ public int fragmentSize(int fragmentNum) { /** * Write a part of the the message onto the specified buffer. - * See releaseResources() above for synchhronization information. + * See releaseResources() above for synchronization information. + * This assumes fragment(int size) has been called. * * @param out target to write * @param outOffset into outOffset to begin writing diff --git a/common/java/router/net/i2p/router/transport/udp/PeerState.java b/common/java/router/net/i2p/router/transport/udp/PeerState.java index feb471a..06a440a 100644 --- a/common/java/router/net/i2p/router/transport/udp/PeerState.java +++ b/common/java/router/net/i2p/router/transport/udp/PeerState.java @@ -11,6 +11,7 @@ import java.util.Set; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; import net.i2p.data.Hash; import net.i2p.data.SessionKey; @@ -119,9 +120,9 @@ class PeerState { */ //private boolean _remoteWantsPreviousACKs; /** how many bytes should we send to the peer in a second */ - private volatile int _sendWindowBytes; + private int _sendWindowBytes; /** how many bytes can we send to the peer in the current second */ - private volatile int _sendWindowBytesRemaining; + private int _sendWindowBytesRemaining; private long _lastSendRefill; private int _sendBps; private int _sendBytes; @@ -225,13 +226,13 @@ class PeerState { /** Make sure a 4229 byte TunnelBuildMessage can be sent in one volley with small MTU */ private static final int MIN_CONCURRENT_MSGS = 8; /** how many concurrent outbound messages do we allow throws OutboundMessageFragments to send */ - private volatile int _concurrentMessagesAllowed = MIN_CONCURRENT_MSGS; + private int _concurrentMessagesAllowed = MIN_CONCURRENT_MSGS; /** * how many outbound messages are currently being transmitted. Not thread safe, as we're not strict */ - private volatile int _concurrentMessagesActive = 0; + private int _concurrentMessagesActive; /** how many concurrency rejections have we had in a row */ - private volatile int _consecutiveRejections = 0; + private int _consecutiveRejections; /** is it inbound? **/ private final boolean _isInbound; /** Last time it was made an introducer **/ @@ -436,9 +437,19 @@ public void changePort(int newPort) { //public boolean getRemoteWantsPreviousACKs() { return _remoteWantsPreviousACKs; } /** how many bytes should we send to the peer in a second */ - public int getSendWindowBytes() { return _sendWindowBytes; } + public int getSendWindowBytes() { + synchronized(_outboundMessages) { + return _sendWindowBytes; + } + } + /** how many bytes can we send to the peer in the current second */ - public int getSendWindowBytesRemaining() { return _sendWindowBytesRemaining; } + public int getSendWindowBytesRemaining() { + synchronized(_outboundMessages) { + return _sendWindowBytesRemaining; + } + } + /** what IP is the peer sending and receiving packets on? */ public byte[] getRemoteIP() { return _remoteIP; } @@ -580,20 +591,24 @@ public long getLastSendOrPingTime() { /** return the smoothed send transfer rate */ public int getSendBps() { return _sendBps; } public int getReceiveBps() { return _receiveBps; } + public int incrementConsecutiveFailedSends() { - _concurrentMessagesActive--; - if (_concurrentMessagesActive < 0) - _concurrentMessagesActive = 0; - - //long now = _context.clock().now()/(10*1000); - //if (_lastFailedSendPeriod >= now) { - // // ignore... too fast - //} else { - // _lastFailedSendPeriod = now; - _consecutiveFailedSends++; - //} - return _consecutiveFailedSends; + synchronized(_outboundMessages) { + _concurrentMessagesActive--; + if (_concurrentMessagesActive < 0) + _concurrentMessagesActive = 0; + + //long now = _context.clock().now()/(10*1000); + //if (_lastFailedSendPeriod >= now) { + // // ignore... too fast + //} else { + // _lastFailedSendPeriod = now; + _consecutiveFailedSends++; + //} + return _consecutiveFailedSends; + } } + public long getInactivityTime() { long now = _context.clock().now(); long lastActivity = Math.max(_lastReceiveTime, _lastSendFullyTime); @@ -620,15 +635,17 @@ public long getInactivityTime() { * returning true if the full size can be decremented, false if it * cannot. If it is not decremented, the window size remaining is * not adjusted at all. + * + * Caller should synch */ - public boolean allocateSendingBytes(int size, int messagePushCount) { return allocateSendingBytes(size, false, messagePushCount); } + private boolean allocateSendingBytes(int size, int messagePushCount) { return allocateSendingBytes(size, false, messagePushCount); } - public boolean allocateSendingBytes(int size, boolean isForACK) { return allocateSendingBytes(size, isForACK, -1); } + //private boolean allocateSendingBytes(int size, boolean isForACK) { return allocateSendingBytes(size, isForACK, -1); } /** * Caller should synch */ - public boolean allocateSendingBytes(int size, boolean isForACK, int messagePushCount) { + private boolean allocateSendingBytes(int size, boolean isForACK, int messagePushCount) { long now = _context.clock().now(); long duration = now - _lastSendRefill; if (duration >= 1000) { @@ -694,9 +711,25 @@ public void setMTU(int mtu) { ****/ public int getSlowStartThreshold() { return _slowStartThreshold; } - public int getConcurrentSends() { return _concurrentMessagesActive; } - public int getConcurrentSendWindow() { return _concurrentMessagesAllowed; } - public int getConsecutiveSendRejections() { return _consecutiveRejections; } + + public int getConcurrentSends() { + synchronized(_outboundMessages) { + return _concurrentMessagesActive; + } + } + + public int getConcurrentSendWindow() { + synchronized(_outboundMessages) { + return _concurrentMessagesAllowed; + } + } + + public int getConsecutiveSendRejections() { + synchronized(_outboundMessages) { + return _consecutiveRejections; + } + } + public boolean isInbound() { return _isInbound; } /** @since IPv6 */ @@ -1674,6 +1707,8 @@ private enum ShouldSend { YES, NO, NO_BW }; /** * Have 3 return values, because if allocateSendingBytes() returns false, * then allocateSend() can stop iterating + * + * Caller should synch */ private ShouldSend locked_shouldSend(OutboundMessageState state) { long now = _context.clock().now(); diff --git a/common/java/router/net/i2p/router/transport/udp/UDPTransport.java b/common/java/router/net/i2p/router/transport/udp/UDPTransport.java index 062e756..1be488e 100644 --- a/common/java/router/net/i2p/router/transport/udp/UDPTransport.java +++ b/common/java/router/net/i2p/router/transport/udp/UDPTransport.java @@ -2114,8 +2114,7 @@ public int countPeers() { public int countActivePeers() { long now = _context.clock().now(); int active = 0; - for (Iterator iter = _peersByIdent.values().iterator(); iter.hasNext(); ) { - PeerState peer = iter.next(); + for (PeerState peer : _peersByIdent.values()) { if (now-peer.getLastReceiveTime() <= 5*60*1000) active++; } @@ -2126,8 +2125,7 @@ public int countActivePeers() { public int countActiveSendPeers() { long now = _context.clock().now(); int active = 0; - for (Iterator iter = _peersByIdent.values().iterator(); iter.hasNext(); ) { - PeerState peer = iter.next(); + for (PeerState peer : _peersByIdent.values()) { if (now-peer.getLastSendFullyTime() <= 1*60*1000) active++; } @@ -2848,7 +2846,7 @@ public ExpirePeerEvent() { public void timeReached() { // Increase allowed idle time if we are well under allowed connections, otherwise decrease - if (haveCapacity(60)) { + if (haveCapacity(45)) { long inc; // don't adjust too quickly if we are looping fast if (_lastLoopShort) diff --git a/common/java/router/net/i2p/router/tunnel/InboundMessageDistributor.java b/common/java/router/net/i2p/router/tunnel/InboundMessageDistributor.java index 6789dbb..062d5f1 100644 --- a/common/java/router/net/i2p/router/tunnel/InboundMessageDistributor.java +++ b/common/java/router/net/i2p/router/tunnel/InboundMessageDistributor.java @@ -2,6 +2,7 @@ import net.i2p.data.DatabaseEntry; import net.i2p.data.Hash; +import net.i2p.data.LeaseSet; import net.i2p.data.Payload; import net.i2p.data.TunnelId; import net.i2p.data.i2np.DataMessage; @@ -58,38 +59,48 @@ public void distribute(I2NPMessage msg, Hash target, TunnelId tunnel) { */ int type = msg.getType(); - // FVSJ could also result in a DSRM. + // FVSJ or client lookups could also result in a DSRM. // Since there's some code that replies directly to this to gather new ff RouterInfos, // sanitize it if ( (_client != null) && - (type == DatabaseSearchReplyMessage.MESSAGE_TYPE) && - (_client.equals(((DatabaseSearchReplyMessage)msg).getSearchKey()))) { + (type == DatabaseSearchReplyMessage.MESSAGE_TYPE)) { + // TODO: Strip in IterativeLookupJob etc. instead, depending on + // LS or RI and client or expl., so that we can safely follow references + // in a reply to a LS lookup over client tunnels. + // ILJ would also have to follow references via client tunnels DatabaseSearchReplyMessage orig = (DatabaseSearchReplyMessage) msg; if (orig.getNumReplies() > 0) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Removing replies from a DSRM down a tunnel for " + _client + ": " + msg); + if (_log.shouldLog(Log.INFO)) + _log.info("Removing replies from a DSRM down a tunnel for " + _client + ": " + msg); DatabaseSearchReplyMessage newMsg = new DatabaseSearchReplyMessage(_context); newMsg.setFromHash(orig.getFromHash()); newMsg.setSearchKey(orig.getSearchKey()); msg = newMsg; } } else if ( (_client != null) && - (type == DatabaseStoreMessage.MESSAGE_TYPE) && - (((DatabaseStoreMessage)msg).getEntry().getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO)) { - // FVSJ may result in an unsolicited RI store if the peer went non-ff. - // Maybe we can figure out a way to handle this safely, so we don't ask him again. - // For now, just hope we eventually find out through other means. - // Todo: if peer was ff and RI is not ff, queue for exploration in netdb (but that isn't part of the facade now) - if (_log.shouldLog(Log.WARN)) - _log.warn("Dropping DSM down a tunnel for " + _client + ": " + msg); - return; + (type == DatabaseStoreMessage.MESSAGE_TYPE)) { + DatabaseStoreMessage dsm = (DatabaseStoreMessage) msg; + if (dsm.getEntry().getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) { + // FVSJ may result in an unsolicited RI store if the peer went non-ff. + // Maybe we can figure out a way to handle this safely, so we don't ask him again. + // For now, just hope we eventually find out through other means. + // Todo: if peer was ff and RI is not ff, queue for exploration in netdb (but that isn't part of the facade now) + if (_log.shouldLog(Log.WARN)) + _log.warn("Dropping DSM down a tunnel for " + _client + ": " + msg); + return; + } else if (dsm.getReplyToken() != 0) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Dropping LS DSM w/ reply token down a tunnel for " + _client + ": " + msg); + return; + } else { + // allow DSM of our own key (used by FloodfillVerifyStoreJob) + // or other keys (used by IterativeSearchJob) + // as long as there's no reply token (we will never set a reply token but an attacker might) + ((LeaseSet)dsm.getEntry()).setReceivedAsReply(); + } } else if ( (_client != null) && (type != DeliveryStatusMessage.MESSAGE_TYPE) && (type != GarlicMessage.MESSAGE_TYPE) && - // allow DSM of our own key (used by FloodfillVerifyStoreJob) - // as long as there's no reply token (FVSJ will never set a reply token but an attacker might) - ((type != DatabaseStoreMessage.MESSAGE_TYPE) || (!_client.equals(((DatabaseStoreMessage)msg).getKey())) || - (((DatabaseStoreMessage)msg).getReplyToken() != 0)) && (type != TunnelBuildReplyMessage.MESSAGE_TYPE) && (type != VariableTunnelBuildReplyMessage.MESSAGE_TYPE)) { // drop it, since we should only get tunnel test messages and garlic messages down @@ -188,6 +199,7 @@ public void handleClove(DeliveryInstructions instructions, I2NPMessage data) { // Or, it's a normal LS bundled with data and a MessageStatusMessage. // ... and inject it. + ((LeaseSet)dsm.getEntry()).setReceivedAsReply(); if (_log.shouldLog(Log.INFO)) _log.info("Storing garlic LS down tunnel for: " + dsm.getKey() + " sent to: " + _client); _context.inNetMessagePool().add(dsm, null, null); @@ -213,13 +225,16 @@ public void handleClove(DeliveryInstructions instructions, I2NPMessage data) { _log.info("Storing garlic RI down tunnel for: " + dsm.getKey() + " sent to: " + _client); _context.inNetMessagePool().add(dsm, null, null); } - } else if (_client != null && type == DatabaseSearchReplyMessage.MESSAGE_TYPE && - _client.equals(((DatabaseSearchReplyMessage) data).getSearchKey())) { + } else if (_client != null && type == DatabaseSearchReplyMessage.MESSAGE_TYPE) { // DSRMs show up here now that replies are encrypted + // TODO: Strip in IterativeLookupJob etc. instead, depending on + // LS or RI and client or expl., so that we can safely follow references + // in a reply to a LS lookup over client tunnels. + // ILJ would also have to follow references via client tunnels DatabaseSearchReplyMessage orig = (DatabaseSearchReplyMessage) data; if (orig.getNumReplies() > 0) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Removing replies from a garlic DSRM down a tunnel for " + _client + ": " + data); + if (_log.shouldLog(Log.INFO)) + _log.info("Removing replies from a garlic DSRM down a tunnel for " + _client + ": " + data); DatabaseSearchReplyMessage newMsg = new DatabaseSearchReplyMessage(_context); newMsg.setFromHash(orig.getFromHash()); newMsg.setSearchKey(orig.getSearchKey()); diff --git a/common/java/router/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java b/common/java/router/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java index 2564ce3..e0581c8 100644 --- a/common/java/router/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java +++ b/common/java/router/net/i2p/router/tunnel/pool/ExploratoryPeerSelector.java @@ -24,7 +24,7 @@ public ExploratoryPeerSelector(RouterContext context) { super(context); } - public List selectPeers(TunnelPoolSettings settings) { + public List selectPeers(TunnelPoolSettings settings) { Log l = ctx.logManager().getLog(getClass()); int length = getLength(settings); if (length < 0) { @@ -34,7 +34,7 @@ public List selectPeers(TunnelPoolSettings settings) { } if (false && shouldSelectExplicit(settings)) { - List rv = selectExplicit(settings, length); + List rv = selectExplicit(settings, length); if (l.shouldLog(Log.DEBUG)) l.debug("Explicit peers selected: " + rv); return rv; diff --git a/common/java/router/net/i2p/router/tunnel/pool/TunnelPool.java b/common/java/router/net/i2p/router/tunnel/pool/TunnelPool.java index 92e19db..38a7e12 100644 --- a/common/java/router/net/i2p/router/tunnel/pool/TunnelPool.java +++ b/common/java/router/net/i2p/router/tunnel/pool/TunnelPool.java @@ -6,7 +6,7 @@ import java.util.Date; import java.util.Iterator; import java.util.List; -import java.util.Map; +import java.util.Properties; import java.util.TreeSet; import net.i2p.data.Hash; @@ -123,7 +123,8 @@ void refreshSettings() { return; // don't override client specified settings } else { if (_settings.isExploratory()) { - Map props = _context.router().getConfigMap(); + Properties props = new Properties(); + props.putAll(_context.router().getConfigMap()); if (_settings.isInbound()) _settings.readFromProperties(TunnelPoolSettings.PREFIX_INBOUND_EXPLORATORY, props); else diff --git a/common/java/router/net/i2p/router/util/EventLog.java b/common/java/router/net/i2p/router/util/EventLog.java index 1f07f7f..1e6f166 100644 --- a/common/java/router/net/i2p/router/util/EventLog.java +++ b/common/java/router/net/i2p/router/util/EventLog.java @@ -3,7 +3,6 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; -import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStream; @@ -116,10 +115,10 @@ public synchronized SortedMap getEvents(String event, long since) } } rv = new TreeMap(); - InputStream in = null; + BufferedReader br = null; try { - in = new FileInputStream(_file); - BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + br = new BufferedReader(new InputStreamReader( + new FileInputStream(_file), "UTF-8")); String line = null; while ( (line = br.readLine()) != null) { try { @@ -141,7 +140,7 @@ public synchronized SortedMap getEvents(String event, long since) _cacheTime.put(event, Long.valueOf(since)); } catch (IOException ioe) { } finally { - if (in != null) try { in.close(); } catch (IOException ioe) {} + if (br != null) try { br.close(); } catch (IOException ioe) {} } return rv; } diff --git a/common/java/router/net/i2p/router/util/PriBlockingQueue.java b/common/java/router/net/i2p/router/util/PriBlockingQueue.java index 0aae2d3..baf6981 100644 --- a/common/java/router/net/i2p/router/util/PriBlockingQueue.java +++ b/common/java/router/net/i2p/router/util/PriBlockingQueue.java @@ -34,7 +34,7 @@ public class PriBlockingQueue extends PriorityBlockingQueue()); _context = ctx; _log = ctx.logManager().getLog(PriorityBlockingQueue.class); _name = name;