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:
jrandom
2005-09-10 04:30:36 +00:00
committed by zzz
parent b5d571c75f
commit 44770b7c07
15 changed files with 1019 additions and 111 deletions

View File

@ -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!)

View File

@ -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

View File

@ -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);

View File

@ -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
}

View File

@ -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));
}
}

View File

@ -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; }

View File

@ -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);

View File

@ -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))

View File

@ -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 + ")");

View File

@ -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; }
}

View File

@ -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); }
}

View File

@ -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]; }
}

View File

@ -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();
}
}
}

View File

@ -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))

View File

@ -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("&gt;");
else
buf.append("&nbsp;");
if (peer.getTheyRelayToUsAs() > 0)
buf.append("&lt;");
else
buf.append("&nbsp;");
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();
}