2005-09-09 jrandom
* Added preliminary support for NAT hole punching through SSU introducers * Honor peer test results from peers that we have an SSU session with if those sessions are idle for 3 minutes or more.
This commit is contained in:
@ -1,7 +1,12 @@
|
||||
$Id: history.txt,v 1.241 2005/09/07 17:32:06 jrandom Exp $
|
||||
$Id: history.txt,v 1.242 2005/09/09 20:13:50 cervantes Exp $
|
||||
|
||||
2005-09-09 jrandom
|
||||
* Added preliminary support for NAT hole punching through SSU introducers
|
||||
* Honor peer test results from peers that we have an SSU session with if
|
||||
those sessions are idle for 3 minutes or more.
|
||||
|
||||
2005-09-09 cervantes
|
||||
* New build due to change in build number :P (thanks ugha!)
|
||||
* New build due to change in build number :P (thanks ugha!)
|
||||
|
||||
2005-09-07 BarkerJr
|
||||
* HTML cleanup for the router console (thanks!)
|
||||
|
@ -1,4 +1,4 @@
|
||||
<code>$Id: udp.html,v 1.15 2005/08/03 13:58:13 jrandom Exp $</code>
|
||||
<code>$Id: udp.html,v 1.16 2005/08/17 15:05:02 jrandom Exp $</code>
|
||||
|
||||
<h1>Secure Semireliable UDP (SSU)</h1>
|
||||
<b>DRAFT</b>
|
||||
@ -251,12 +251,12 @@ bits 4-7: total identity fragments</pre></li>
|
||||
<td><ul>
|
||||
<li>4 byte relay tag</li>
|
||||
<li>1 byte IP address size</li>
|
||||
<li>that many byte representation of Bob's IP address</li>
|
||||
<li>1 byte IP address size</li>
|
||||
<li>that many byte representation of Alice's IP address</li>
|
||||
<li>2 byte port number (of Alice)</li>
|
||||
<li>1 byte challenge size</li>
|
||||
<li>that many bytes to be relayed to Charlie in the intro</li>
|
||||
<li>Alice's intro key (so Bob can reply with Charlie's info)</li>
|
||||
<li>4 byte nonce of alice's relay request</li>
|
||||
<li>N bytes, currently uninterpreted</li>
|
||||
</ul></td></tr>
|
||||
<tr><td align="right" valign="top"><b>Key used:</b></td>
|
||||
@ -267,14 +267,19 @@ bits 4-7: total identity fragments</pre></li>
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| relay tag |size| that many |
|
||||
+----+----+----+----+----+ +----|
|
||||
| bytes making up Bob's IP address |size|
|
||||
| bytes for Alice's IP address |port
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| that many bytes making up Alice's IP |
|
||||
(A) |size| that many challenge bytes |
|
||||
+----+----+ |
|
||||
| to be delivered to Charlie |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| Port (A)|size| that many challenge |
|
||||
+----+----+----+ |
|
||||
| bytes to be delivered to Charlie |
|
||||
| Alice's intro key |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| nonce | |
|
||||
+----+----+----+----+ |
|
||||
| arbitrary amount of uninterpreted data|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
</pre>
|
||||
@ -291,6 +296,7 @@ bits 4-7: total identity fragments</pre></li>
|
||||
<li>1 byte IP address size</li>
|
||||
<li>that many byte representation of Alice's IP address</li>
|
||||
<li>2 byte port number</li>
|
||||
<li>4 byte nonce sent by Alice</li>
|
||||
<li>N bytes, currently uninterpreted</li>
|
||||
</ul></td></tr>
|
||||
<tr><td align="right" valign="top"><b>Key used:</b></td>
|
||||
@ -307,6 +313,8 @@ bits 4-7: total identity fragments</pre></li>
|
||||
+----+ +----+----+
|
||||
| Alice's IP address | Port (A)|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| nonce | |
|
||||
+----+----+----+----+ |
|
||||
| arbitrary amount of uninterpreted data|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
</pre>
|
||||
@ -332,7 +340,7 @@ bits 4-7: total identity fragments</pre></li>
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|size| that many bytes making up |
|
||||
+----+ +----+----+
|
||||
| Charlie's IP address | Port (C)|
|
||||
| Alice's IP address | Port (A)|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|size| that many bytes of challenge |
|
||||
+----+ |
|
||||
@ -563,6 +571,33 @@ are not in any particular order - in fact, they are likely to be
|
||||
entirely random. The SSU layer makes no attempt at messageId
|
||||
replay prevention - higher layers should take that into account.</p>
|
||||
|
||||
<h2><a name="introduction">Introduction</a></h2>
|
||||
|
||||
<p>Indirect session establishment by means of a third party introduction
|
||||
is necessary for efficient NAT traversal. Charlie, a router behind a
|
||||
NAT or firewall which does not allow unsolicited inbound UDP packets,
|
||||
first contacts a few peers, choosing some to serve as introducers. Each
|
||||
of these peers (Bob, Bill, Betty, etc) provide Charlie with an introduction
|
||||
tag - a 4 byte random number - which he then makes available to the public
|
||||
as methods of contacting him. Alice, a router who has Charlie's published
|
||||
contact methods, first sends a RelayRequest packet to one or more of the
|
||||
introducers, asking each to introduce her to Charlie (offering the
|
||||
introduction tag to identify Charlie). Bob then forwards a RelayIntro
|
||||
packet to Charlie including Alice's public IP and port number, then sends
|
||||
Alice back a RelayResponse packet containing Charlie's public IP and port
|
||||
number. When Charlie receives the RelayIntro packet, he sends off a small
|
||||
random packet to Alice's IP and port (poking a hole in his NAT/firewall),
|
||||
and when Alice receive's Bob's RelayResponse packet, she begins a new
|
||||
full direction session establishment with the specified IP and port.</p>
|
||||
|
||||
<!--
|
||||
should Bob wait for Charlie to ack the RelayIntro packet to avoid
|
||||
situations where that packet is lost yet Alice gets Charlie's IP with
|
||||
Charlie not yet punching a hole in his NAT for her to get through?
|
||||
Perhaps Alice should send to multiple Bobs at once, hoping that at
|
||||
least one of them gets through
|
||||
-->
|
||||
|
||||
<h2><a name="peerTesting">Peer testing</a></h2>
|
||||
|
||||
<p>The automation of collaborative reachability testing for peers is
|
||||
|
@ -15,9 +15,9 @@ import net.i2p.CoreVersion;
|
||||
*
|
||||
*/
|
||||
public class RouterVersion {
|
||||
public final static String ID = "$Revision: 1.228 $ $Date: 2005/09/07 17:31:13 $";
|
||||
public final static String ID = "$Revision: 1.229 $ $Date: 2005/09/09 20:13:49 $";
|
||||
public final static String VERSION = "0.6.0.5";
|
||||
public final static long BUILD = 3;
|
||||
public final static long BUILD = 4;
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
|
||||
System.out.println("Router ID: " + RouterVersion.ID);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -8,15 +9,18 @@ import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.crypto.DHSessionKeyBuilder;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
* Coordinate the establishment of new sessions - both inbound and outbound.
|
||||
@ -35,6 +39,8 @@ public class EstablishmentManager {
|
||||
private Map _outboundStates;
|
||||
/** map of RemoteHostId to List of OutNetMessage for messages exceeding capacity */
|
||||
private Map _queuedOutbound;
|
||||
/** map of nonce (Long) to OutboundEstablishState */
|
||||
private Map _liveIntroductions;
|
||||
private boolean _alive;
|
||||
private Object _activityLock;
|
||||
private int _activity;
|
||||
@ -50,11 +56,15 @@ public class EstablishmentManager {
|
||||
_inboundStates = new HashMap(32);
|
||||
_outboundStates = new HashMap(32);
|
||||
_queuedOutbound = new HashMap(32);
|
||||
_liveIntroductions = new HashMap(32);
|
||||
_activityLock = new Object();
|
||||
_context.statManager().createRateStat("udp.inboundEstablishTime", "How long it takes for a new inbound session to be established", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.outboundEstablishTime", "How long it takes for a new outbound session to be established", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.inboundEstablishFailedState", "What state a failed inbound establishment request fails in", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.outboundEstablishFailedState", "What state a failed outbound establishment request fails in", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.sendIntroRelayRequest", "How often we send a relay request to reach a peer", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.sendIntroRelayTimeout", "How often a relay request times out before getting a response (due to the target or intro peer being offline)", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.receiveIntroRelayResponse", "How long it took to receive a relay response", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
@ -134,7 +144,7 @@ public class EstablishmentManager {
|
||||
} else {
|
||||
state = new OutboundEstablishState(_context, remAddr, port,
|
||||
msg.getTarget().getIdentity(),
|
||||
new SessionKey(addr.getIntroKey()));
|
||||
new SessionKey(addr.getIntroKey()), addr);
|
||||
_outboundStates.put(to, state);
|
||||
}
|
||||
}
|
||||
@ -155,15 +165,28 @@ public class EstablishmentManager {
|
||||
*
|
||||
*/
|
||||
void receiveSessionRequest(RemoteHostId from, UDPPacketReader reader) {
|
||||
boolean isNew = false;
|
||||
InboundEstablishState state = null;
|
||||
synchronized (_inboundStates) {
|
||||
state = (InboundEstablishState)_inboundStates.get(from);
|
||||
if (state == null) {
|
||||
state = new InboundEstablishState(_context, from.getIP(), from.getPort(), _transport.getLocalPort());
|
||||
isNew = true;
|
||||
_inboundStates.put(from, state);
|
||||
}
|
||||
}
|
||||
state.receiveSessionRequest(reader.getSessionRequestReader());
|
||||
if (isNew) {
|
||||
if (!_transport.introducersRequired()) {
|
||||
long tag = _context.random().nextLong(MAX_TAG_VALUE);
|
||||
state.setSentRelayTag(tag);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received session request from " + from + ", sending relay tag " + tag);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received session request, but our status is " + _transport.getReachabilityStatus());
|
||||
}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive session request from: " + state.getRemoteHostId().toString());
|
||||
@ -265,7 +288,7 @@ public class EstablishmentManager {
|
||||
|
||||
OutboundEstablishState qstate = new OutboundEstablishState(_context, remAddr, port,
|
||||
msg.getTarget().getIdentity(),
|
||||
new SessionKey(addr.getIntroKey()));
|
||||
new SessionKey(addr.getIntroKey()), addr);
|
||||
_outboundStates.put(to, qstate);
|
||||
|
||||
for (int i = 0; i < queued.size(); i++)
|
||||
@ -304,10 +327,10 @@ public class EstablishmentManager {
|
||||
peer.setLastSendTime(now);
|
||||
peer.setRemoteAddress(state.getSentIP(), state.getSentPort());
|
||||
peer.setRemotePeer(remote.calculateHash());
|
||||
if (true) // for now, only support direct
|
||||
peer.setRemoteRequiresIntroduction(false);
|
||||
peer.setTheyRelayToUsAs(0);
|
||||
peer.setWeRelayToThemAs(state.getSentRelayTag());
|
||||
peer.setTheyRelayToUsAs(0);
|
||||
//if (true) // for now, only support direct
|
||||
// peer.setRemoteRequiresIntroduction(false);
|
||||
|
||||
_transport.addRemotePeerState(peer);
|
||||
|
||||
@ -334,8 +357,6 @@ public class EstablishmentManager {
|
||||
peer.setLastSendTime(now);
|
||||
peer.setRemoteAddress(state.getSentIP(), state.getSentPort());
|
||||
peer.setRemotePeer(remote.calculateHash());
|
||||
if (true) // for now, only support direct
|
||||
peer.setRemoteRequiresIntroduction(false);
|
||||
peer.setTheyRelayToUsAs(state.getReceivedRelayTag());
|
||||
peer.setWeRelayToThemAs(0);
|
||||
|
||||
@ -364,10 +385,19 @@ public class EstablishmentManager {
|
||||
_transport.send(m, peer);
|
||||
}
|
||||
|
||||
public static final long MAX_TAG_VALUE = 0xFFFFFFFFl;
|
||||
|
||||
private void sendCreated(InboundEstablishState state) {
|
||||
long now = _context.clock().now();
|
||||
if (true) // for now, don't offer to relay
|
||||
if (!_transport.introducersRequired()) {
|
||||
// offer to relay
|
||||
// (perhaps we should check our bw usage and/or how many peers we are
|
||||
// already offering introducing?)
|
||||
state.setSentRelayTag(_context.random().nextLong(MAX_TAG_VALUE));
|
||||
} else {
|
||||
// don't offer to relay
|
||||
state.setSentRelayTag(0);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send created to: " + state.getRemoteHostId().toString());
|
||||
@ -390,13 +420,89 @@ public class EstablishmentManager {
|
||||
|
||||
private void sendRequest(OutboundEstablishState state) {
|
||||
long now = _context.clock().now();
|
||||
state.prepareSessionRequest();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send request to: " + state.getRemoteHostId().toString());
|
||||
_transport.send(_builder.buildSessionRequestPacket(state));
|
||||
state.requestSent();
|
||||
}
|
||||
|
||||
private static final long MAX_NONCE = 0xFFFFFFFFl;
|
||||
/** if we don't get a relayResponse in 3 seconds, try again with another intro peer */
|
||||
private static final int INTRO_ATTEMPT_TIMEOUT = 3*1000;
|
||||
|
||||
private void handlePendingIntro(OutboundEstablishState state) {
|
||||
long nonce = _context.random().nextLong(MAX_NONCE);
|
||||
while (true) {
|
||||
synchronized (_liveIntroductions) {
|
||||
OutboundEstablishState old = (OutboundEstablishState)_liveIntroductions.put(new Long(nonce), state);
|
||||
if (old != null) {
|
||||
nonce = _context.random().nextLong(MAX_NONCE);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SimpleTimer.getInstance().addEvent(new FailIntroduction(state, nonce), INTRO_ATTEMPT_TIMEOUT);
|
||||
state.setIntroNonce(nonce);
|
||||
_context.statManager().addRateData("udp.sendIntroRelayRequest", 1, 0);
|
||||
_transport.send(_builder.buildRelayRequest(state, _transport.getIntroKey()));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send intro for " + state.getRemoteHostId().toString() + " with our intro key as " + _transport.getIntroKey().toBase64());
|
||||
state.introSent();
|
||||
}
|
||||
private class FailIntroduction implements SimpleTimer.TimedEvent {
|
||||
private long _nonce;
|
||||
private OutboundEstablishState _state;
|
||||
public FailIntroduction(OutboundEstablishState state, long nonce) {
|
||||
_nonce = nonce;
|
||||
_state = state;
|
||||
}
|
||||
public void timeReached() {
|
||||
OutboundEstablishState removed = null;
|
||||
synchronized (_liveIntroductions) {
|
||||
removed = (OutboundEstablishState)_liveIntroductions.remove(new Long(_nonce));
|
||||
if (removed != _state) {
|
||||
// another one with the same nonce in a very brief time...
|
||||
_liveIntroductions.put(new Long(_nonce), removed);
|
||||
removed = null;
|
||||
}
|
||||
}
|
||||
if (removed != null) {
|
||||
_context.statManager().addRateData("udp.sendIntroRelayTimeout", 1, 0);
|
||||
notifyActivity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveRelayResponse(RemoteHostId bob, UDPPacketReader reader) {
|
||||
long nonce = reader.getRelayResponseReader().readNonce();
|
||||
OutboundEstablishState state = null;
|
||||
synchronized (_liveIntroductions) {
|
||||
state = (OutboundEstablishState)_liveIntroductions.remove(new Long(nonce));
|
||||
}
|
||||
if (state == null)
|
||||
return; // already established
|
||||
|
||||
int sz = reader.getRelayResponseReader().readCharlieIPSize();
|
||||
byte ip[] = new byte[sz];
|
||||
reader.getRelayResponseReader().readCharlieIP(ip, 0);
|
||||
InetAddress addr = null;
|
||||
try {
|
||||
addr = InetAddress.getByAddress(ip);
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Introducer for " + state + " (" + bob + ") sent us an invalid IP for our targer: " + Base64.encode(ip), uhe);
|
||||
// these two cause this peer to requeue for a new intro peer
|
||||
state.introductionFailed();
|
||||
notifyActivity();
|
||||
return;
|
||||
}
|
||||
_context.statManager().addRateData("udp.receiveIntroRelayResponse", state.getLifetime(), 0);
|
||||
int port = reader.getRelayResponseReader().readCharliePort();
|
||||
state.introduced(addr, ip, port);
|
||||
notifyActivity();
|
||||
}
|
||||
|
||||
private void sendConfirmation(OutboundEstablishState state) {
|
||||
long now = _context.clock().now();
|
||||
boolean valid = state.validateSessionCreated();
|
||||
@ -594,6 +700,9 @@ public class EstablishmentManager {
|
||||
case OutboundEstablishState.STATE_REQUEST_SENT:
|
||||
err = "Took too long to establish remote connection (request sent)";
|
||||
break;
|
||||
case OutboundEstablishState.STATE_PENDING_INTRO:
|
||||
err = "Took too long to establish remote connection (intro failed)";
|
||||
break;
|
||||
case OutboundEstablishState.STATE_UNKNOWN: // fallthrough
|
||||
default:
|
||||
err = "Took too long to establish remote connection (unknown state)";
|
||||
@ -626,6 +735,9 @@ public class EstablishmentManager {
|
||||
case OutboundEstablishState.STATE_CONFIRMED_COMPLETELY:
|
||||
handleCompletelyEstablished(outboundState);
|
||||
break;
|
||||
case OutboundEstablishState.STATE_PENDING_INTRO:
|
||||
handlePendingIntro(outboundState);
|
||||
break;
|
||||
default:
|
||||
// wtf
|
||||
}
|
||||
|
@ -0,0 +1,100 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class IntroductionManager {
|
||||
private RouterContext _context;
|
||||
private Log _log;
|
||||
private UDPTransport _transport;
|
||||
private PacketBuilder _builder;
|
||||
/** map of relay tag to PeerState that should receive the introduction */
|
||||
private Map _outbound;
|
||||
/** list of peers (PeerState) who have given us introduction tags */
|
||||
private List _inbound;
|
||||
|
||||
public IntroductionManager(RouterContext ctx, UDPTransport transport) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(IntroductionManager.class);
|
||||
_transport = transport;
|
||||
_builder = new PacketBuilder(ctx);
|
||||
_outbound = Collections.synchronizedMap(new HashMap(128));
|
||||
_inbound = new ArrayList(128);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
_inbound.clear();
|
||||
_outbound.clear();
|
||||
}
|
||||
|
||||
public void add(PeerState peer) {
|
||||
if (peer == null) return;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Adding peer " + peer.getRemoteHostId() + ", weRelayToThemAs "
|
||||
+ peer.getWeRelayToThemAs() + ", theyRelayToUsAs " + peer.getTheyRelayToUsAs());
|
||||
if (peer.getWeRelayToThemAs() > 0)
|
||||
_outbound.put(new Long(peer.getWeRelayToThemAs()), peer);
|
||||
if (peer.getTheyRelayToUsAs() > 0) {
|
||||
synchronized (_inbound) {
|
||||
if (!_inbound.contains(peer))
|
||||
_inbound.add(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(PeerState peer) {
|
||||
if (peer == null) return;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("removing peer " + peer.getRemoteHostId() + ", weRelayToThemAs "
|
||||
+ peer.getWeRelayToThemAs() + ", theyRelayToUsAs " + peer.getTheyRelayToUsAs());
|
||||
if (peer.getWeRelayToThemAs() > 0)
|
||||
_outbound.remove(new Long(peer.getWeRelayToThemAs()));
|
||||
if (peer.getTheyRelayToUsAs() > 0) {
|
||||
synchronized (_inbound) {
|
||||
_inbound.remove(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PeerState get(long id) {
|
||||
return (PeerState)_outbound.get(new Long(id));
|
||||
}
|
||||
|
||||
public void pickInbound(List rv, int howMany) {
|
||||
int start = _context.random().nextInt(Integer.MAX_VALUE);
|
||||
synchronized (_inbound) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Picking inbound out of " + _inbound);
|
||||
if (_inbound.size() <= 0) return;
|
||||
start = start % _inbound.size();
|
||||
for (int i = 0; i < _inbound.size() && rv.size() < howMany; i++) {
|
||||
PeerState cur = (PeerState)_inbound.get((start + i) % _inbound.size());
|
||||
rv.add(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveRelayIntro(RemoteHostId bob, UDPPacketReader reader) {
|
||||
_transport.send(_builder.buildHolePunch(reader));
|
||||
}
|
||||
|
||||
public void receiveRelayRequest(RemoteHostId alice, UDPPacketReader reader) {
|
||||
long tag = reader.getRelayRequestReader().readTag();
|
||||
PeerState charlie = _transport.getPeerState(tag);
|
||||
if (charlie == null)
|
||||
return;
|
||||
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
reader.getRelayRequestReader().readAliceIntroKey(key, 0);
|
||||
SessionKey aliceIntroKey = new SessionKey(key);
|
||||
// send that peer an introduction for alice
|
||||
_transport.send(_builder.buildRelayIntro(alice, charlie, reader.getRelayRequestReader()));
|
||||
// send alice back charlie's info
|
||||
_transport.send(_builder.buildRelayResponse(alice, charlie, reader.getRelayRequestReader().readNonce(), aliceIntroKey));
|
||||
}
|
||||
}
|
@ -56,6 +56,9 @@ public class OutboundEstablishState {
|
||||
private SessionKey _introKey;
|
||||
private List _queuedMessages;
|
||||
private int _currentState;
|
||||
private long _introductionNonce;
|
||||
// intro
|
||||
private UDPAddress _remoteAddress;
|
||||
|
||||
/** nothin sent yet */
|
||||
public static final int STATE_UNKNOWN = 0;
|
||||
@ -67,12 +70,14 @@ public class OutboundEstablishState {
|
||||
public static final int STATE_CONFIRMED_PARTIALLY = 3;
|
||||
/** we have received a data packet */
|
||||
public static final int STATE_CONFIRMED_COMPLETELY = 4;
|
||||
/** we need to have someone introduce us to the peer, but haven't received a RelayResponse yet */
|
||||
public static final int STATE_PENDING_INTRO = 5;
|
||||
|
||||
public OutboundEstablishState(RouterContext ctx, InetAddress remoteHost, int remotePort,
|
||||
RouterIdentity remotePeer, SessionKey introKey) {
|
||||
RouterIdentity remotePeer, SessionKey introKey, UDPAddress addr) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(OutboundEstablishState.class);
|
||||
_bobIP = remoteHost.getAddress();
|
||||
_bobIP = (remoteHost != null ? remoteHost.getAddress() : null);
|
||||
_bobPort = remotePort;
|
||||
_remoteHostId = new RemoteHostId(_bobIP, _bobPort);
|
||||
_remotePeer = remotePeer;
|
||||
@ -81,10 +86,22 @@ public class OutboundEstablishState {
|
||||
_queuedMessages = new ArrayList(4);
|
||||
_currentState = STATE_UNKNOWN;
|
||||
_establishBegin = ctx.clock().now();
|
||||
_remoteAddress = addr;
|
||||
_introductionNonce = -1;
|
||||
prepareSessionRequest();
|
||||
if ( (addr != null) && (addr.getIntroducerCount() > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("new outbound establish to " + remotePeer.calculateHash().toBase64() + ", with address: " + addr);
|
||||
_currentState = STATE_PENDING_INTRO;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized int getState() { return _currentState; }
|
||||
|
||||
public UDPAddress getRemoteAddress() { return _remoteAddress; }
|
||||
public void setIntroNonce(long nonce) { _introductionNonce = nonce; }
|
||||
public long getIntroNonce() { return _introductionNonce; }
|
||||
|
||||
public void addMessage(OutNetMessage msg) {
|
||||
synchronized (_queuedMessages) {
|
||||
_queuedMessages.add(msg);
|
||||
@ -101,7 +118,7 @@ public class OutboundEstablishState {
|
||||
public RouterIdentity getRemoteIdentity() { return _remotePeer; }
|
||||
public SessionKey getIntroKey() { return _introKey; }
|
||||
|
||||
public synchronized void prepareSessionRequest() {
|
||||
private void prepareSessionRequest() {
|
||||
_keyBuilder = new DHSessionKeyBuilder();
|
||||
byte X[] = _keyBuilder.getMyPublicValue().toByteArray();
|
||||
if (_sentX == null)
|
||||
@ -342,6 +359,31 @@ public class OutboundEstablishState {
|
||||
if (_currentState == STATE_UNKNOWN)
|
||||
_currentState = STATE_REQUEST_SENT;
|
||||
}
|
||||
public synchronized void introSent() {
|
||||
_lastSend = _context.clock().now();
|
||||
_nextSend = _lastSend + 5*1000;
|
||||
if (_currentState == STATE_UNKNOWN)
|
||||
_currentState = STATE_PENDING_INTRO;
|
||||
}
|
||||
public synchronized void introductionFailed() {
|
||||
_nextSend = _context.clock().now();
|
||||
// keep the state as STATE_PENDING_INTRO, so next time the EstablishmentManager asks us
|
||||
// whats up, it'll try a new random intro peer
|
||||
}
|
||||
|
||||
public synchronized void introduced(InetAddress bob, byte bobIP[], int bobPort) {
|
||||
if (_currentState != STATE_PENDING_INTRO)
|
||||
return; // we've already successfully been introduced, so don't overwrite old settings
|
||||
_nextSend = _context.clock().now() + 500; // wait briefly for the hole punching
|
||||
if (_currentState == STATE_PENDING_INTRO) {
|
||||
// STATE_UNKNOWN will probe the EstablishmentManager to send a new
|
||||
// session request to this newly known address
|
||||
_currentState = STATE_UNKNOWN;
|
||||
}
|
||||
_bobIP = bobIP;
|
||||
_bobPort = bobPort;
|
||||
_remoteHostId = new RemoteHostId(bobIP, bobPort);
|
||||
}
|
||||
|
||||
/** how long have we been trying to establish this session? */
|
||||
public synchronized long getLifetime() { return _context.clock().now() - _establishBegin; }
|
||||
|
@ -628,6 +628,228 @@ public class PacketBuilder {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* full flag info for a relay request message. this can be fixed,
|
||||
* since we never rekey on relay request, and don't need any extended options
|
||||
*/
|
||||
private static final byte PEER_RELAY_REQUEST_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_RELAY_REQUEST << 4);
|
||||
|
||||
// specify these if we know what our external receive ip/port is and if its different
|
||||
// from what bob is going to think
|
||||
private byte[] getOurExplicitIP() { return null; }
|
||||
private int getOurExplicitPort() { return 0; }
|
||||
|
||||
public UDPPacket buildRelayRequest(OutboundEstablishState state, SessionKey ourIntroKey) {
|
||||
UDPAddress addr = state.getRemoteAddress();
|
||||
int index = _context.random().nextInt(UDPAddress.MAX_INTRODUCERS) % addr.getIntroducerCount();
|
||||
InetAddress iaddr = addr.getIntroducerHost(index);
|
||||
int iport = addr.getIntroducerPort(index);
|
||||
byte ikey[] = addr.getIntroducerKey(index);
|
||||
long tag = addr.getIntroducerTag(index);
|
||||
return buildRelayRequest(iaddr, iport, ikey, tag, ourIntroKey, state.getIntroNonce(), true);
|
||||
}
|
||||
|
||||
public UDPPacket buildRelayRequest(InetAddress introHost, int introPort, byte introKey[], long introTag, SessionKey ourIntroKey, long introNonce, boolean encrypt) {
|
||||
UDPPacket packet = UDPPacket.acquire(_context);
|
||||
byte data[] = packet.getPacket().getData();
|
||||
Arrays.fill(data, 0, data.length, (byte)0x0);
|
||||
int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
|
||||
|
||||
byte ourIP[] = getOurExplicitIP();
|
||||
int ourPort = getOurExplicitPort();
|
||||
|
||||
// header
|
||||
data[off] = PEER_RELAY_REQUEST_FLAG_BYTE;
|
||||
off++;
|
||||
long now = _context.clock().now() / 1000;
|
||||
DataHelper.toLong(data, off, 4, now);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending intro relay request to " + introHost + ":" + introPort); // + " regarding " + state.getRemoteIdentity().calculateHash().toBase64());
|
||||
off += 4;
|
||||
|
||||
// now for the body
|
||||
DataHelper.toLong(data, off, 4, introTag);
|
||||
off += 4;
|
||||
if (ourIP != null) {
|
||||
DataHelper.toLong(data, off, 1, ourIP.length);
|
||||
off++;
|
||||
System.arraycopy(ourIP, 0, data, off, ourIP.length);
|
||||
off += ourIP.length;
|
||||
} else {
|
||||
DataHelper.toLong(data, off, 1, 0);
|
||||
off++;
|
||||
}
|
||||
|
||||
DataHelper.toLong(data, off, 2, ourPort);
|
||||
off += 2;
|
||||
|
||||
// challenge...
|
||||
DataHelper.toLong(data, off, 1, 0);
|
||||
off++;
|
||||
off += 0; // *cough*
|
||||
|
||||
System.arraycopy(ourIntroKey.getData(), 0, data, off, SessionKey.KEYSIZE_BYTES);
|
||||
off += SessionKey.KEYSIZE_BYTES;
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("wrote alice intro key: " + Base64.encode(data, off-SessionKey.KEYSIZE_BYTES, SessionKey.KEYSIZE_BYTES)
|
||||
+ " with nonce " + introNonce + " size=" + (off+4 + (16 - (off+4)%16))
|
||||
+ " and data: " + Base64.encode(data, 0, off));
|
||||
|
||||
DataHelper.toLong(data, off, 4, introNonce);
|
||||
off += 4;
|
||||
|
||||
// we can pad here if we want, maybe randomized?
|
||||
|
||||
// pad up so we're on the encryption boundary
|
||||
if ( (off % 16) != 0)
|
||||
off += 16 - (off % 16);
|
||||
packet.getPacket().setLength(off);
|
||||
if (encrypt)
|
||||
authenticate(packet, new SessionKey(introKey), new SessionKey(introKey));
|
||||
setTo(packet, introHost, introPort);
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* full flag info for a relay intro message. this can be fixed,
|
||||
* since we never rekey on relay request, and don't need any extended options
|
||||
*/
|
||||
private static final byte PEER_RELAY_INTRO_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_RELAY_INTRO << 4);
|
||||
|
||||
public UDPPacket buildRelayIntro(RemoteHostId alice, PeerState charlie, UDPPacketReader.RelayRequestReader request) {
|
||||
UDPPacket packet = UDPPacket.acquire(_context);
|
||||
byte data[] = packet.getPacket().getData();
|
||||
Arrays.fill(data, 0, data.length, (byte)0x0);
|
||||
int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
|
||||
|
||||
// header
|
||||
data[off] = PEER_RELAY_INTRO_FLAG_BYTE;
|
||||
off++;
|
||||
long now = _context.clock().now() / 1000;
|
||||
DataHelper.toLong(data, off, 4, now);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending intro to " + charlie + " for " + alice);
|
||||
off += 4;
|
||||
|
||||
// now for the body
|
||||
byte ip[] = alice.getIP();
|
||||
DataHelper.toLong(data, off, 1, ip.length);
|
||||
off++;
|
||||
System.arraycopy(ip, 0, data, off, ip.length);
|
||||
off += ip.length;
|
||||
DataHelper.toLong(data, off, 2, alice.getPort());
|
||||
off += 2;
|
||||
|
||||
int sz = request.readChallengeSize();
|
||||
DataHelper.toLong(data, off, 1, sz);
|
||||
off++;
|
||||
if (sz > 0) {
|
||||
request.readChallengeSize(data, off);
|
||||
off += sz;
|
||||
}
|
||||
|
||||
// we can pad here if we want, maybe randomized?
|
||||
|
||||
// pad up so we're on the encryption boundary
|
||||
if ( (off % 16) != 0)
|
||||
off += 16 - (off % 16);
|
||||
packet.getPacket().setLength(off);
|
||||
authenticate(packet, charlie.getCurrentCipherKey(), charlie.getCurrentMACKey());
|
||||
setTo(packet, charlie.getRemoteIPAddress(), charlie.getRemotePort());
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* full flag info for a relay response message. this can be fixed,
|
||||
* since we never rekey on relay response, and don't need any extended options
|
||||
*/
|
||||
private static final byte PEER_RELAY_RESPONSE_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_RELAY_RESPONSE << 4);
|
||||
|
||||
public UDPPacket buildRelayResponse(RemoteHostId alice, PeerState charlie, long nonce, SessionKey aliceIntroKey) {
|
||||
InetAddress aliceAddr = null;
|
||||
try {
|
||||
aliceAddr = InetAddress.getByAddress(alice.getIP());
|
||||
} catch (UnknownHostException uhe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
UDPPacket packet = UDPPacket.acquire(_context);
|
||||
byte data[] = packet.getPacket().getData();
|
||||
Arrays.fill(data, 0, data.length, (byte)0x0);
|
||||
int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
|
||||
|
||||
// header
|
||||
data[off] = PEER_RELAY_RESPONSE_FLAG_BYTE;
|
||||
off++;
|
||||
long now = _context.clock().now() / 1000;
|
||||
DataHelper.toLong(data, off, 4, now);
|
||||
off += 4;
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending relay response to " + alice + " for " + charlie + " with alice's intro key " + aliceIntroKey.toBase64());
|
||||
|
||||
// now for the body
|
||||
byte charlieIP[] = charlie.getRemoteIP();
|
||||
DataHelper.toLong(data, off, 1, charlieIP.length);
|
||||
off++;
|
||||
System.arraycopy(charlieIP, 0, data, off, charlieIP.length);
|
||||
off += charlieIP.length;
|
||||
DataHelper.toLong(data, off, 2, charlie.getRemotePort());
|
||||
off += 2;
|
||||
|
||||
byte aliceIP[] = alice.getIP();
|
||||
DataHelper.toLong(data, off, 1, aliceIP.length);
|
||||
off++;
|
||||
System.arraycopy(aliceIP, 0, data, off, aliceIP.length);
|
||||
off += aliceIP.length;
|
||||
DataHelper.toLong(data, off, 2, alice.getPort());
|
||||
off += 2;
|
||||
|
||||
DataHelper.toLong(data, off, 4, nonce);
|
||||
off += 4;
|
||||
|
||||
// we can pad here if we want, maybe randomized?
|
||||
|
||||
// pad up so we're on the encryption boundary
|
||||
if ( (off % 16) != 0)
|
||||
off += 16 - (off % 16);
|
||||
packet.getPacket().setLength(off);
|
||||
authenticate(packet, aliceIntroKey, aliceIntroKey);
|
||||
setTo(packet, aliceAddr, alice.getPort());
|
||||
return packet;
|
||||
}
|
||||
|
||||
public UDPPacket buildHolePunch(UDPPacketReader reader) {
|
||||
UDPPacket packet = UDPPacket.acquire(_context);
|
||||
byte data[] = packet.getPacket().getData();
|
||||
Arrays.fill(data, 0, data.length, (byte)0x0);
|
||||
int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
|
||||
|
||||
int ipSize = reader.getRelayIntroReader().readIPSize();
|
||||
byte ip[] = new byte[ipSize];
|
||||
reader.getRelayIntroReader().readIP(ip, 0);
|
||||
int port = reader.getRelayIntroReader().readPort();
|
||||
|
||||
InetAddress to = null;
|
||||
try {
|
||||
to = InetAddress.getByAddress(ip);
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("IP for alice to hole punch to is invalid", uhe);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending relay hole punch to " + to + ":" + port);
|
||||
|
||||
// the packet is empty and does not need to be authenticated, since
|
||||
// its just for hole punching
|
||||
packet.getPacket().setLength(0);
|
||||
setTo(packet, to, port);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private void setTo(UDPPacket packet, InetAddress ip, int port) {
|
||||
packet.getPacket().setAddress(ip);
|
||||
packet.getPacket().setPort(port);
|
||||
|
@ -30,6 +30,7 @@ public class PacketHandler {
|
||||
private EstablishmentManager _establisher;
|
||||
private InboundMessageFragments _inbound;
|
||||
private PeerTestManager _testManager;
|
||||
private IntroductionManager _introManager;
|
||||
private boolean _keepReading;
|
||||
private List _handlers;
|
||||
|
||||
@ -38,7 +39,7 @@ public class PacketHandler {
|
||||
private static final long GRACE_PERIOD = Router.CLOCK_FUDGE_FACTOR + 30*1000;
|
||||
|
||||
|
||||
public PacketHandler(RouterContext ctx, UDPTransport transport, UDPEndpoint endpoint, EstablishmentManager establisher, InboundMessageFragments inbound, PeerTestManager testManager) {
|
||||
public PacketHandler(RouterContext ctx, UDPTransport transport, UDPEndpoint endpoint, EstablishmentManager establisher, InboundMessageFragments inbound, PeerTestManager testManager, IntroductionManager introManager) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(PacketHandler.class);
|
||||
_transport = transport;
|
||||
@ -46,6 +47,7 @@ public class PacketHandler {
|
||||
_establisher = establisher;
|
||||
_inbound = inbound;
|
||||
_testManager = testManager;
|
||||
_introManager = introManager;
|
||||
_handlers = new ArrayList(NUM_HANDLERS);
|
||||
for (int i = 0; i < NUM_HANDLERS; i++) {
|
||||
_handlers.add(new Handler());
|
||||
@ -193,8 +195,8 @@ public class PacketHandler {
|
||||
// process, so try our intro key
|
||||
// (after an outbound establishment process, there wouldn't
|
||||
// be any stray packets)
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Validation with existing con failed, but validation as reestablish/stray passed");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Validation with existing con failed, but validation as reestablish/stray passed");
|
||||
packet.decrypt(_transport.getIntroKey());
|
||||
} else {
|
||||
_state = 21;
|
||||
@ -235,8 +237,8 @@ public class PacketHandler {
|
||||
_state = 28;
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Valid introduction packet received: " + packet);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Valid introduction packet received: " + packet);
|
||||
}
|
||||
|
||||
_state = 29;
|
||||
@ -392,16 +394,31 @@ public class PacketHandler {
|
||||
_state = 50;
|
||||
if (outState != null)
|
||||
state = _establisher.receiveData(outState);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received new DATA packet from " + state + ": " + packet);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received new DATA packet from " + state + ": " + packet);
|
||||
_inbound.receiveData(state, reader.getDataReader());
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_TEST:
|
||||
_state = 51;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received test packet: " + reader + " from " + from);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received test packet: " + reader + " from " + from);
|
||||
_testManager.receiveTest(from, reader);
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_RELAY_REQUEST:
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received relay request packet: " + reader + " from " + from);
|
||||
_introManager.receiveRelayRequest(from, reader);
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_RELAY_INTRO:
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received relay intro packet: " + reader + " from " + from);
|
||||
_introManager.receiveRelayIntro(from, reader);
|
||||
break;
|
||||
case UDPPacket.PAYLOAD_TYPE_RELAY_RESPONSE:
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received relay response packet: " + reader + " from " + from);
|
||||
_establisher.receiveRelayResponse(from, reader);
|
||||
break;
|
||||
default:
|
||||
_state = 52;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
|
@ -125,6 +125,12 @@ class PeerTestManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have sent a packet to charlie within the last 3 minutes, ignore any test
|
||||
* results we get from them, as our NAT will have poked a hole anyway
|
||||
*
|
||||
*/
|
||||
private static final long CHARLIE_RECENT_PERIOD = 3*60*1000;
|
||||
|
||||
/**
|
||||
* Receive a PeerTest message which contains the correct nonce for our current
|
||||
@ -152,7 +158,10 @@ class PeerTestManager {
|
||||
}
|
||||
} else {
|
||||
PeerState charlieSession = _transport.getPeerState(from);
|
||||
if (charlieSession != null) {
|
||||
long recentBegin = _context.clock().now() - CHARLIE_RECENT_PERIOD;
|
||||
if ( (charlieSession != null) &&
|
||||
(charlieSession.getLastACKSend() > recentBegin) &&
|
||||
(charlieSession.getLastSendTime() > recentBegin) ) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Bob chose a charlie we already have a session to, cancelling the test and rerunning (bob: "
|
||||
+ _currentTest + ", charlie: " + from + ")");
|
||||
|
@ -1,24 +0,0 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import net.i2p.data.SessionKey;
|
||||
|
||||
/**
|
||||
* Describe the offering to act as an introducer
|
||||
*
|
||||
*/
|
||||
class RelayPeer {
|
||||
private String _host;
|
||||
private int _port;
|
||||
private byte _tag[];
|
||||
private SessionKey _relayIntroKey;
|
||||
public RelayPeer(String host, int port, byte tag[], SessionKey introKey) {
|
||||
_host = host;
|
||||
_port = port;
|
||||
_tag = tag;
|
||||
_relayIntroKey = introKey;
|
||||
}
|
||||
public String getHost() { return _host; }
|
||||
public int getPort() { return _port; }
|
||||
public byte[] getTag() { return _tag; }
|
||||
public SessionKey getIntroKey() { return _relayIntroKey; }
|
||||
}
|
@ -36,11 +36,17 @@ final class RemoteHostId {
|
||||
return (_port == id.getPort()) && DataHelper.eq(_ip, id.getIP());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
public String toString() { return toString(true); }
|
||||
public String toString(boolean includePort) {
|
||||
StringBuffer buf = new StringBuffer(_ip.length + 5);
|
||||
for (int i = 0; i < _ip.length; i++)
|
||||
buf.append(_ip[i]&0xFF).append('.');
|
||||
buf.append(_port);
|
||||
for (int i = 0; i < _ip.length; i++) {
|
||||
buf.append(_ip[i]&0xFF);
|
||||
if (i + 1 < _ip.length)
|
||||
buf.append('.');
|
||||
}
|
||||
if (includePort)
|
||||
buf.append(':').append(_port);
|
||||
return buf.toString();
|
||||
}
|
||||
public String toHostString() { return toString(false); }
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import java.util.Properties;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.SessionKey;
|
||||
|
||||
/**
|
||||
* basic helper to parse out peer info from a udp address
|
||||
@ -15,6 +16,11 @@ public class UDPAddress {
|
||||
private InetAddress _hostAddress;
|
||||
private int _port;
|
||||
private byte[] _introKey;
|
||||
private String _introHosts[];
|
||||
private InetAddress _introAddresses[];
|
||||
private int _introPorts[];
|
||||
private byte[] _introKeys[];
|
||||
private long _introTags[];
|
||||
|
||||
public static final String PROP_PORT = "port";
|
||||
public static final String PROP_HOST = "host";
|
||||
@ -23,11 +29,36 @@ public class UDPAddress {
|
||||
public static final String PROP_CAPACITY = "caps";
|
||||
public static final char CAPACITY_TESTING = 'B';
|
||||
public static final char CAPACITY_INTRODUCER = 'C';
|
||||
|
||||
public static final String PROP_INTRO_HOST_PREFIX = "ihost";
|
||||
public static final String PROP_INTRO_PORT_PREFIX = "iport";
|
||||
public static final String PROP_INTRO_KEY_PREFIX = "ikey";
|
||||
public static final String PROP_INTRO_TAG_PREFIX = "itag";
|
||||
static final int MAX_INTRODUCERS = 3;
|
||||
|
||||
public UDPAddress(RouterAddress addr) {
|
||||
parse(addr);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer rv = new StringBuffer(64);
|
||||
rv.append("[SSU ");
|
||||
if (_host != null)
|
||||
rv.append("host: ").append(_host).append(' ');
|
||||
if (_port > 0)
|
||||
rv.append("port: ").append(_port).append(' ');
|
||||
if (_introKey != null)
|
||||
rv.append("key: ").append(Base64.encode(_introKey)).append(' ');
|
||||
if (_introHosts != null) {
|
||||
for (int i = 0; i < _introHosts.length; i++) {
|
||||
rv.append("intro[" + i + "]: ").append(_introHosts[i]);
|
||||
rv.append(':').append(_introPorts[i]);
|
||||
rv.append('/').append(Base64.encode(_introKeys[i])).append(' ');
|
||||
}
|
||||
}
|
||||
return rv.toString();
|
||||
}
|
||||
|
||||
private void parse(RouterAddress addr) {
|
||||
Properties opts = addr.getOptions();
|
||||
_host = opts.getProperty(PROP_HOST);
|
||||
@ -42,6 +73,45 @@ public class UDPAddress {
|
||||
String key = opts.getProperty(PROP_INTRO_KEY);
|
||||
if (key != null)
|
||||
_introKey = Base64.decode(key.trim());
|
||||
|
||||
for (int i = MAX_INTRODUCERS; i >= 0; i--) {
|
||||
String host = opts.getProperty(PROP_INTRO_HOST_PREFIX + i);
|
||||
if (host == null) continue;
|
||||
String port = opts.getProperty(PROP_INTRO_PORT_PREFIX + i);
|
||||
if (port == null) continue;
|
||||
String k = opts.getProperty(PROP_INTRO_KEY_PREFIX + i);
|
||||
if (k == null) continue;
|
||||
byte ikey[] = Base64.decode(k);
|
||||
if ( (ikey == null) || (ikey.length != SessionKey.KEYSIZE_BYTES) )
|
||||
continue;
|
||||
String t = opts.getProperty(PROP_INTRO_TAG_PREFIX + i);
|
||||
if (t == null) continue;
|
||||
int p = -1;
|
||||
try {
|
||||
p = Integer.parseInt(port);
|
||||
if (p <= 0) continue;
|
||||
} catch (NumberFormatException nfe) {
|
||||
continue;
|
||||
}
|
||||
long tag = -1;
|
||||
try {
|
||||
tag = Long.parseLong(t);
|
||||
if (tag <= 0) continue;
|
||||
} catch (NumberFormatException nfe) {
|
||||
continue;
|
||||
}
|
||||
if (_introHosts == null) {
|
||||
_introHosts = new String[i+1];
|
||||
_introPorts = new int[i+1];
|
||||
_introAddresses = new InetAddress[i+1];
|
||||
_introKeys = new byte[i+1][];
|
||||
_introTags = new long[i+1];
|
||||
}
|
||||
_introHosts[i] = host;
|
||||
_introPorts[i] = p;
|
||||
_introKeys[i] = ikey;
|
||||
_introTags[i] = tag;
|
||||
}
|
||||
}
|
||||
|
||||
public String getHost() { return _host; }
|
||||
@ -57,4 +127,20 @@ public class UDPAddress {
|
||||
}
|
||||
public int getPort() { return _port; }
|
||||
public byte[] getIntroKey() { return _introKey; }
|
||||
|
||||
public int getIntroducerCount() { return (_introAddresses == null ? 0 : _introAddresses.length); }
|
||||
public InetAddress getIntroducerHost(int i) {
|
||||
if (_introAddresses[i] == null) {
|
||||
try {
|
||||
_introAddresses[i] = InetAddress.getByName(_introHosts[i]);
|
||||
} catch (UnknownHostException uhe) {
|
||||
_introAddresses[i] = null;
|
||||
}
|
||||
}
|
||||
return _introAddresses[i];
|
||||
}
|
||||
public int getIntroducerPort(int i) { return _introPorts[i]; }
|
||||
public byte[] getIntroducerKey(int i) { return _introKeys[i]; }
|
||||
public long getIntroducerTag(int i) { return _introTags[i]; }
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -24,6 +25,9 @@ public class UDPPacketReader {
|
||||
private SessionConfirmedReader _sessionConfirmedReader;
|
||||
private DataReader _dataReader;
|
||||
private PeerTestReader _peerTestReader;
|
||||
private RelayRequestReader _relayRequestReader;
|
||||
private RelayIntroReader _relayIntroReader;
|
||||
private RelayResponseReader _relayResponseReader;
|
||||
|
||||
private static final int KEYING_MATERIAL_LENGTH = 64;
|
||||
|
||||
@ -35,6 +39,9 @@ public class UDPPacketReader {
|
||||
_sessionConfirmedReader = new SessionConfirmedReader();
|
||||
_dataReader = new DataReader();
|
||||
_peerTestReader = new PeerTestReader();
|
||||
_relayRequestReader = new RelayRequestReader();
|
||||
_relayIntroReader = new RelayIntroReader();
|
||||
_relayResponseReader = new RelayResponseReader();
|
||||
}
|
||||
|
||||
public void initialize(UDPPacket packet) {
|
||||
@ -93,6 +100,9 @@ public class UDPPacketReader {
|
||||
public SessionConfirmedReader getSessionConfirmedReader() { return _sessionConfirmedReader; }
|
||||
public DataReader getDataReader() { return _dataReader; }
|
||||
public PeerTestReader getPeerTestReader() { return _peerTestReader; }
|
||||
public RelayRequestReader getRelayRequestReader() { return _relayRequestReader; }
|
||||
public RelayIntroReader getRelayIntroReader() { return _relayIntroReader; }
|
||||
public RelayResponseReader getRelayResponseReader() { return _relayResponseReader; }
|
||||
|
||||
public String toString() {
|
||||
switch (readPayloadType()) {
|
||||
@ -106,6 +116,12 @@ public class UDPPacketReader {
|
||||
return "Session request packet";
|
||||
case UDPPacket.PAYLOAD_TYPE_TEST:
|
||||
return "Peer test packet";
|
||||
case UDPPacket.PAYLOAD_TYPE_RELAY_INTRO:
|
||||
return "Relay intro packet";
|
||||
case UDPPacket.PAYLOAD_TYPE_RELAY_REQUEST:
|
||||
return "Relay request packet";
|
||||
case UDPPacket.PAYLOAD_TYPE_RELAY_RESPONSE:
|
||||
return "Relay response packet";
|
||||
default:
|
||||
return "Other packet type...";
|
||||
}
|
||||
@ -538,4 +554,214 @@ public class UDPPacketReader {
|
||||
System.arraycopy(_message, offset, target, targetOffset, SessionKey.KEYSIZE_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
/** Help read the RelayRequest payload */
|
||||
public class RelayRequestReader {
|
||||
public long readTag() {
|
||||
long rv = DataHelper.fromLong(_message, readBodyOffset(), 4);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("read alice tag: " + rv);
|
||||
return rv;
|
||||
}
|
||||
public int readIPSize() {
|
||||
int offset = readBodyOffset() + 4;
|
||||
int rv = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("read alice ip size: " + rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** what IP Alice is reachable on */
|
||||
public void readIP(byte target[], int targetOffset) {
|
||||
int offset = readBodyOffset() + 4;
|
||||
int size = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
System.arraycopy(_message, offset, target, targetOffset, size);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("read alice ip: " + Base64.encode(target, targetOffset, size));
|
||||
}
|
||||
public int readPort() {
|
||||
int offset = readBodyOffset() + 4;
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
int rv = (int)DataHelper.fromLong(_message, offset, 2);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("read alice port: " + rv);
|
||||
return rv;
|
||||
}
|
||||
public int readChallengeSize() {
|
||||
int offset = readBodyOffset() + 4;
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += 2;
|
||||
int rv = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("read challenge size: " + rv);
|
||||
return rv;
|
||||
}
|
||||
public void readChallengeSize(byte target[], int targetOffset) {
|
||||
int offset = readBodyOffset() + 4;
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += 2;
|
||||
int sz = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
System.arraycopy(_message, offset, target, targetOffset, sz);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("read challenge data: " + Base64.encode(target));
|
||||
}
|
||||
public void readAliceIntroKey(byte target[], int targetOffset) {
|
||||
int offset = readBodyOffset() + 4;
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += 2;
|
||||
int sz = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += sz;
|
||||
System.arraycopy(_message, offset, target, targetOffset, SessionKey.KEYSIZE_BYTES);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("read alice intro key: " + Base64.encode(target, targetOffset, SessionKey.KEYSIZE_BYTES)
|
||||
+ " packet size: " + _payloadLength + " off: " + offset + " data: " + Base64.encode(_message));
|
||||
}
|
||||
public long readNonce() {
|
||||
int offset = readBodyOffset() + 4;
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += 2;
|
||||
int sz = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += sz;
|
||||
offset += SessionKey.KEYSIZE_BYTES;
|
||||
long rv = DataHelper.fromLong(_message, offset, 4);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("read request nonce: " + rv);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
/** Help read the RelayIntro payload */
|
||||
public class RelayIntroReader {
|
||||
public int readIPSize() {
|
||||
int offset = readBodyOffset();
|
||||
return (int)DataHelper.fromLong(_message, offset, 1);
|
||||
}
|
||||
|
||||
/** what IP Alice is reachable on */
|
||||
public void readIP(byte target[], int targetOffset) {
|
||||
int offset = readBodyOffset();
|
||||
int size = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
System.arraycopy(_message, offset, target, targetOffset, size);
|
||||
}
|
||||
public int readPort() {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
return (int)DataHelper.fromLong(_message, offset, 2);
|
||||
}
|
||||
public int readChallengeSize() {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += 2;
|
||||
return (int)DataHelper.fromLong(_message, offset, 1);
|
||||
}
|
||||
public void readChallengeSize(byte target[], int targetOffset) {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += 2;
|
||||
int sz = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
System.arraycopy(_message, offset, target, targetOffset, sz);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Help read the RelayResponse payload */
|
||||
public class RelayResponseReader {
|
||||
public int readCharlieIPSize() {
|
||||
int offset = readBodyOffset();
|
||||
return (int)DataHelper.fromLong(_message, offset, 1);
|
||||
}
|
||||
/** what IP charlie is reachable on */
|
||||
public void readCharlieIP(byte target[], int targetOffset) {
|
||||
int offset = readBodyOffset();
|
||||
int size = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
System.arraycopy(_message, offset, target, targetOffset, size);
|
||||
}
|
||||
/** what port charlie is reachable on */
|
||||
public int readCharliePort() {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
return (int)DataHelper.fromLong(_message, offset, 2);
|
||||
}
|
||||
|
||||
public int readAliceIPSize() {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += 2;
|
||||
return (int)DataHelper.fromLong(_message, offset, 1);
|
||||
}
|
||||
public void readAliceIP(byte target[], int targetOffset) {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += 2;
|
||||
int sz = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
System.arraycopy(_message, offset, target, targetOffset, sz);
|
||||
}
|
||||
public int readAlicePort() {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += 2;
|
||||
int sz = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += sz;
|
||||
return (int)DataHelper.fromLong(_message, offset, 2);
|
||||
}
|
||||
public long readNonce() {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += 2;
|
||||
int sz = (int)DataHelper.fromLong(_message, offset, 1);
|
||||
offset++;
|
||||
offset += sz;
|
||||
offset += 2;
|
||||
return DataHelper.fromLong(_message, offset, 4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String args[]) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
try {
|
||||
PacketBuilder b = new PacketBuilder(ctx);
|
||||
InetAddress introHost = InetAddress.getLocalHost();
|
||||
int introPort = 1234;
|
||||
byte introKey[] = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
ctx.random().nextBytes(introKey);
|
||||
long introTag = ctx.random().nextLong(0xFFFFFFFFl);
|
||||
long introNonce = ctx.random().nextLong(0xFFFFFFFFl);
|
||||
SessionKey ourIntroKey = ctx.keyGenerator().generateSessionKey();
|
||||
UDPPacket packet = b.buildRelayRequest(introHost, introPort, introKey, introTag, ourIntroKey, introNonce, false);
|
||||
UDPPacketReader r = new UDPPacketReader(ctx);
|
||||
r.initialize(packet);
|
||||
RelayRequestReader reader = r.getRelayRequestReader();
|
||||
System.out.println("Nonce: " + reader.readNonce() + " / " + introNonce);
|
||||
System.out.println("Tag : " + reader.readTag() + " / " + introTag);
|
||||
byte readKey[] = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
reader.readAliceIntroKey(readKey, 0);
|
||||
System.out.println("Key : " + Base64.encode(readKey) + " / " + ourIntroKey.toBase64());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ public class UDPReceiver {
|
||||
_context.statManager().createRateStat("udp.droppedInbound", "How many packet are queued up but not yet received when we drop", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.droppedInboundProbabalistically", "How many packet we drop probabalistically (to simulate failures)", "udp", new long[] { 60*1000, 5*60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.acceptedInboundProbabalistically", "How many packet we accept probabalistically (to simulate failures)", "udp", new long[] { 60*1000, 5*60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.receiveHolePunch", "How often we receive a NAT hole punch", "udp", new long[] { 60*1000, 5*60*1000, 10*60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
@ -212,10 +213,15 @@ public class UDPReceiver {
|
||||
FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestInbound(size, "UDP receiver");
|
||||
while (req.getPendingInboundRequested() > 0)
|
||||
req.waitForNextAllocation();
|
||||
|
||||
int queued = receive(packet);
|
||||
_context.statManager().addRateData("udp.receivePacketSize", size, queued);
|
||||
} else {
|
||||
_context.statManager().addRateData("udp.receiveHolePunch", 1, 0);
|
||||
// nat hole punch packets are 0 bytes
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Received a 0 byte udp packet from " + packet.getPacket().getAddress() + ":" + packet.getPacket().getPort());
|
||||
}
|
||||
|
||||
int queued = receive(packet);
|
||||
_context.statManager().addRateData("udp.receivePacketSize", size, queued);
|
||||
} catch (IOException ioe) {
|
||||
if (_socketChanged) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
|
@ -40,8 +40,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
private Map _peersByIdent;
|
||||
/** RemoteHostId to PeerState */
|
||||
private Map _peersByRemoteHost;
|
||||
/** Relay tag (base64 String) to PeerState */
|
||||
private Map _peersByRelayTag;
|
||||
/**
|
||||
* Array of list of PeerState instances, where each list contains peers with one
|
||||
* of the given capacities (from 0-25, referencing 'A'-'Z').
|
||||
@ -57,14 +55,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
private InboundMessageFragments _inboundFragments;
|
||||
private UDPFlooder _flooder;
|
||||
private PeerTestManager _testManager;
|
||||
private IntroductionManager _introManager;
|
||||
private ExpirePeerEvent _expireEvent;
|
||||
private PeerTestEvent _testEvent;
|
||||
private short _reachabilityStatus;
|
||||
private long _reachabilityStatusLastUpdated;
|
||||
private long _introducersSelectedOn;
|
||||
|
||||
/** list of RelayPeer objects for people who will relay to us */
|
||||
private List _relayPeers;
|
||||
|
||||
/** summary info to distribute */
|
||||
private RouterAddress _externalAddress;
|
||||
/** port number on which we can be reached, or -1 */
|
||||
@ -122,7 +119,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
_log = ctx.logManager().getLog(UDPTransport.class);
|
||||
_peersByIdent = new HashMap(128);
|
||||
_peersByRemoteHost = new HashMap(128);
|
||||
_peersByRelayTag = new HashMap(128);
|
||||
_peersByCapacity = new ArrayList['Z'-'A'+1];
|
||||
for (int i = 0; i < _peersByCapacity.length; i++)
|
||||
_peersByCapacity[i] = new ArrayList(16);
|
||||
@ -131,7 +127,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
TimedWeightedPriorityMessageQueue mq = new TimedWeightedPriorityMessageQueue(ctx, PRIORITY_LIMITS, PRIORITY_WEIGHT, this);
|
||||
_outboundMessages = mq;
|
||||
_activeThrottle = mq;
|
||||
_relayPeers = new ArrayList(1);
|
||||
|
||||
_fastBid = new SharedBid(50);
|
||||
_slowBid = new SharedBid(1000);
|
||||
@ -143,6 +138,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
_expireEvent = new ExpirePeerEvent();
|
||||
_testEvent = new PeerTestEvent();
|
||||
_reachabilityStatus = CommSystemFacade.STATUS_UNKNOWN;
|
||||
_introManager = new IntroductionManager(_context, this);
|
||||
_introducersSelectedOn = -1;
|
||||
|
||||
_context.statManager().createRateStat("udp.droppedPeer", "How long ago did we receive from a dropped peer (duration == session lifetime", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("udp.droppedPeerInactive", "How long ago did we receive from a dropped peer (duration == session lifetime)", "udp", new long[] { 60*60*1000, 24*60*60*1000 });
|
||||
@ -172,6 +169,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
_inboundFragments.shutdown();
|
||||
if (_flooder != null)
|
||||
_flooder.shutdown();
|
||||
_introManager.reset();
|
||||
|
||||
_introKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
|
||||
System.arraycopy(_context.routerHash().getData(), 0, _introKey.getData(), 0, SessionKey.KEYSIZE_BYTES);
|
||||
@ -225,7 +223,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
_testManager = new PeerTestManager(_context, this);
|
||||
|
||||
if (_handler == null)
|
||||
_handler = new PacketHandler(_context, this, _endpoint, _establisher, _inboundFragments, _testManager);
|
||||
_handler = new PacketHandler(_context, this, _endpoint, _establisher, _inboundFragments, _testManager, _introManager);
|
||||
|
||||
if (_refiller == null)
|
||||
_refiller = new OutboundRefiller(_context, _fragments, _outboundMessages);
|
||||
@ -365,10 +363,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
* get the state for the peer being introduced, or null if we aren't
|
||||
* offering to introduce anyone with that tag.
|
||||
*/
|
||||
public PeerState getPeerState(String relayTag) {
|
||||
synchronized (_peersByRelayTag) {
|
||||
return (PeerState)_peersByRelayTag.get(relayTag);
|
||||
}
|
||||
public PeerState getPeerState(long relayTag) {
|
||||
return _introManager.get(relayTag);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -486,8 +482,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
}
|
||||
}
|
||||
|
||||
if ( (oldPeer != null) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Peer already connected: old=" + oldPeer + " new=" + peer, new Exception("dup"));
|
||||
oldPeer = null;
|
||||
|
||||
RemoteHostId remoteId = peer.getRemoteHostId();
|
||||
@ -513,11 +507,24 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
|
||||
_expireEvent.add(peer);
|
||||
|
||||
_introManager.add(peer);
|
||||
|
||||
if (oldEstablishedOn > 0)
|
||||
_context.statManager().addRateData("udp.alreadyConnected", oldEstablishedOn, 0);
|
||||
|
||||
// if we need introducers, try to shift 'em around every 10 minutes
|
||||
if (introducersRequired() && (_introducersSelectedOn < _context.clock().now() - 10*60*1000))
|
||||
rebuildExternalAddress();
|
||||
return true;
|
||||
}
|
||||
|
||||
public RouterAddress getCurrentAddress() {
|
||||
// if we need introducers, try to shift 'em around every 10 minutes
|
||||
if (introducersRequired() && (_introducersSelectedOn < _context.clock().now() - 10*60*1000))
|
||||
rebuildExternalAddress(false);
|
||||
return super.getCurrentAddress();
|
||||
}
|
||||
|
||||
private void dropPeer(PeerState peer) {
|
||||
dropPeer(peer, true);
|
||||
}
|
||||
@ -562,6 +569,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
_log.info(buf.toString(), new Exception("Dropped by"));
|
||||
}
|
||||
|
||||
_introManager.remove(peer);
|
||||
// a bit overzealous - perhaps we should only rebuild the external if the peer being dropped
|
||||
// is one of our introducers?
|
||||
rebuildExternalAddress();
|
||||
|
||||
if (peer.getRemotePeer() != null) {
|
||||
dropPeerCapacities(peer);
|
||||
|
||||
@ -701,21 +713,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
void setExternalListenHost(byte addr[]) throws UnknownHostException {
|
||||
_externalListenHost = InetAddress.getByAddress(addr);
|
||||
}
|
||||
void addRelayPeer(String host, int port, byte tag[], SessionKey relayIntroKey) {
|
||||
if ( (_externalListenPort > 0) && (_externalListenHost != null) )
|
||||
return; // no need for relay peers, as we are reachable
|
||||
|
||||
RelayPeer peer = new RelayPeer(host, port, tag, relayIntroKey);
|
||||
synchronized (_relayPeers) {
|
||||
_relayPeers.add(peer);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean explicitAddressSpecified() {
|
||||
return (_context.getProperty(PROP_EXTERNAL_HOST) != null);
|
||||
}
|
||||
|
||||
void rebuildExternalAddress() {
|
||||
void rebuildExternalAddress() { rebuildExternalAddress(true); }
|
||||
void rebuildExternalAddress(boolean allowRebuildRouterInfo) {
|
||||
// if the external port is specified, we want to use that to bind to even
|
||||
// if we don't know the external host.
|
||||
String port = _context.getProperty(PROP_EXTERNAL_PORT);
|
||||
@ -736,29 +740,49 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
}
|
||||
}
|
||||
|
||||
Properties options = new Properties();
|
||||
Properties options = new Properties();
|
||||
boolean introducersRequired = introducersRequired();
|
||||
if (introducersRequired) {
|
||||
List peers = new ArrayList(PUBLIC_RELAY_COUNT);
|
||||
int found = 0;
|
||||
_introManager.pickInbound(peers, PUBLIC_RELAY_COUNT);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Introducers required, picked peers: " + peers);
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
PeerState peer = (PeerState)peers.get(i);
|
||||
RouterInfo ri = _context.netDb().lookupRouterInfoLocally(peer.getRemotePeer());
|
||||
if (ri == null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Picked peer has no local routerInfo: " + peer);
|
||||
continue;
|
||||
}
|
||||
RouterAddress ra = ri.getTargetAddress(STYLE);
|
||||
if (ra == null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Picked peer has no SSU address: " + ri);
|
||||
continue;
|
||||
}
|
||||
UDPAddress ura = new UDPAddress(ra);
|
||||
options.setProperty(UDPAddress.PROP_INTRO_HOST_PREFIX + i, peer.getRemoteHostId().toHostString());
|
||||
options.setProperty(UDPAddress.PROP_INTRO_PORT_PREFIX + i, String.valueOf(peer.getRemotePort()));
|
||||
options.setProperty(UDPAddress.PROP_INTRO_KEY_PREFIX + i, Base64.encode(ura.getIntroKey()));
|
||||
options.setProperty(UDPAddress.PROP_INTRO_TAG_PREFIX + i, String.valueOf(peer.getTheyRelayToUsAs()));
|
||||
found++;
|
||||
}
|
||||
if (found > 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Picked peers: " + found);
|
||||
_introducersSelectedOn = _context.clock().now();
|
||||
}
|
||||
}
|
||||
if ( (_externalListenPort > 0) && (_externalListenHost != null) ) {
|
||||
options.setProperty(UDPAddress.PROP_PORT, String.valueOf(_externalListenPort));
|
||||
options.setProperty(UDPAddress.PROP_HOST, _externalListenHost.getHostAddress());
|
||||
// if we have explicit external addresses, they had better be reachable
|
||||
options.setProperty(UDPAddress.PROP_CAPACITY, ""+UDPAddress.CAPACITY_TESTING);
|
||||
} else {
|
||||
// grab 3 relays randomly
|
||||
synchronized (_relayPeers) {
|
||||
Collections.shuffle(_relayPeers);
|
||||
int numPeers = PUBLIC_RELAY_COUNT;
|
||||
if (numPeers > _relayPeers.size())
|
||||
numPeers = _relayPeers.size();
|
||||
for (int i = 0; i < numPeers; i++) {
|
||||
RelayPeer peer = (RelayPeer)_relayPeers.get(i);
|
||||
options.setProperty("relay." + i + ".host", peer.getHost());
|
||||
options.setProperty("relay." + i + ".port", String.valueOf(peer.getPort()));
|
||||
options.setProperty("relay." + i + ".tag", Base64.encode(peer.getTag()));
|
||||
options.setProperty("relay." + i + ".key", peer.getIntroKey().toBase64());
|
||||
}
|
||||
}
|
||||
if (options.size() <= 0)
|
||||
return;
|
||||
if (introducersRequired)
|
||||
options.setProperty(UDPAddress.PROP_CAPACITY, ""+UDPAddress.CAPACITY_TESTING);
|
||||
else
|
||||
options.setProperty(UDPAddress.PROP_CAPACITY, ""+UDPAddress.CAPACITY_TESTING + UDPAddress.CAPACITY_INTRODUCER);
|
||||
}
|
||||
options.setProperty(UDPAddress.PROP_INTRO_KEY, _introKey.toBase64());
|
||||
|
||||
@ -768,10 +792,29 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
addr.setTransportStyle(STYLE);
|
||||
addr.setOptions(options);
|
||||
|
||||
boolean wantsRebuild = false;
|
||||
if ( (_externalAddress == null) || !(_externalAddress.equals(addr)) )
|
||||
wantsRebuild = true;
|
||||
_externalAddress = addr;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Address rebuilt: " + addr);
|
||||
replaceAddress(addr);
|
||||
if (allowRebuildRouterInfo)
|
||||
_context.router().rebuildRouterInfo();
|
||||
}
|
||||
|
||||
public static final String PROP_FORCE_INTRODUCERS = "i2np.udp.forceIntroducers";
|
||||
public boolean introducersRequired() {
|
||||
String forceIntroducers = _context.getProperty(PROP_FORCE_INTRODUCERS);
|
||||
if ( (forceIntroducers != null) && (Boolean.valueOf(forceIntroducers).booleanValue()) )
|
||||
return true;
|
||||
switch (getReachabilityStatus()) {
|
||||
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
|
||||
case CommSystemFacade.STATUS_DIFFERENT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String getPacketHandlerStatus() {
|
||||
@ -869,7 +912,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
buf.append("<tr>");
|
||||
|
||||
String name = peer.getRemotePeer().toBase64().substring(0,6);
|
||||
buf.append("<td valign=\"top\" nowrap><code>");
|
||||
buf.append("<td valign=\"top\" nowrap=\"nowrap\"><code>");
|
||||
buf.append("<a href=\"netdb.jsp#");
|
||||
buf.append(name);
|
||||
buf.append("\">");
|
||||
@ -897,6 +940,15 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
buf.append("0");
|
||||
buf.append(port);
|
||||
buf.append("</a>");
|
||||
if (peer.getWeRelayToThemAs() > 0)
|
||||
buf.append(">");
|
||||
else
|
||||
buf.append(" ");
|
||||
if (peer.getTheyRelayToUsAs() > 0)
|
||||
buf.append("<");
|
||||
else
|
||||
buf.append(" ");
|
||||
|
||||
boolean appended = false;
|
||||
if (_activeThrottle.isChoked(peer.getRemotePeer())) {
|
||||
if (!appended) buf.append("<br />");
|
||||
@ -1131,7 +1183,21 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
|
||||
break;
|
||||
}
|
||||
}
|
||||
public short getReachabilityStatus() { return _reachabilityStatus; }
|
||||
private static final String PROP_REACHABILITY_STATUS_OVERRIDE = "i2np.udp.status";
|
||||
public short getReachabilityStatus() {
|
||||
String override = _context.getProperty(PROP_REACHABILITY_STATUS_OVERRIDE);
|
||||
if (override == null)
|
||||
return _reachabilityStatus;
|
||||
|
||||
if ("ok".equals(override))
|
||||
return CommSystemFacade.STATUS_OK;
|
||||
else if ("err-reject".equals(override))
|
||||
return CommSystemFacade.STATUS_REJECT_UNSOLICITED;
|
||||
else if ("err-different".equals(override))
|
||||
return CommSystemFacade.STATUS_DIFFERENT;
|
||||
|
||||
return _reachabilityStatus;
|
||||
}
|
||||
public void recheckReachability() {
|
||||
_testEvent.runTest();
|
||||
}
|
||||
|
Reference in New Issue
Block a user