First pass of the UDP transport. No where near ready for use, but it does

the basics (negotiate a session and send I2NP messages back and forth).  Lots,
lots more left.
This commit is contained in:
jrandom
2005-04-12 16:48:43 +00:00
committed by zzz
parent 5b56d22da9
commit 7beb92b1cc
34 changed files with 5744 additions and 47 deletions

View File

@ -247,6 +247,23 @@ public class DHSessionKeyBuilder {
if (_myPublicValue == null) _myPublicValue = generateMyValue();
return _myPublicValue;
}
/**
* Return a 256 byte representation of our public key, with leading 0s
* if necessary.
*
*/
public byte[] getMyPublicValueBytes() {
BigInteger bi = getMyPublicValue();
byte data[] = bi.toByteArray();
byte rv[] = new byte[256];
if (data.length == 257) // high byte has the sign bit
System.arraycopy(data, 1, rv, 0, rv.length);
else if (data.length == 256)
System.arraycopy(data, 0, rv, 0, rv.length);
else
System.arraycopy(data, 0, rv, rv.length-data.length, data.length);
return rv;
}
/**
* Specify the value given by the peer for use in the session key negotiation
@ -255,6 +272,20 @@ public class DHSessionKeyBuilder {
public void setPeerPublicValue(BigInteger peerVal) {
_peerValue = peerVal;
}
public void setPeerPublicValue(byte val[]) {
if (val.length != 256)
throw new IllegalArgumentException("Peer public value must be exactly 256 bytes");
if (1 == (val[0] & 0x80)) {
// high bit set, need to inject an additional byte to keep 2s complement
if (_log.shouldLog(Log.DEBUG))
_log.debug("High bit set");
byte val2[] = new byte[257];
System.arraycopy(val, 0, val2, 1, 256);
val = val2;
}
_peerValue = new NativeBigInteger(val);
}
public BigInteger getPeerPublicValue() {
return _peerValue;

View File

@ -61,6 +61,7 @@ public interface I2NPMessage extends DataStructure {
* Replay resistent message Id
*/
public long getUniqueId();
public void setUniqueId(long id);
/**
* Date after which the message should be dropped (and the associated uniqueId forgotten)
@ -72,7 +73,20 @@ public interface I2NPMessage extends DataStructure {
/** How large the message is, including any checksums */
public int getMessageSize();
/** How large the raw message is */
public int getRawMessageSize();
/** write the message to the buffer, returning the number of bytes written */
/**
* write the message to the buffer, returning the number of bytes written.
* the data is formatted so as to be self contained, with the type, size,
* expiration, unique id, as well as a checksum bundled along.
*/
public int toByteArray(byte buffer[]);
/**
* write the message to the buffer, returning the number of bytes written.
* the data is is not self contained - it does not include the size,
* unique id, or any checksum, but does include the type and expiration.
*/
public int toRawByteArray(byte buffer[]);
}

View File

@ -49,7 +49,7 @@ public class I2NPMessageHandler {
try {
int type = (int)DataHelper.readLong(in, 1);
_lastReadBegin = System.currentTimeMillis();
I2NPMessage msg = createMessage(type);
I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type);
if (msg == null)
throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message");
try {
@ -94,7 +94,7 @@ public class I2NPMessageHandler {
int type = (int)DataHelper.fromLong(data, cur, 1);
cur++;
_lastReadBegin = System.currentTimeMillis();
I2NPMessage msg = createMessage(type);
I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type);
if (msg == null)
throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message");
try {
@ -118,39 +118,6 @@ public class I2NPMessageHandler {
public long getLastReadTime() { return _lastReadEnd - _lastReadBegin; }
public int getLastSize() { return _lastSize; }
/**
* Yes, this is fairly ugly, but its the only place it ever happens.
*
*/
private I2NPMessage createMessage(int type) throws I2NPMessageException {
switch (type) {
case DatabaseStoreMessage.MESSAGE_TYPE:
return new DatabaseStoreMessage(_context);
case DatabaseLookupMessage.MESSAGE_TYPE:
return new DatabaseLookupMessage(_context);
case DatabaseSearchReplyMessage.MESSAGE_TYPE:
return new DatabaseSearchReplyMessage(_context);
case DeliveryStatusMessage.MESSAGE_TYPE:
return new DeliveryStatusMessage(_context);
case DateMessage.MESSAGE_TYPE:
return new DateMessage(_context);
case GarlicMessage.MESSAGE_TYPE:
return new GarlicMessage(_context);
case TunnelDataMessage.MESSAGE_TYPE:
return new TunnelDataMessage(_context);
case TunnelGatewayMessage.MESSAGE_TYPE:
return new TunnelGatewayMessage(_context);
case DataMessage.MESSAGE_TYPE:
return new DataMessage(_context);
case TunnelCreateMessage.MESSAGE_TYPE:
return new TunnelCreateMessage(_context);
case TunnelCreateStatusMessage.MESSAGE_TYPE:
return new TunnelCreateStatusMessage(_context);
default:
return null;
}
}
public static void main(String args[]) {
try {
I2NPMessage msg = new I2NPMessageHandler(I2PAppContext.getGlobalContext()).readMessage(new FileInputStream(args[0]));

View File

@ -35,6 +35,8 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
public final static long DEFAULT_EXPIRATION_MS = 1*60*1000; // 1 minute by default
public final static int CHECKSUM_LENGTH = 1; //Hash.HASH_LENGTH;
private static final boolean RAW_FULL_SIZE = true;
public I2NPMessageImpl(I2PAppContext context) {
_context = context;
_log = context.logManager().getLog(I2NPMessageImpl.class);
@ -165,7 +167,13 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
public void setMessageExpiration(long exp) { _expiration = exp; }
public synchronized int getMessageSize() {
return calculateWrittenLength()+15 + CHECKSUM_LENGTH; // 47 bytes in the header
return calculateWrittenLength()+15 + CHECKSUM_LENGTH; // 16 bytes in the header
}
public synchronized int getRawMessageSize() {
if (RAW_FULL_SIZE)
return getMessageSize();
else
return calculateWrittenLength()+5;
}
public byte[] toByteArray() {
@ -248,4 +256,83 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
return curIndex;
}
*/
public int toRawByteArray(byte buffer[]) {
if (RAW_FULL_SIZE)
return toByteArray(buffer);
try {
int off = 0;
DataHelper.toLong(buffer, off, 1, getType());
off += 1;
DataHelper.toLong(buffer, off, 4, _expiration/1000); // seconds
off += 4;
return writeMessageBody(buffer, off);
} catch (I2NPMessageException ime) {
_context.logManager().getLog(getClass()).log(Log.CRIT, "Error writing", ime);
throw new IllegalStateException("Unable to serialize the message (" + getClass().getName()
+ "): " + ime.getMessage());
}
}
public static I2NPMessage fromRawByteArray(I2PAppContext ctx, byte buffer[], int offset, int len) throws I2NPMessageException {
int type = (int)DataHelper.fromLong(buffer, offset, 1);
offset++;
I2NPMessage msg = createMessage(ctx, type);
if (msg == null)
throw new I2NPMessageException("Unknown message type: " + type);
if (RAW_FULL_SIZE) {
try {
msg.readBytes(buffer, type, offset);
} catch (IOException ioe) {
throw new I2NPMessageException("Error reading the " + msg, ioe);
}
return msg;
}
long expiration = DataHelper.fromLong(buffer, offset, 4) * 1000; // seconds
offset += 4;
int dataSize = len - 1 - 4;
try {
msg.readMessage(buffer, offset, dataSize, type);
msg.setMessageExpiration(expiration);
return msg;
} catch (IOException ioe) {
throw new I2NPMessageException("IO error reading raw message", ioe);
}
}
/**
* Yes, this is fairly ugly, but its the only place it ever happens.
*
*/
public static I2NPMessage createMessage(I2PAppContext context, int type) throws I2NPMessageException {
switch (type) {
case DatabaseStoreMessage.MESSAGE_TYPE:
return new DatabaseStoreMessage(context);
case DatabaseLookupMessage.MESSAGE_TYPE:
return new DatabaseLookupMessage(context);
case DatabaseSearchReplyMessage.MESSAGE_TYPE:
return new DatabaseSearchReplyMessage(context);
case DeliveryStatusMessage.MESSAGE_TYPE:
return new DeliveryStatusMessage(context);
case DateMessage.MESSAGE_TYPE:
return new DateMessage(context);
case GarlicMessage.MESSAGE_TYPE:
return new GarlicMessage(context);
case TunnelDataMessage.MESSAGE_TYPE:
return new TunnelDataMessage(context);
case TunnelGatewayMessage.MESSAGE_TYPE:
return new TunnelGatewayMessage(context);
case DataMessage.MESSAGE_TYPE:
return new DataMessage(context);
case TunnelCreateMessage.MESSAGE_TYPE:
return new TunnelCreateMessage(context);
case TunnelCreateStatusMessage.MESSAGE_TYPE:
return new TunnelCreateStatusMessage(context);
default:
return null;
}
}
}

View File

@ -8,6 +8,8 @@ package net.i2p.router.transport;
*
*/
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Set;
@ -38,5 +40,5 @@ public interface Transport {
public int countActivePeers();
public List getMostRecentErrorMessages();
public String renderStatusHTML();
public void renderStatusHTML(Writer out) throws IOException;
}

View File

@ -8,6 +8,8 @@ package net.i2p.router.transport;
*
*/
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@ -365,7 +367,7 @@ public abstract class TransportImpl implements Transport {
/** Who to notify on message availability */
public void setListener(TransportEventListener listener) { _listener = listener; }
/** Make this stuff pretty (only used in the old console) */
public String renderStatusHTML() { return null; }
public void renderStatusHTML(Writer out) throws IOException {}
public RouterContext getContext() { return _context; }
}

View File

@ -21,6 +21,7 @@ import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.tcp.TCPTransport;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.util.Log;
public class TransportManager implements TransportEventListener {
@ -29,6 +30,7 @@ public class TransportManager implements TransportEventListener {
private RouterContext _context;
private final static String PROP_DISABLE_TCP = "i2np.tcp.disable";
private static final boolean ENABLE_UDP = false;
public TransportManager(RouterContext context) {
_context = context;
@ -57,6 +59,11 @@ public class TransportManager implements TransportEventListener {
t.setListener(this);
_transports.add(t);
}
if (ENABLE_UDP) {
UDPTransport udp = new UDPTransport(_context);
udp.setListener(this);
_transports.add(udp);
}
}
public void startListening() {
@ -172,13 +179,15 @@ public class TransportManager implements TransportEventListener {
}
}
buf.append("</pre>\n");
out.write(buf.toString());
for (Iterator iter = _transports.iterator(); iter.hasNext(); ) {
Transport t = (Transport)iter.next();
String str = t.renderStatusHTML();
if (str != null)
buf.append(str);
//String str = t.renderStatusHTML();
//if (str != null)
// buf.append(str);
t.renderStatusHTML(out);
}
out.write(buf.toString());
//out.write(buf.toString());
out.flush();
}
}

View File

@ -1,5 +1,7 @@
package net.i2p.router.transport.tcp;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@ -766,7 +768,7 @@ public class TCPTransport extends TransportImpl {
}
/** Make this stuff pretty (only used in the old console) */
public String renderStatusHTML() {
public void renderStatusHTML(Writer out) throws IOException {
StringBuffer buf = new StringBuffer(1024);
synchronized (_connectionLock) {
long offsetTotal = 0;
@ -813,7 +815,7 @@ public class TCPTransport extends TransportImpl {
}
buf.append("</ul>");
return buf.toString();
out.write(buf.toString());
}
/**

View File

@ -0,0 +1,44 @@
package net.i2p.router.transport.udp;
import java.util.List;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
* Blocking thread that pulls peers off the inboundFragment pool and
* sends them any outstanding ACKs. The logic of what peers get ACKed when
* is determined by the {@link InboundMessageFragments#getNextPeerToACK }
*
*/
public class ACKSender implements Runnable {
private RouterContext _context;
private Log _log;
private InboundMessageFragments _fragments;
private UDPTransport _transport;
private PacketBuilder _builder;
public ACKSender(RouterContext ctx, InboundMessageFragments fragments, UDPTransport transport) {
_context = ctx;
_log = ctx.logManager().getLog(ACKSender.class);
_fragments = fragments;
_transport = transport;
_builder = new PacketBuilder(_context, _transport);
}
public void run() {
while (_fragments.isAlive()) {
PeerState peer = _fragments.getNextPeerToACK();
if (peer != null) {
List acks = peer.retrieveACKs();
if ( (acks != null) && (acks.size() > 0) ) {
UDPPacket ack = _builder.buildACK(peer, acks);
if (_log.shouldLog(Log.INFO))
_log.info("Sending ACK for " + acks);
_transport.send(ack);
}
}
}
}
}

View File

@ -0,0 +1,556 @@
package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
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.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Coordinate the establishment of new sessions - both inbound and outbound.
* This has its own thread to add packets to the packet queue when necessary,
* as well as to drop any failed establishment attempts.
*
*/
public class EstablishmentManager {
private RouterContext _context;
private Log _log;
private UDPTransport _transport;
private PacketBuilder _builder;
/** map of host+port (String) to InboundEstablishState */
private Map _inboundStates;
/** map of host+port (String) to OutboundEstablishState */
private Map _outboundStates;
private boolean _alive;
private Object _activityLock;
private int _activity;
public EstablishmentManager(RouterContext ctx, UDPTransport transport) {
_context = ctx;
_log = ctx.logManager().getLog(EstablishmentManager.class);
_transport = transport;
_builder = new PacketBuilder(ctx, _transport);
_inboundStates = new HashMap(32);
_outboundStates = new HashMap(32);
_activityLock = new Object();
}
public void startup() {
_alive = true;
I2PThread t = new I2PThread(new Establisher(), "UDP Establisher");
t.setDaemon(true);
t.start();
}
public void shutdown() {
_alive = false;
notifyActivity();
}
/**
* Grab the active establishing state
*/
InboundEstablishState getInboundState(InetAddress fromHost, int fromPort) {
String from = PeerState.calculateRemoteHostString(fromHost.getAddress(), fromPort);
synchronized (_inboundStates) {
InboundEstablishState state = (InboundEstablishState)_inboundStates.get(from);
if ( (state == null) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("No inbound states for " + from + ", with remaining: " + _inboundStates);
return state;
}
}
OutboundEstablishState getOutboundState(InetAddress fromHost, int fromPort) {
String from = PeerState.calculateRemoteHostString(fromHost.getAddress(), fromPort);
synchronized (_outboundStates) {
OutboundEstablishState state = (OutboundEstablishState)_outboundStates.get(from);
if ( (state == null) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("No outbound states for " + from + ", with remaining: " + _outboundStates);
return state;
}
}
/**
* Send the message to its specified recipient by establishing a connection
* with them and sending it off. This call does not block, and on failure,
* the message is failed.
*
*/
public void establish(OutNetMessage msg) {
RouterAddress ra = msg.getTarget().getTargetAddress(_transport.getStyle());
if (ra == null) {
_transport.failed(msg);
return;
}
UDPAddress addr = new UDPAddress(ra);
InetAddress remAddr = addr.getHostAddress();
int port = addr.getPort();
String to = PeerState.calculateRemoteHostString(remAddr.getAddress(), port);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Add outobund establish state to: " + to);
synchronized (_outboundStates) {
OutboundEstablishState state = (OutboundEstablishState)_outboundStates.get(to);
if (state == null) {
state = new OutboundEstablishState(_context, remAddr, port,
msg.getTarget().getIdentity(),
new SessionKey(addr.getIntroKey()));
_outboundStates.put(to, state);
}
state.addMessage(msg);
}
notifyActivity();
}
/**
* Got a SessionRequest (initiates an inbound establishment)
*
*/
void receiveSessionRequest(String from, InetAddress host, int port, UDPPacketReader reader) {
InboundEstablishState state = null;
synchronized (_inboundStates) {
state = (InboundEstablishState)_inboundStates.get(from);
if (state == null) {
state = new InboundEstablishState(_context, host, port, _transport.getLocalPort());
_inboundStates.put(from, state);
}
}
state.receiveSessionRequest(reader.getSessionRequestReader());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive session request from: " + state.getRemoteHostInfo());
notifyActivity();
}
/**
* got a SessionConfirmed (should only happen as part of an inbound
* establishment)
*/
void receiveSessionConfirmed(String from, UDPPacketReader reader) {
InboundEstablishState state = null;
synchronized (_inboundStates) {
state = (InboundEstablishState)_inboundStates.get(from);
}
if (state != null) {
state.receiveSessionConfirmed(reader.getSessionConfirmedReader());
notifyActivity();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive session confirmed from: " + state.getRemoteHostInfo());
}
}
/**
* Got a SessionCreated (in response to our outbound SessionRequest)
*
*/
void receiveSessionCreated(String from, UDPPacketReader reader) {
OutboundEstablishState state = null;
synchronized (_outboundStates) {
state = (OutboundEstablishState)_outboundStates.get(from);
}
if (state != null) {
state.receiveSessionCreated(reader.getSessionCreatedReader());
notifyActivity();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive session created from: " + state.getRemoteHostInfo());
}
}
/**
* A data packet arrived on an outbound connection being established, which
* means its complete (yay!). This is a blocking call, more than I'd like...
*
*/
PeerState receiveData(OutboundEstablishState state) {
state.dataReceived();
synchronized (_outboundStates) {
_outboundStates.remove(state.getRemoteHostInfo());
}
if (_log.shouldLog(Log.INFO))
_log.info("Outbound established completely! yay");
PeerState peer = handleCompletelyEstablished(state);
notifyActivity();
return peer;
}
private void notifyActivity() {
synchronized (_activityLock) {
_activity++;
_activityLock.notifyAll();
}
}
/** kill any inbound or outbound that takes more than 30s */
private static final int MAX_ESTABLISH_TIME = 30*1000;
/**
* ok, fully received, add it to the established cons and queue up a
* netDb store to them
*
*/
private void handleCompletelyEstablished(InboundEstablishState state) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handle completely established (inbound): " + state.getRemoteHostInfo());
long now = _context.clock().now();
RouterIdentity remote = state.getConfirmedIdentity();
PeerState peer = new PeerState(_context);
peer.setCurrentCipherKey(state.getCipherKey());
peer.setCurrentMACKey(state.getMACKey());
peer.setCurrentReceiveSecond(now - (now % 1000));
peer.setKeyEstablishedTime(now);
peer.setLastReceiveTime(now);
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());
_transport.addRemotePeerState(peer);
sendOurInfo(peer);
}
/**
* ok, fully received, add it to the established cons and send any
* queued messages
*
*/
private PeerState handleCompletelyEstablished(OutboundEstablishState state) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handle completely established (outbound): " + state.getRemoteHostInfo());
long now = _context.clock().now();
RouterIdentity remote = state.getRemoteIdentity();
PeerState peer = new PeerState(_context);
peer.setCurrentCipherKey(state.getCipherKey());
peer.setCurrentMACKey(state.getMACKey());
peer.setCurrentReceiveSecond(now - (now % 1000));
peer.setKeyEstablishedTime(now);
peer.setLastReceiveTime(now);
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);
_transport.addRemotePeerState(peer);
sendOurInfo(peer);
while (true) {
OutNetMessage msg = state.getNextQueuedMessage();
if (msg == null)
break;
_transport.send(msg);
}
return peer;
}
private void sendOurInfo(PeerState peer) {
if (_log.shouldLog(Log.INFO))
_log.info("Publishing to the peer after confirm: " + peer);
DatabaseStoreMessage m = new DatabaseStoreMessage(_context);
m.setKey(_context.routerHash());
m.setRouterInfo(_context.router().getRouterInfo());
m.setMessageExpiration(_context.clock().now() + 10*1000);
_transport.send(m, peer);
}
private void sendCreated(InboundEstablishState state) {
long now = _context.clock().now();
if (true) // for now, don't offer to relay
state.setSentRelayTag(0);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send created to: " + state.getRemoteHostInfo());
state.generateSessionKey();
_transport.send(_builder.buildSessionCreatedPacket(state));
// if they haven't advanced to sending us confirmed packets in 5s,
// repeat
state.setNextSendTime(now + 5*1000);
}
private void sendRequest(OutboundEstablishState state) {
long now = _context.clock().now();
state.prepareSessionRequest();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send request to: " + state.getRemoteHostInfo());
_transport.send(_builder.buildSessionRequestPacket(state));
state.requestSent();
}
private void sendConfirmation(OutboundEstablishState state) {
long now = _context.clock().now();
boolean valid = state.validateSessionCreated();
if (!valid) // validate clears fields on failure
return;
// gives us the opportunity to "detect" our external addr
_transport.externalAddressReceived(state.getReceivedIP(), state.getReceivedPort());
// signs if we havent signed yet
state.prepareSessionConfirmed();
UDPPacket packets[] = _builder.buildSessionConfirmedPackets(state);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send confirm to: " + state.getRemoteHostInfo());
for (int i = 0; i < packets.length; i++)
_transport.send(packets[i]);
state.confirmedPacketsSent();
}
/**
* Drive through the inbound establishment states, adjusting one of them
* as necessary
*/
private long handleInbound() {
long now = _context.clock().now();
long nextSendTime = -1;
InboundEstablishState inboundState = null;
synchronized (_inboundStates) {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("# inbound states: " + _inboundStates.size());
for (Iterator iter = _inboundStates.values().iterator(); iter.hasNext(); ) {
InboundEstablishState cur = (InboundEstablishState)iter.next();
if (cur.getState() == InboundEstablishState.STATE_CONFIRMED_COMPLETELY) {
// completely received (though the signature may be invalid)
iter.remove();
inboundState = cur;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Removing completely confirmed inbound state");
break;
} else if (cur.getLifetime() > MAX_ESTABLISH_TIME) {
// took too long, fuck 'em
iter.remove();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Removing expired inbound state");
} else {
if (cur.getNextSendTime() <= now) {
// our turn...
inboundState = cur;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Processing inbound that wanted activity");
break;
} else {
// nothin to do but wait for them to send us
// stuff, so lets move on to the next one being
// established
long when = -1;
if (cur.getNextSendTime() <= 0) {
when = cur.getEstablishBeginTime() + MAX_ESTABLISH_TIME;
} else {
when = cur.getNextSendTime();
}
if (when < nextSendTime)
nextSendTime = when;
}
}
}
}
if (inboundState != null) {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Processing for inbound: " + inboundState);
switch (inboundState.getState()) {
case InboundEstablishState.STATE_REQUEST_RECEIVED:
sendCreated(inboundState);
break;
case InboundEstablishState.STATE_CREATED_SENT: // fallthrough
case InboundEstablishState.STATE_CONFIRMED_PARTIALLY:
// if its been 5s since we sent the SessionCreated, resend
if (inboundState.getNextSendTime() <= now)
sendCreated(inboundState);
break;
case InboundEstablishState.STATE_CONFIRMED_COMPLETELY:
if (inboundState.getConfirmedIdentity() != null) {
handleCompletelyEstablished(inboundState);
break;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("why are we confirmed with no identity? " + inboundState);
break;
}
case InboundEstablishState.STATE_UNKNOWN: // fallthrough
default:
// wtf
if (_log.shouldLog(Log.ERROR))
_log.error("hrm, state is unknown for " + inboundState);
}
// ok, since there was something to do, we want to loop again
nextSendTime = now;
}
return nextSendTime;
}
/**
* Drive through the outbound establishment states, adjusting one of them
* as necessary
*/
private long handleOutbound() {
long now = _context.clock().now();
long nextSendTime = -1;
OutboundEstablishState outboundState = null;
synchronized (_outboundStates) {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("# outbound states: " + _outboundStates.size());
for (Iterator iter = _outboundStates.values().iterator(); iter.hasNext(); ) {
OutboundEstablishState cur = (OutboundEstablishState)iter.next();
if (cur.getState() == OutboundEstablishState.STATE_CONFIRMED_COMPLETELY) {
// completely received
iter.remove();
outboundState = cur;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Removing confirmed outbound: " + cur);
break;
} else if (cur.getLifetime() > MAX_ESTABLISH_TIME) {
// took too long, fuck 'em
iter.remove();
outboundState = cur;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Removing expired outbound: " + cur);
break;
} else {
if (cur.getNextSendTime() <= now) {
// our turn...
outboundState = cur;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Outbound wants activity: " + cur);
break;
} else {
// nothin to do but wait for them to send us
// stuff, so lets move on to the next one being
// established
long when = -1;
if (cur.getNextSendTime() <= 0) {
when = cur.getEstablishBeginTime() + MAX_ESTABLISH_TIME;
} else {
when = cur.getNextSendTime();
}
if ( (nextSendTime <= 0) || (when < nextSendTime) )
nextSendTime = when;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Outbound doesn't want activity: " + cur + " (next=" + (when-now) + ")");
}
}
}
}
if (outboundState != null) {
if (outboundState.getLifetime() > MAX_ESTABLISH_TIME) {
if (outboundState.getState() != OutboundEstablishState.STATE_CONFIRMED_COMPLETELY) {
while (true) {
OutNetMessage msg = outboundState.getNextQueuedMessage();
if (msg == null)
break;
_transport.failed(msg);
}
_context.shitlist().shitlistRouter(outboundState.getRemoteIdentity().calculateHash(), "Unable to establish");
} else {
while (true) {
OutNetMessage msg = outboundState.getNextQueuedMessage();
if (msg == null)
break;
_transport.send(msg);
}
}
} else {
switch (outboundState.getState()) {
case OutboundEstablishState.STATE_UNKNOWN:
sendRequest(outboundState);
break;
case OutboundEstablishState.STATE_REQUEST_SENT:
// no response yet (or it was invalid), lets retry
if (outboundState.getNextSendTime() <= now)
sendRequest(outboundState);
break;
case OutboundEstablishState.STATE_CREATED_RECEIVED: // fallthrough
case OutboundEstablishState.STATE_CONFIRMED_PARTIALLY:
if (outboundState.getNextSendTime() <= now)
sendConfirmation(outboundState);
break;
case OutboundEstablishState.STATE_CONFIRMED_COMPLETELY:
handleCompletelyEstablished(outboundState);
break;
default:
// wtf
}
}
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Since something happened outbound, next=now");
// ok, since there was something to do, we want to loop again
nextSendTime = now;
} else {
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Nothing happened outbound, next is in " + (nextSendTime-now));
}
return nextSendTime;
}
/**
* Driving thread, processing up to one step for an inbound peer and up to
* one step for an outbound peer. This is prodded whenever any peer's state
* changes as well.
*
*/
private class Establisher implements Runnable {
public void run() {
while (_alive) {
_activity = 0;
long now = _context.clock().now();
long nextSendTime = -1;
long nextSendInbound = handleInbound();
long nextSendOutbound = handleOutbound();
if (nextSendInbound > 0)
nextSendTime = nextSendInbound;
if ( (nextSendTime < 0) || (nextSendOutbound < nextSendTime) )
nextSendTime = nextSendOutbound;
long delay = nextSendTime - now;
if ( (nextSendTime == -1) || (delay > 0) ) {
boolean interrupted = false;
try {
synchronized (_activityLock) {
if (_activity > 0)
continue;
if (nextSendTime == -1)
_activityLock.wait();
else
_activityLock.wait(delay);
}
} catch (InterruptedException ie) {
interrupted = true;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("After waiting w/ nextSend=" + nextSendTime
+ " and delay=" + delay + " and interrupted=" + interrupted);
}
}
}
}
}

View File

@ -0,0 +1,308 @@
package net.i2p.router.transport.udp;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import net.i2p.crypto.DHSessionKeyBuilder;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterIdentity;
import net.i2p.data.SessionKey;
import net.i2p.data.Signature;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
* Data for a new connection being established, where the remote peer has
* initiated the connection with us. In other words, they are Alice and
* we are Bob.
*
*/
public class InboundEstablishState {
private RouterContext _context;
private Log _log;
// SessionRequest message
private byte _receivedX[];
private byte _bobIP[];
private int _bobPort;
private DHSessionKeyBuilder _keyBuilder;
// SessionCreated message
private byte _sentY[];
private byte _aliceIP[];
private int _alicePort;
private long _sentRelayTag;
private long _sentSignedOnTime;
private SessionKey _sessionKey;
private SessionKey _macKey;
private Signature _sentSignature;
// SessionConfirmed messages
private byte _receivedIdentity[][];
private long _receivedSignedOnTime;
private byte _receivedSignature[];
private boolean _verificationAttempted;
private RouterIdentity _receivedConfirmedIdentity;
// general status
private long _establishBegin;
private long _lastReceive;
private long _lastSend;
private long _nextSend;
private String _remoteHostInfo;
private int _currentState;
/** nothin known yet */
public static final int STATE_UNKNOWN = 0;
/** we have received an initial request */
public static final int STATE_REQUEST_RECEIVED = 1;
/** we have sent a signed creation packet */
public static final int STATE_CREATED_SENT = 2;
/** we have received one or more confirmation packets */
public static final int STATE_CONFIRMED_PARTIALLY = 3;
/** we have completely received all of the confirmation packets */
public static final int STATE_CONFIRMED_COMPLETELY = 4;
public InboundEstablishState(RouterContext ctx, InetAddress remoteHost, int remotePort, int localPort) {
_context = ctx;
_log = ctx.logManager().getLog(InboundEstablishState.class);
_aliceIP = remoteHost.getAddress();
_alicePort = remotePort;
_remoteHostInfo = PeerState.calculateRemoteHostString(_aliceIP, _alicePort);
_bobPort = localPort;
_keyBuilder = null;
_verificationAttempted = false;
_currentState = STATE_UNKNOWN;
_establishBegin = ctx.clock().now();
}
public synchronized int getState() { return _currentState; }
public synchronized void receiveSessionRequest(UDPPacketReader.SessionRequestReader req) {
if (_receivedX == null)
_receivedX = new byte[UDPPacketReader.SessionRequestReader.X_LENGTH];
req.readX(_receivedX, 0);
if (_bobIP == null)
_bobIP = new byte[req.readIPSize()];
req.readIP(_bobIP, 0);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive sessionRequest, BobIP = " + Base64.encode(_bobIP));
if (_currentState == STATE_UNKNOWN)
_currentState = STATE_REQUEST_RECEIVED;
packetReceived();
}
public synchronized boolean sessionRequestReceived() { return _receivedX != null; }
public synchronized byte[] getReceivedX() { return _receivedX; }
public synchronized byte[] getReceivedOurIP() { return _bobIP; }
public synchronized void generateSessionKey() {
if (_sessionKey != null) return;
_keyBuilder = new DHSessionKeyBuilder();
_keyBuilder.setPeerPublicValue(_receivedX);
_sessionKey = _keyBuilder.getSessionKey();
ByteArray extra = _keyBuilder.getExtraBytes();
_macKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
System.arraycopy(extra.getData(), 0, _macKey.getData(), 0, SessionKey.KEYSIZE_BYTES);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Established inbound keys. cipher: " + Base64.encode(_sessionKey.getData())
+ " mac: " + Base64.encode(_macKey.getData()));
}
public synchronized SessionKey getCipherKey() { return _sessionKey; }
public synchronized SessionKey getMACKey() { return _macKey; }
/** what IP do they appear to be on? */
public synchronized byte[] getSentIP() { return _aliceIP; }
/** what port number do they appear to be coming from? */
public synchronized int getSentPort() { return _alicePort; }
public synchronized byte[] getSentY() {
if (_sentY == null)
_sentY = _keyBuilder.getMyPublicValueBytes();
return _sentY;
}
public synchronized long getSentRelayTag() { return _sentRelayTag; }
public synchronized void setSentRelayTag(long tag) { _sentRelayTag = tag; }
public synchronized long getSentSignedOnTime() { return _sentSignedOnTime; }
public synchronized void prepareSessionCreated() {
if (_sentSignature == null) signSessionCreated();
}
public synchronized Signature getSentSignature() { return _sentSignature; }
/**
* Sign: Alice's IP + Alice's port + Bob's IP + Bob's port + Alice's
* new relay tag + Bob's signed on time
*/
private void signSessionCreated() {
byte signed[] = new byte[_aliceIP.length + 2
+ _bobIP.length + 2
+ 4 // sent relay tag
+ 4 // signed on time
];
_sentSignedOnTime = _context.clock().now() / 1000;
int off = 0;
System.arraycopy(_aliceIP, 0, signed, off, _aliceIP.length);
off += _aliceIP.length;
DataHelper.toLong(signed, off, 2, _alicePort);
off += 2;
System.arraycopy(_bobIP, 0, signed, off, _bobIP.length);
off += _bobIP.length;
DataHelper.toLong(signed, off, 2, _bobPort);
off += 2;
DataHelper.toLong(signed, off, 4, _sentRelayTag);
off += 4;
DataHelper.toLong(signed, off, 4, _sentSignedOnTime);
_sentSignature = _context.dsa().sign(signed, _context.keyManager().getSigningPrivateKey());
if (_log.shouldLog(Log.DEBUG)) {
StringBuffer buf = new StringBuffer(128);
buf.append("Signing sessionCreated:");
buf.append(" AliceIP: ").append(Base64.encode(_aliceIP));
buf.append(" AlicePort: ").append(_alicePort);
buf.append(" BobIP: ").append(Base64.encode(_bobIP));
buf.append(" BobPort: ").append(_bobPort);
buf.append(" RelayTag: ").append(_sentRelayTag);
buf.append(" SignedOn: ").append(_sentSignedOnTime);
buf.append(" signature: ").append(Base64.encode(_sentSignature.getData()));
_log.debug(buf.toString());
}
}
/** note that we just sent a SessionCreated packet */
public synchronized void createdPacketSent() {
_lastSend = _context.clock().now();
if ( (_currentState == STATE_UNKNOWN) || (_currentState == STATE_REQUEST_RECEIVED) )
_currentState = STATE_CREATED_SENT;
}
/** how long have we been trying to establish this session? */
public synchronized long getLifetime() { return _context.clock().now() - _establishBegin; }
public synchronized long getEstablishBeginTime() { return _establishBegin; }
public synchronized long getNextSendTime() { return _nextSend; }
public synchronized void setNextSendTime(long when) { _nextSend = when; }
/** host+port, uniquely identifies an attempt */
public String getRemoteHostInfo() { return _remoteHostInfo; }
public synchronized void receiveSessionConfirmed(UDPPacketReader.SessionConfirmedReader conf) {
if (_receivedIdentity == null)
_receivedIdentity = new byte[conf.readTotalFragmentNum()][];
int cur = conf.readCurrentFragmentNum();
if (_receivedIdentity[cur] == null) {
byte fragment[] = new byte[conf.readCurrentFragmentSize()];
conf.readFragmentData(fragment, 0);
_receivedIdentity[cur] = fragment;
}
if (cur == _receivedIdentity.length-1) {
_receivedSignedOnTime = conf.readFinalFragmentSignedOnTime();
if (_receivedSignature == null)
_receivedSignature = new byte[Signature.SIGNATURE_BYTES];
conf.readFinalSignature(_receivedSignature, 0);
}
if ( (_currentState == STATE_UNKNOWN) ||
(_currentState == STATE_REQUEST_RECEIVED) ||
(_currentState == STATE_CREATED_SENT) ) {
if (confirmedFullyReceived())
_currentState = STATE_CONFIRMED_COMPLETELY;
else
_currentState = STATE_CONFIRMED_PARTIALLY;
}
packetReceived();
}
/** have we fully received the SessionConfirmed messages from Alice? */
public synchronized boolean confirmedFullyReceived() {
if (_receivedIdentity != null) {
for (int i = 0; i < _receivedIdentity.length; i++)
if (_receivedIdentity[i] == null)
return false;
return true;
} else {
return false;
}
}
/**
* Who is Alice (null if forged/unknown)
*/
public synchronized RouterIdentity getConfirmedIdentity() {
if (!_verificationAttempted) {
verifyIdentity();
_verificationAttempted = true;
}
return _receivedConfirmedIdentity;
}
/**
* Determine if Alice sent us a valid confirmation packet. The
* identity signs: Alice's IP + Alice's port + Bob's IP + Bob's port
* + Alice's new relay key + Alice's signed on time
*/
private synchronized void verifyIdentity() {
int identSize = 0;
for (int i = 0; i < _receivedIdentity.length; i++)
identSize += _receivedIdentity[i].length;
byte ident[] = new byte[identSize];
int off = 0;
for (int i = 0; i < _receivedIdentity.length; i++) {
int len = _receivedIdentity[i].length;
System.arraycopy(_receivedIdentity[i], 0, ident, off, len);
off += len;
}
ByteArrayInputStream in = new ByteArrayInputStream(ident);
RouterIdentity peer = new RouterIdentity();
try {
peer.readBytes(in);
byte signed[] = new byte[_aliceIP.length + 2
+ _bobIP.length + 2
+ 4 // Alice's relay key
+ 4 // signed on time
];
off = 0;
System.arraycopy(_aliceIP, 0, signed, off, _aliceIP.length);
off += _aliceIP.length;
DataHelper.toLong(signed, off, 2, _alicePort);
off += 2;
System.arraycopy(_bobIP, 0, signed, off, _bobIP.length);
off += _bobIP.length;
DataHelper.toLong(signed, off, 2, _bobPort);
off += 2;
DataHelper.toLong(signed, off, 4, _sentRelayTag);
off += 4;
DataHelper.toLong(signed, off, 4, _receivedSignedOnTime);
Signature sig = new Signature(_receivedSignature);
boolean ok = _context.dsa().verifySignature(sig, signed, peer.getSigningPublicKey());
if (ok) {
_receivedConfirmedIdentity = peer;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Signature failed from " + peer);
}
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Improperly formatted yet fully received ident", dfe);
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Improperly formatted yet fully received ident", ioe);
}
}
private void packetReceived() {
_lastReceive = _context.clock().now();
_nextSend = _lastReceive;
}
}

View File

@ -0,0 +1,228 @@
package net.i2p.router.transport.udp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Organize the received data message fragments, allowing its
* {@link MessageReceiver} to pull off completed messages and its
* {@link ACKSender} to pull off peers who need to receive an ACK for
* these messages. In addition, it drops failed fragments and keeps a
* minimal list of the most recently completed messages (even though higher
* up in the router we have full blown replay detection, its nice to have a
* basic line of defense here)
*
*/
public class InboundMessageFragments {
private RouterContext _context;
private Log _log;
/** Map of peer (Hash) to a Map of messageId (Long) to InboundMessageState objects */
private Map _inboundMessages;
/** list of peers (PeerState) who we have received data from but not yet ACKed to */
private List _unsentACKs;
/** list of messages (InboundMessageState) fully received but not interpreted yet */
private List _completeMessages;
/** list of message IDs (Long) recently received, so we can ignore in flight dups */
private List _recentlyCompletedMessages;
private OutboundMessageFragments _outbound;
private UDPTransport _transport;
/** this can be broken down further, but to start, OneBigLock does the trick */
private Object _stateLock;
private boolean _alive;
private static final int RECENTLY_COMPLETED_SIZE = 100;
/** how frequently do we want to send ACKs to a peer? */
private static final int ACK_FREQUENCY = 100;
public InboundMessageFragments(RouterContext ctx, OutboundMessageFragments outbound, UDPTransport transport) {
_context = ctx;
_log = ctx.logManager().getLog(InboundMessageFragments.class);
_inboundMessages = new HashMap(64);
_unsentACKs = new ArrayList(64);
_completeMessages = new ArrayList(64);
_recentlyCompletedMessages = new ArrayList(RECENTLY_COMPLETED_SIZE);
_outbound = outbound;
_transport = transport;
_context.statManager().createRateStat("udp.receivedCompleteTime", "How long it takes to receive a full message", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.receivedCompleteFragments", "How many fragments go in a fully received message", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.receivedACKs", "How many messages were ACKed at a time", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.ignoreRecentDuplicate", "Take note that we received a packet for a recently completed message", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.receiveMessagePeriod", "How long it takes to pull the message fragments out of a packet", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.receiveACKPeriod", "How long it takes to pull the ACKs out of a packet", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_stateLock = this;
}
public void startup() {
_alive = true;
I2PThread t = new I2PThread(new ACKSender(_context, this, _transport), "UDP ACK sender");
t.setDaemon(true);
t.start();
t = new I2PThread(new MessageReceiver(_context, this, _transport), "UDP message receiver");
t.setDaemon(true);
t.start();
}
public void shutdown() {
_alive = false;
synchronized (_stateLock) {
_completeMessages.clear();
_unsentACKs.clear();
_inboundMessages.clear();
_stateLock.notifyAll();
}
}
public boolean isAlive() { return _alive; }
/**
* Pull the fragments and ACKs out of the authenticated data packet
*/
public void receiveData(PeerState from, UDPPacketReader.DataReader data) {
long beforeMsgs = _context.clock().now();
receiveMessages(from, data);
long afterMsgs = _context.clock().now();
receiveACKs(from, data);
long afterACKs = _context.clock().now();
_context.statManager().addRateData("udp.receiveMessagePeriod", afterMsgs-beforeMsgs, afterACKs-beforeMsgs);
_context.statManager().addRateData("udp.receiveACKPeriod", afterACKs-afterMsgs, afterACKs-beforeMsgs);
}
/**
* Pull out all the data fragments and shove them into InboundMessageStates.
* Along the way, if any state expires, or a full message arrives, move it
* appropriately.
*
*/
private void receiveMessages(PeerState from, UDPPacketReader.DataReader data) {
int fragments = data.readFragmentCount();
if (fragments <= 0) return;
synchronized (_stateLock) {
Map messages = (Map)_inboundMessages.get(from.getRemotePeer());
if (messages == null) {
messages = new HashMap(fragments);
_inboundMessages.put(from.getRemotePeer(), messages);
}
for (int i = 0; i < fragments; i++) {
Long messageId = new Long(data.readMessageId(i));
if (_recentlyCompletedMessages.contains(messageId)) {
_context.statManager().addRateData("udp.ignoreRecentDuplicate", 1, 0);
continue;
}
int size = data.readMessageFragmentSize(i);
InboundMessageState state = null;
boolean messageComplete = false;
boolean messageExpired = false;
boolean fragmentOK = false;
state = (InboundMessageState)messages.get(messageId);
if (state == null) {
state = new InboundMessageState(_context, messageId.longValue(), from.getRemotePeer());
messages.put(messageId, state);
}
fragmentOK = state.receiveFragment(data, i);
if (state.isComplete()) {
messageComplete = true;
messages.remove(messageId);
while (_recentlyCompletedMessages.size() >= RECENTLY_COMPLETED_SIZE)
_recentlyCompletedMessages.remove(0);
_recentlyCompletedMessages.add(messageId);
_completeMessages.add(state);
from.messageFullyReceived(messageId);
if (!_unsentACKs.contains(from))
_unsentACKs.add(from);
if (_log.shouldLog(Log.INFO))
_log.info("Message received completely! " + state);
_context.statManager().addRateData("udp.receivedCompleteTime", state.getLifetime(), state.getLifetime());
_context.statManager().addRateData("udp.receivedCompleteFragments", state.getFragmentCount(), state.getLifetime());
_stateLock.notifyAll();
} else if (state.isExpired()) {
messageExpired = true;
messages.remove(messageId);
if (_log.shouldLog(Log.WARN))
_log.warn("Message expired while only being partially read: " + state);
state.releaseResources();
}
if (!fragmentOK)
break;
}
}
}
private void receiveACKs(PeerState from, UDPPacketReader.DataReader data) {
if (data.readACKsIncluded()) {
int fragments = 0;
long acks[] = data.readACKs();
_context.statManager().addRateData("udp.receivedACKs", acks.length, 0);
for (int i = 0; i < acks.length; i++) {
if (_log.shouldLog(Log.INFO))
_log.info("Full ACK of message " + acks[i] + " received!");
fragments += _outbound.acked(acks[i], from.getRemotePeer());
}
from.messageACKed(fragments * from.getMTU()); // estimated size
}
if (data.readECN())
from.ECNReceived();
else
from.dataReceived();
}
/**
* Blocking call to pull off the next fully received message
*
*/
public InboundMessageState receiveNextMessage() {
while (_alive) {
try {
synchronized (_stateLock) {
if (_completeMessages.size() > 0)
return (InboundMessageState)_completeMessages.remove(0);
_stateLock.wait();
}
} catch (InterruptedException ie) {}
}
return null;
}
/**
* Pull off the peer who we next want to send ACKs/NACKs to.
* This call blocks, and only returns null on shutdown.
*
*/
public PeerState getNextPeerToACK() {
while (_alive) {
try {
long now = _context.clock().now();
synchronized (_stateLock) {
for (int i = 0; i < _unsentACKs.size(); i++) {
PeerState peer = (PeerState)_unsentACKs.get(i);
if (peer.getLastACKSend() + ACK_FREQUENCY <= now) {
_unsentACKs.remove(i);
peer.setLastACKSend(now);
return peer;
}
}
if (_unsentACKs.size() > 0)
_stateLock.wait(_context.random().nextInt(100));
else
_stateLock.wait();
}
} catch (InterruptedException ie) {}
}
return null;
}
}

View File

@ -0,0 +1,112 @@
package net.i2p.router.transport.udp;
import net.i2p.data.ByteArray;
import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**
* Hold the raw data fragments of an inbound message
*
*/
public class InboundMessageState {
private RouterContext _context;
private Log _log;
private long _messageId;
private Hash _from;
/**
* indexed array of fragments for the message, where not yet
* received fragments are null.
*/
private ByteArray _fragments[];
/**
* what is the last fragment in the message (or -1 if not yet known)
*/
private int _lastFragment;
private long _receiveBegin;
/** expire after 30s */
private static final long MAX_RECEIVE_TIME = 30*1000;
private static final int MAX_FRAGMENTS = 32;
private static final ByteCache _fragmentCache = ByteCache.getInstance(64, 2048);
public InboundMessageState(RouterContext ctx, long messageId, Hash from) {
_context = ctx;
_log = ctx.logManager().getLog(InboundMessageState.class);
_messageId = messageId;
_from = from;
_fragments = new ByteArray[MAX_FRAGMENTS];
_lastFragment = -1;
_receiveBegin = ctx.clock().now();
}
/**
* Read in the data from the fragment.
*
* @return true if the data was ok, false if it was corrupt
*/
public synchronized boolean receiveFragment(UDPPacketReader.DataReader data, int dataFragment) {
int fragmentNum = data.readMessageFragmentNum(dataFragment);
if ( (fragmentNum < 0) || (fragmentNum > _fragments.length)) {
_log.log(Log.CRIT, "Invalid fragment " + fragmentNum + ": " + data, new Exception("source"));
return false;
}
if (_fragments[fragmentNum] == null) {
// new fragment, read it
ByteArray message = _fragmentCache.acquire();
data.readMessageFragment(dataFragment, message.getData(), 0);
int size = data.readMessageFragmentSize(dataFragment);
message.setValid(size);
_fragments[fragmentNum] = message;
if (data.readMessageIsLast(dataFragment))
_lastFragment = fragmentNum;
}
return true;
}
public synchronized boolean isComplete() {
if (_lastFragment < 0) return false;
for (int i = 0; i <= _lastFragment; i++)
if (_fragments[i] == null)
return false;
return true;
}
public synchronized boolean isExpired() {
return _context.clock().now() > _receiveBegin + MAX_RECEIVE_TIME;
}
public long getLifetime() {
return _context.clock().now() - _receiveBegin;
}
public Hash getFrom() { return _from; }
public long getMessageId() { return _messageId; }
public synchronized int getCompleteSize() {
int size = 0;
for (int i = 0; i <= _lastFragment; i++)
size += _fragments[i].getValid();
return size;
}
public void releaseResources() {
if (_fragments != null)
for (int i = 0; i < _fragments.length; i++)
_fragmentCache.release(_fragments[i]);
_fragments = null;
}
public ByteArray[] getFragments() {
return _fragments;
}
public int getFragmentCount() { return _lastFragment+1; }
public String toString() {
StringBuffer buf = new StringBuffer(32);
buf.append("Message: ").append(_messageId);
if (isComplete()) {
buf.append(" completely received with ");
buf.append(getCompleteSize()).append(" bytes");
}
return buf.toString();
}
}

View File

@ -0,0 +1,20 @@
package net.i2p.router.transport.udp;
import net.i2p.router.OutNetMessage;
/**
* Base queue for messages not yet packetized
*/
public interface MessageQueue {
/**
* Get the next message, blocking until one is found or the expiration
* reached.
*
* @param blockUntil expiration, or -1 if indefinite
*/
public OutNetMessage getNext(long blockUntil);
/**
* Add on a new message to the queue
*/
public void add(OutNetMessage message);
}

View File

@ -0,0 +1,74 @@
package net.i2p.router.transport.udp;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataFormatException;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.data.i2np.I2NPMessageImpl;
import net.i2p.data.i2np.I2NPMessageException;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
* Pull fully completed fragments off the {@link InboundMessageFragments} queue,
* parse 'em into I2NPMessages, and stick them on the
* {@link net.i2p.router.InNetMessagePool} by way of the {@link UDPTransport}.
*/
public class MessageReceiver implements Runnable {
private RouterContext _context;
private Log _log;
private InboundMessageFragments _fragments;
private UDPTransport _transport;
public MessageReceiver(RouterContext ctx, InboundMessageFragments frag, UDPTransport transport) {
_context = ctx;
_log = ctx.logManager().getLog(MessageReceiver.class);
_fragments = frag;
_transport = transport;
}
public void run() {
while (_fragments.isAlive()) {
InboundMessageState message = _fragments.receiveNextMessage();
if (message == null) continue;
int size = message.getCompleteSize();
if (_log.shouldLog(Log.INFO))
_log.info("Full message received (" + message.getMessageId() + ") after " + message.getLifetime()
+ "... todo: parse and plop it onto InNetMessagePool");
I2NPMessage msg = readMessage(message);
if (msg != null)
_transport.messageReceived(msg, null, message.getFrom(), message.getLifetime(), size);
}
}
private I2NPMessage readMessage(InboundMessageState state) {
try {
byte buf[] = new byte[state.getCompleteSize()];
ByteArray fragments[] = state.getFragments();
int numFragments = state.getFragmentCount();
int off = 0;
for (int i = 0; i < numFragments; i++) {
System.arraycopy(fragments[i].getData(), 0, buf, off, fragments[i].getValid());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Raw fragment[" + i + "] for " + state.getMessageId() + ": "
+ Base64.encode(fragments[i].getData(), 0, fragments[i].getValid()));
off += fragments[i].getValid();
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Raw byte array for " + state.getMessageId() + ": " + Base64.encode(buf));
I2NPMessage m = I2NPMessageImpl.fromRawByteArray(_context, buf, 0, buf.length);
m.setUniqueId(state.getMessageId());
return m;
} catch (I2NPMessageException ime) {
if (_log.shouldLog(Log.WARN))
_log.warn("Message invalid: " + state, ime);
return null;
} catch (Exception e) {
_log.log(Log.CRIT, "Error dealing with a message: " + state, e);
return null;
} finally {
state.releaseResources();
}
}
}

View File

@ -0,0 +1,349 @@
package net.i2p.router.transport.udp;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import net.i2p.crypto.DHSessionKeyBuilder;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterIdentity;
import net.i2p.data.SessionKey;
import net.i2p.data.Signature;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
* Data for a new connection being established, where we initiated the
* connection with a remote peer. In other words, we are Alice and
* they are Bob.
*
*/
public class OutboundEstablishState {
private RouterContext _context;
private Log _log;
// SessionRequest message
private byte _sentX[];
private byte _bobIP[];
private int _bobPort;
private DHSessionKeyBuilder _keyBuilder;
// SessionCreated message
private byte _receivedY[];
private byte _aliceIP[];
private int _alicePort;
private long _receivedRelayTag;
private long _receivedSignedOnTime;
private SessionKey _sessionKey;
private SessionKey _macKey;
private Signature _receivedSignature;
private byte[] _receivedEncryptedSignature;
private byte[] _receivedIV;
// SessionConfirmed messages
private long _sentSignedOnTime;
private Signature _sentSignature;
// general status
private long _establishBegin;
private long _lastReceive;
private long _lastSend;
private long _nextSend;
private String _remoteHostInfo;
private RouterIdentity _remotePeer;
private SessionKey _introKey;
private List _queuedMessages;
private int _currentState;
/** nothin sent yet */
public static final int STATE_UNKNOWN = 0;
/** we have sent an initial request */
public static final int STATE_REQUEST_SENT = 1;
/** we have received a signed creation packet */
public static final int STATE_CREATED_RECEIVED = 2;
/** we have sent one or more confirmation packets */
public static final int STATE_CONFIRMED_PARTIALLY = 3;
/** we have received a data packet */
public static final int STATE_CONFIRMED_COMPLETELY = 4;
public OutboundEstablishState(RouterContext ctx, InetAddress remoteHost, int remotePort,
RouterIdentity remotePeer, SessionKey introKey) {
_context = ctx;
_log = ctx.logManager().getLog(OutboundEstablishState.class);
_bobIP = remoteHost.getAddress();
_bobPort = remotePort;
_remoteHostInfo = PeerState.calculateRemoteHostString(_bobIP, _bobPort);
_remotePeer = remotePeer;
_introKey = introKey;
_keyBuilder = null;
_queuedMessages = new ArrayList(4);
_currentState = STATE_UNKNOWN;
_establishBegin = ctx.clock().now();
}
public synchronized int getState() { return _currentState; }
public void addMessage(OutNetMessage msg) {
synchronized (_queuedMessages) {
_queuedMessages.add(msg);
}
}
public OutNetMessage getNextQueuedMessage() {
synchronized (_queuedMessages) {
if (_queuedMessages.size() > 0)
return (OutNetMessage)_queuedMessages.remove(0);
}
return null;
}
public RouterIdentity getRemoteIdentity() { return _remotePeer; }
public SessionKey getIntroKey() { return _introKey; }
public synchronized void prepareSessionRequest() {
_keyBuilder = new DHSessionKeyBuilder();
byte X[] = _keyBuilder.getMyPublicValue().toByteArray();
if (_sentX == null)
_sentX = new byte[UDPPacketReader.SessionRequestReader.X_LENGTH];
if (X.length == 257)
System.arraycopy(X, 1, _sentX, 0, _sentX.length);
else if (X.length == 256)
System.arraycopy(X, 0, _sentX, 0, _sentX.length);
else
System.arraycopy(X, 0, _sentX, _sentX.length - X.length, X.length);
}
public synchronized byte[] getSentX() { return _sentX; }
public synchronized byte[] getSentIP() { return _bobIP; }
public synchronized int getSentPort() { return _bobPort; }
public synchronized void receiveSessionCreated(UDPPacketReader.SessionCreatedReader reader) {
if (_receivedY != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Session created already received, ignoring");
return; // already received
}
_receivedY = new byte[UDPPacketReader.SessionCreatedReader.Y_LENGTH];
reader.readY(_receivedY, 0);
if (_aliceIP == null)
_aliceIP = new byte[reader.readIPSize()];
reader.readIP(_aliceIP, 0);
_alicePort = reader.readPort();
_receivedRelayTag = reader.readRelayTag();
_receivedSignedOnTime = reader.readSignedOnTime();
_receivedEncryptedSignature = new byte[Signature.SIGNATURE_BYTES + 8];
reader.readEncryptedSignature(_receivedEncryptedSignature, 0);
_receivedIV = new byte[UDPPacket.IV_SIZE];
reader.readIV(_receivedIV, 0);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receive session created:\neSig: " + Base64.encode(_receivedEncryptedSignature)
+ "\nreceivedIV: " + Base64.encode(_receivedIV)
+ "\nAliceIP: " + Base64.encode(_aliceIP)
+ " RelayTag: " + _receivedRelayTag
+ " SignedOn: " + _receivedSignedOnTime
+ "\nthis: " + this.toString());
if ( (_currentState == STATE_UNKNOWN) || (_currentState == STATE_REQUEST_SENT) )
_currentState = STATE_CREATED_RECEIVED;
packetReceived();
}
/**
* Blocking call (run in the establisher thread) to determine if the
* session was created properly. If it wasn't, all the SessionCreated
* remnants are dropped (perhaps they were spoofed, etc) so that we can
* receive another one
*/
public synchronized boolean validateSessionCreated() {
if (_receivedSignature != null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Session created already validated");
return true;
}
generateSessionKey();
decryptSignature();
if (verifySessionCreated()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Session created passed validation");
return true;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Session created failed validation, clearing state");
_receivedY = null;
_aliceIP = null;
_receivedRelayTag = 0;
_receivedSignedOnTime = -1;
_receivedEncryptedSignature = null;
_receivedIV = null;
_receivedSignature = null;
if ( (_currentState == STATE_UNKNOWN) ||
(_currentState == STATE_REQUEST_SENT) ||
(_currentState == STATE_CREATED_RECEIVED) )
_currentState = STATE_REQUEST_SENT;
_nextSend = _context.clock().now();
return false;
}
}
private void generateSessionKey() {
if (_sessionKey != null) return;
_keyBuilder.setPeerPublicValue(_receivedY);
_sessionKey = _keyBuilder.getSessionKey();
ByteArray extra = _keyBuilder.getExtraBytes();
_macKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
System.arraycopy(extra.getData(), 0, _macKey.getData(), 0, SessionKey.KEYSIZE_BYTES);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Established outbound keys. cipher: " + Base64.encode(_sessionKey.getData())
+ " mac: " + Base64.encode(_macKey.getData()));
}
/**
* decrypt the signature (and subsequent pad bytes) with the
* additional layer of encryption using the negotiated key along side
* the packet's IV
*/
private void decryptSignature() {
if (_receivedEncryptedSignature == null) throw new NullPointerException("encrypted signature is null! this=" + this.toString());
else if (_sessionKey == null) throw new NullPointerException("SessionKey is null!");
else if (_receivedIV == null) throw new NullPointerException("IV is null!");
_context.aes().decrypt(_receivedEncryptedSignature, 0, _receivedEncryptedSignature, 0,
_sessionKey, _receivedIV, _receivedEncryptedSignature.length);
byte signatureBytes[] = new byte[Signature.SIGNATURE_BYTES];
System.arraycopy(_receivedEncryptedSignature, 0, signatureBytes, 0, Signature.SIGNATURE_BYTES);
_receivedSignature = new Signature(signatureBytes);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Decrypted received signature: \n" + Base64.encode(signatureBytes));
}
/**
* Verify: Alice's IP + Alice's port + Bob's IP + Bob's port + Alice's
* new relay tag + Bob's signed on time
*/
private boolean verifySessionCreated() {
byte signed[] = new byte[_aliceIP.length + 2
+ _bobIP.length + 2
+ 4 // sent relay tag
+ 4 // signed on time
];
int off = 0;
System.arraycopy(_aliceIP, 0, signed, off, _aliceIP.length);
off += _aliceIP.length;
DataHelper.toLong(signed, off, 2, _alicePort);
off += 2;
System.arraycopy(_bobIP, 0, signed, off, _bobIP.length);
off += _bobIP.length;
DataHelper.toLong(signed, off, 2, _bobPort);
off += 2;
DataHelper.toLong(signed, off, 4, _receivedRelayTag);
off += 4;
DataHelper.toLong(signed, off, 4, _receivedSignedOnTime);
if (_log.shouldLog(Log.DEBUG)) {
StringBuffer buf = new StringBuffer(128);
buf.append("Signed sessionCreated:");
buf.append(" AliceIP: ").append(Base64.encode(_aliceIP));
buf.append(" AlicePort: ").append(_alicePort);
buf.append(" BobIP: ").append(Base64.encode(_bobIP));
buf.append(" BobPort: ").append(_bobPort);
buf.append(" RelayTag: ").append(_receivedRelayTag);
buf.append(" SignedOn: ").append(_receivedSignedOnTime);
buf.append(" signature: ").append(Base64.encode(_receivedSignature.getData()));
_log.debug(buf.toString());
}
return _context.dsa().verifySignature(_receivedSignature, signed, _remotePeer.getSigningPublicKey());
}
public synchronized SessionKey getCipherKey() { return _sessionKey; }
public synchronized SessionKey getMACKey() { return _macKey; }
public synchronized long getReceivedRelayTag() { return _receivedRelayTag; }
public synchronized long getSentSignedOnTime() { return _sentSignedOnTime; }
public synchronized long getReceivedSignedOnTime() { return _receivedSignedOnTime; }
public synchronized byte[] getReceivedIP() { return _aliceIP; }
public synchronized int getReceivedPort() { return _alicePort; }
/**
* Lets sign everything so we can fragment properly
*
*/
public synchronized void prepareSessionConfirmed() {
if (_sentSignedOnTime > 0)
return;
byte signed[] = new byte[_aliceIP.length + 2
+ _bobIP.length + 2
+ 4 // Alice's relay key
+ 4 // signed on time
];
_sentSignedOnTime = _context.clock().now() / 1000;
int off = 0;
System.arraycopy(_aliceIP, 0, signed, off, _aliceIP.length);
off += _aliceIP.length;
DataHelper.toLong(signed, off, 2, _alicePort);
off += 2;
System.arraycopy(_bobIP, 0, signed, off, _bobIP.length);
off += _bobIP.length;
DataHelper.toLong(signed, off, 2, _bobPort);
off += 2;
DataHelper.toLong(signed, off, 4, _receivedRelayTag);
off += 4;
DataHelper.toLong(signed, off, 4, _sentSignedOnTime);
_sentSignature = _context.dsa().sign(signed, _context.keyManager().getSigningPrivateKey());
}
public synchronized Signature getSentSignature() { return _sentSignature; }
/** note that we just sent the SessionConfirmed packet */
public synchronized void confirmedPacketsSent() {
_lastSend = _context.clock().now();
_nextSend = _lastSend + 5*1000;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send confirm packets, nextSend = 5s");
if ( (_currentState == STATE_UNKNOWN) ||
(_currentState == STATE_REQUEST_SENT) ||
(_currentState == STATE_CREATED_RECEIVED) )
_currentState = STATE_CONFIRMED_PARTIALLY;
}
/** note that we just sent the SessionRequest packet */
public synchronized void requestSent() {
_lastSend = _context.clock().now();
_nextSend = _lastSend + 5*1000;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send a request packet, nextSend = 5s");
if (_currentState == STATE_UNKNOWN)
_currentState = STATE_REQUEST_SENT;
}
/** how long have we been trying to establish this session? */
public synchronized long getLifetime() { return _context.clock().now() - _establishBegin; }
public synchronized long getEstablishBeginTime() { return _establishBegin; }
public synchronized long getNextSendTime() { return _nextSend; }
public synchronized void setNextSendTime(long when) {
_nextSend = when;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Explicit nextSend=" + (_nextSend-_context.clock().now()), new Exception("Set by"));
}
/** host+port, uniquely identifies an attempt */
public String getRemoteHostInfo() { return _remoteHostInfo; }
/** we have received a real data packet, so we're done establishing */
public synchronized void dataReceived() {
packetReceived();
_currentState = STATE_CONFIRMED_COMPLETELY;
}
private void packetReceived() {
_lastReceive = _context.clock().now();
_nextSend = _lastReceive;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Got a packet, nextSend == now");
}
}

View File

@ -0,0 +1,358 @@
package net.i2p.router.transport.udp;
import java.util.ArrayList;
import java.util.List;
import net.i2p.data.Hash;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Coordinate the outbound fragments and select the next one to be built.
* This pool contains messages we are actively trying to send, essentially
* doing a round robin across each message to send one fragment, as implemented
* in {@link #getNextPacket()}. This also honors per-peer throttling, taking
* note of each peer's allocations. If a message has each of its fragments
* sent more than a certain number of times, it is failed out. In addition,
* this instance also receives notification of message ACKs from the
* {@link InboundMessageFragments}, signaling that we can stop sending a
* message.
*
*/
public class OutboundMessageFragments {
private RouterContext _context;
private Log _log;
private UDPTransport _transport;
/** OutboundMessageState for messages being sent */
private List _activeMessages;
private boolean _alive;
/** which message should we build the next packet out of? */
private int _nextPacketMessage;
private PacketBuilder _builder;
private static final int MAX_ACTIVE = 64;
// don't send a packet more than 10 times
private static final int MAX_VOLLEYS = 10;
public OutboundMessageFragments(RouterContext ctx, UDPTransport transport) {
_context = ctx;
_log = ctx.logManager().getLog(OutboundMessageFragments.class);
_transport = transport;
_activeMessages = new ArrayList(MAX_ACTIVE);
_nextPacketMessage = 0;
_builder = new PacketBuilder(ctx, _transport);
_alive = true;
_context.statManager().createRateStat("udp.sendVolleyTime", "Long it takes to send a full volley", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.sendConfirmTime", "How long it takes to send a message and get the ACK", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.sendConfirmFragments", "How many fragments are included in a fully ACKed message", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.sendConfirmVolley", "How many times did fragments need to be sent before ACK", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.sendFailed", "How many fragments were in a message that couldn't be delivered", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
_context.statManager().createRateStat("udp.sendAggressiveFailed", "How many volleys was a packet sent before we gave up", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 });
}
public void startup() { _alive = true; }
public void shutdown() {
_alive = false;
synchronized (_activeMessages) {
_activeMessages.notifyAll();
}
}
/**
* Block until we allow more messages to be admitted to the active
* pool. This is called by the {@link OutboundRefiller}
*
* @return true if more messages are allowed
*/
public boolean waitForMoreAllowed() {
while (_alive) {
finishMessages();
try {
synchronized (_activeMessages) {
if (!_alive)
return false;
else if (_activeMessages.size() < MAX_ACTIVE)
return true;
else
_activeMessages.wait();
}
} catch (InterruptedException ie) {}
}
return false;
}
/**
* Add a new message to the active pool
*
*/
public void add(OutNetMessage msg) {
OutboundMessageState state = new OutboundMessageState(_context);
state.initialize(msg);
finishMessages();
synchronized (_activeMessages) {
_activeMessages.add(state);
_activeMessages.notifyAll();
}
}
/**
* short circuit the OutNetMessage, letting us send the establish
* complete message reliably
*/
public void add(OutboundMessageState state) {
synchronized (_activeMessages) {
_activeMessages.add(state);
_activeMessages.notifyAll();
}
}
/**
* Remove any expired or complete messages
*/
private void finishMessages() {
synchronized (_activeMessages) {
for (int i = 0; i < _activeMessages.size(); i++) {
OutboundMessageState state = (OutboundMessageState)_activeMessages.get(i);
if (state.isComplete()) {
_activeMessages.remove(i);
_transport.succeeded(state.getMessage());
i--;
} else if (state.isExpired()) {
_activeMessages.remove(i);
_context.statManager().addRateData("udp.sendFailed", state.getFragmentCount(), state.getLifetime());
if (state.getMessage() != null) {
_transport.failed(state.getMessage());
} else {
// it can not have an OutNetMessage if the source is the
// final after establishment message
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to send a direct message: " + state);
}
i--;
} else if (state.getPushCount() > MAX_VOLLEYS) {
_activeMessages.remove(i);
_context.statManager().addRateData("udp.sendAggressiveFailed", state.getPushCount(), state.getLifetime());
if (state.getPeer() != null)
state.getPeer().congestionOccurred();
if (state.getMessage() != null) {
_transport.failed(state.getMessage());
} else {
// it can not have an OutNetMessage if the source is the
// final after establishment message
if (_log.shouldLog(Log.WARN))
_log.warn("Unable to send a direct message: " + state);
}
i--;
}
}
}
}
/**
* Grab the next packet that we want to send, blocking until one is ready.
* This is the main driver for the packet scheduler
*
*/
public UDPPacket getNextPacket() {
PeerState peer = null;
OutboundMessageState state = null;
int currentFragment = -1;
while (_alive && (currentFragment < 0) ) {
long now = _context.clock().now();
long nextSend = -1;
finishMessages();
synchronized (_activeMessages) {
for (int i = 0; i < _activeMessages.size(); i++) {
int cur = (i + _nextPacketMessage) % _activeMessages.size();
state = (OutboundMessageState)_activeMessages.get(cur);
if (state.getNextSendTime() <= now) {
peer = state.getPeer(); // known if this is immediately after establish
if (peer == null)
peer = _transport.getPeerState(state.getMessage().getTarget().getIdentity().calculateHash());
if (peer == null) {
// peer disconnected (whatever that means)
_activeMessages.remove(cur);
_transport.failed(state.getMessage());
if (_log.shouldLog(Log.WARN))
_log.warn("Peer disconnected for " + state);
i--;
} else {
if (!state.isFragmented()) {
state.fragment(fragmentSize(peer.getMTU()));
if (_log.shouldLog(Log.INFO))
_log.info("Fragmenting " + state);
}
int oldVolley = state.getPushCount();
// pickNextFragment increments the pushCount every
// time we cycle through all of the packets
currentFragment = state.pickNextFragment();
int fragmentSize = state.fragmentSize(currentFragment);
if (peer.allocateSendingBytes(fragmentSize)) {
if (_log.shouldLog(Log.INFO))
_log.info("Allocation of " + fragmentSize + " allowed");
// for fairness, we move on in a round robin
_nextPacketMessage = i + 1;
if (state.getPushCount() != oldVolley) {
_context.statManager().addRateData("udp.sendVolleyTime", state.getLifetime(), state.getFragmentCount());
state.setNextSendTime(now + 500);
} else {
if (peer.getSendWindowBytesRemaining() > 0)
state.setNextSendTime(now);
else
state.setNextSendTime(now + 50 );
}
break;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Allocation of " + fragmentSize + " rejected");
state.setNextSendTime(now + _context.random().nextInt(500));
currentFragment = -1;
}
}
}
long time = state.getNextSendTime();
if ( (nextSend < 0) || (time < nextSend) )
nextSend = time;
}
if (currentFragment < 0) {
if (nextSend <= 0) {
try {
_activeMessages.wait(100);
} catch (InterruptedException ie) {}
} else {
// none of the packets were eligible for sending
long delay = nextSend - now;
if (delay <= 0)
delay = 10;
if (delay > 500)
delay = 500;
try {
_activeMessages.wait(delay);
} catch (InterruptedException ie) {}
}
}
}
}
if (currentFragment >= 0) {
if (_log.shouldLog(Log.INFO))
_log.info("Building packet for fragment " + currentFragment
+ " of " + state + " to " + peer);
UDPPacket rv = _builder.buildPacket(state, currentFragment, peer);
return rv;
} else {
// !alive
return null;
}
}
private static final int SSU_HEADER_SIZE = 46;
private static final int UDP_HEADER_SIZE = 8;
private static final int IP_HEADER_SIZE = 20;
/** how much payload data can we shove in there? */
private static final int fragmentSize(int mtu) {
return mtu - SSU_HEADER_SIZE - UDP_HEADER_SIZE - IP_HEADER_SIZE;
}
/**
* We received an ACK of the given messageId from the given peer, so if it
* is still unacked, mark it as complete.
*
* @return fragments acked
*/
public int acked(long messageId, Hash ackedBy) {
OutboundMessageState state = null;
synchronized (_activeMessages) {
// linear search, since its tiny
for (int i = 0; i < _activeMessages.size(); i++) {
state = (OutboundMessageState)_activeMessages.get(i);
if (state.getMessageId() == messageId) {
OutNetMessage msg = state.getMessage();
if (msg != null) {
Hash expectedBy = msg.getTarget().getIdentity().getHash();
if (!expectedBy.equals(ackedBy)) {
state = null;
return 0;
}
}
// either the message was a short circuit after establishment,
// or it was received from who we sent it to. yay!
_activeMessages.remove(i);
_activeMessages.notifyAll();
break;
} else {
state = null;
}
}
}
if (state != null) {
if (_log.shouldLog(Log.INFO))
_log.info("Received ack of " + messageId + " by " + ackedBy.toBase64()
+ " after " + state.getLifetime());
_context.statManager().addRateData("udp.sendConfirmTime", state.getLifetime(), state.getLifetime());
_context.statManager().addRateData("udp.sendConfirmFragments", state.getFragmentCount(), state.getLifetime());
int numSends = state.getMaxSends();
_context.statManager().addRateData("udp.sendConfirmVolley", numSends, state.getFragmentCount());
if ( (numSends > 1) && (state.getPeer() != null) )
state.getPeer().congestionOccurred();
_transport.succeeded(state.getMessage());
return state.getFragmentCount();
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Received an ACK for a message not pending: " + messageId);
return 0;
}
}
/**
* Receive a set of fragment ACKs for a given messageId from the
* specified peer
*
*/
public void acked(long messageId, int ackedFragments[], Hash ackedBy) {
if (_log.shouldLog(Log.INFO))
_log.info("Received partial ack of " + messageId + " by " + ackedBy.toBase64());
OutboundMessageState state = null;
synchronized (_activeMessages) {
// linear search, since its tiny
for (int i = 0; i < _activeMessages.size(); i++) {
state = (OutboundMessageState)_activeMessages.get(i);
if (state.getMessage().getMessageId() == messageId) {
Hash expectedBy = state.getMessage().getTarget().getIdentity().calculateHash();
if (!expectedBy.equals(ackedBy)) {
return;
} else {
state.acked(ackedFragments);
if (state.isComplete()) {
_activeMessages.remove(i);
_activeMessages.notifyAll();
}
break;
}
}
}
}
if ( (state != null) && (state.isComplete()) ) {
if (_log.shouldLog(Log.INFO))
_log.info("Received ack of " + messageId + " by " + ackedBy.toBase64()
+ " after " + state.getLifetime());
_context.statManager().addRateData("udp.sendConfirmTime", state.getLifetime(), state.getLifetime());
_context.statManager().addRateData("udp.sendConfirmFragments", state.getFragmentCount(), state.getLifetime());
_transport.succeeded(state.getMessage());
}
}
}

View File

@ -0,0 +1,232 @@
package net.i2p.router.transport.udp;
import java.util.Arrays;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.OutNetMessage;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**
* Maintain the outbound fragmentation for resending
*
*/
public class OutboundMessageState {
private RouterContext _context;
private Log _log;
/** may be null if we are part of the establishment */
private OutNetMessage _message;
private long _messageId;
/** will be null, unless we are part of the establishment */
private PeerState _peer;
private long _expiration;
private ByteArray _messageBuf;
/** fixed fragment size across the message */
private int _fragmentSize;
/** sends[i] is how many times the fragment has been sent, or -1 if ACKed */
private short _fragmentSends[];
private long _startedOn;
private long _nextSendTime;
private int _pushCount;
private short _maxSends;
public static final int MAX_FRAGMENTS = 32;
private static final ByteCache _cache = ByteCache.getInstance(64, MAX_FRAGMENTS*1024);
public OutboundMessageState(RouterContext context) {
_context = context;
_log = _context.logManager().getLog(OutboundMessageState.class);
_pushCount = 0;
_maxSends = 0;
}
public synchronized void initialize(OutNetMessage msg) {
initialize(msg, msg.getMessage(), null);
}
public void initialize(I2NPMessage msg, PeerState peer) {
initialize(null, msg, peer);
}
private void initialize(OutNetMessage m, I2NPMessage msg, PeerState peer) {
_message = m;
_peer = peer;
if (_messageBuf != null) {
_cache.release(_messageBuf);
_messageBuf = null;
}
_messageBuf = _cache.acquire();
int size = msg.getRawMessageSize();
if (size > _messageBuf.getData().length)
throw new IllegalArgumentException("Size too large! " + size + " in " + msg);
int len = msg.toRawByteArray(_messageBuf.getData());
_messageBuf.setValid(len);
_messageId = msg.getUniqueId();
_startedOn = _context.clock().now();
_nextSendTime = _startedOn;
_expiration = _startedOn + 10*1000;
//_expiration = msg.getExpiration();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Raw byte array for " + _messageId + ": " + Base64.encode(_messageBuf.getData(), 0, len));
}
public OutNetMessage getMessage() { return _message; }
public long getMessageId() { return _messageId; }
public PeerState getPeer() { return _peer; }
public boolean isExpired() {
return _expiration < _context.clock().now();
}
public boolean isComplete() {
if (_fragmentSends == null) return false;
for (int i = 0; i < _fragmentSends.length; i++)
if (_fragmentSends[i] >= 0)
return false;
// nothing else pending ack
return true;
}
public long getLifetime() { return _context.clock().now() - _startedOn; }
/**
* Ack all the fragments in the ack list
*/
public void acked(int ackedFragments[]) {
// stupid brute force, but the cardinality should be trivial
for (int i = 0; i < ackedFragments.length; i++) {
if ( (ackedFragments[i] < 0) || (ackedFragments[i] >= _fragmentSends.length) )
continue;
_fragmentSends[ackedFragments[i]] = -1;
}
}
public long getNextSendTime() { return _nextSendTime; }
public void setNextSendTime(long when) { _nextSendTime = when; }
public int getMaxSends() { return _maxSends; }
public int getPushCount() { return _pushCount; }
/** note that we have pushed the message fragments */
public void push() { _pushCount++; }
public boolean isFragmented() { return _fragmentSends != null; }
/**
* Prepare the message for fragmented delivery, using no more than
* fragmentSize bytes per fragment.
*
*/
public void fragment(int fragmentSize) {
int totalSize = _messageBuf.getValid();
int numFragments = totalSize / fragmentSize;
if (numFragments * fragmentSize != totalSize)
numFragments++;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fragmenting a " + totalSize + " message into " + numFragments + " fragments");
//_fragmentEnd = new int[numFragments];
_fragmentSends = new short[numFragments];
//Arrays.fill(_fragmentEnd, -1);
Arrays.fill(_fragmentSends, (short)0);
_fragmentSize = fragmentSize;
}
/** how many fragments in the message */
public int getFragmentCount() {
if (_fragmentSends == null)
return -1;
else
return _fragmentSends.length;
}
/** should we continue sending this fragment? */
public boolean shouldSend(int fragmentNum) { return _fragmentSends[fragmentNum] >= (short)0; }
public int fragmentSize(int fragmentNum) {
if (fragmentNum + 1 == _fragmentSends.length)
return _messageBuf.getValid() % _fragmentSize;
else
return _fragmentSize;
}
/**
* Pick a fragment that we still need to send. Current implementation
* picks the fragment which has been sent the least (randomly choosing
* among equals), incrementing the # sends of the winner in the process.
*
* @return fragment index, or -1 if all of the fragments were acked
*/
public int pickNextFragment() {
short minValue = -1;
int minIndex = -1;
int startOffset = _context.random().nextInt(_fragmentSends.length);
for (int i = 0; i < _fragmentSends.length; i++) {
int cur = (i + startOffset) % _fragmentSends.length;
if (_fragmentSends[cur] < (short)0)
continue;
else if ( (minValue < (short)0) || (_fragmentSends[cur] < minValue) ) {
minValue = _fragmentSends[cur];
minIndex = cur;
}
}
if (minIndex >= 0) {
_fragmentSends[minIndex]++;
if (_fragmentSends[minIndex] > _maxSends)
_maxSends = _fragmentSends[minIndex];
}
// if all fragments have now been sent an equal number of times,
// lets give pause for an ACK
boolean endOfVolley = true;
for (int i = 0; i < _fragmentSends.length; i++) {
if (_fragmentSends[i] < (short)0)
continue;
if (_fragmentSends[i] != (short)_pushCount+1) {
endOfVolley = false;
break;
}
}
if (endOfVolley)
_pushCount++;
if (_log.shouldLog(Log.DEBUG)) {
StringBuffer buf = new StringBuffer(64);
buf.append("Next fragment is ").append(minIndex);
if (minIndex >= 0) {
buf.append(" (#sends: ").append(_fragmentSends[minIndex]-1);
buf.append(" #fragments: ").append(_fragmentSends.length);
buf.append(")");
}
_log.debug(buf.toString());
}
return minIndex;
}
/**
* Write a part of the the message onto the specified buffer.
*
* @param out target to write
* @param outOffset into outOffset to begin writing
* @param fragmentNum fragment to write (0 indexed)
* @return bytesWritten
*/
public synchronized int writeFragment(byte out[], int outOffset, int fragmentNum) {
int start = _fragmentSize * fragmentNum;
int end = start + _fragmentSize;
if (end > _messageBuf.getValid())
end = _messageBuf.getValid();
int toSend = end - start;
System.arraycopy(_messageBuf.getData(), start, out, outOffset, toSend);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Raw fragment[" + fragmentNum + "] for " + _messageId + ": "
+ Base64.encode(_messageBuf.getData(), start, toSend));
return toSend;
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append("Message ").append(_messageId);
if (_fragmentSends != null)
buf.append(" with ").append(_fragmentSends.length).append(" fragments");
return buf.toString();
}
}

View File

@ -0,0 +1,62 @@
package net.i2p.router.transport.udp;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Blocking thread to grab new messages off the outbound queue and
* plopping them into our active pool.
*
*/
public class OutboundRefiller implements Runnable {
private RouterContext _context;
private Log _log;
private OutboundMessageFragments _fragments;
private MessageQueue _messages;
private boolean _alive;
private Object _refillLock;
public OutboundRefiller(RouterContext ctx, OutboundMessageFragments fragments, MessageQueue messages) {
_context = ctx;
_log = ctx.logManager().getLog(OutboundRefiller.class);
_fragments = fragments;
_messages = messages;
_refillLock = this;
_context.statManager().createRateStat("udp.timeToActive", "Message lifetime until it reaches the outbound fragment queue", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
}
public void startup() {
_alive = true;
I2PThread t = new I2PThread(this, "UDP outbound refiller");
t.setDaemon(true);
t.start();
}
public void shutdown() { _alive = false; }
public void run() {
while (_alive) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Check the fragments to see if we can add more...");
boolean wantMore = _fragments.waitForMoreAllowed();
if (wantMore) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Want more fragments...");
OutNetMessage msg = _messages.getNext(-1);
if (msg != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("New message found to fragments: " + msg);
_context.statManager().addRateData("udp.timeToActive", msg.getLifetime(), msg.getLifetime());
_fragments.add(msg);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("No message found to fragment");
}
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("No more fragments allowed, looping");
}
}
}
}

View File

@ -0,0 +1,445 @@
package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.data.Signature;
import net.i2p.router.RouterContext;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**
* Big ol' class to do all our packet formatting. The UDPPackets generated are
* fully authenticated, encrypted, and configured for delivery to the peer.
*
*/
public class PacketBuilder {
private RouterContext _context;
private Log _log;
private UDPTransport _transport;
private static final ByteCache _ivCache = ByteCache.getInstance(64, UDPPacket.IV_SIZE);
public PacketBuilder(RouterContext ctx, UDPTransport transport) {
_context = ctx;
_log = ctx.logManager().getLog(PacketBuilder.class);
_transport = transport;
}
public UDPPacket buildPacket(OutboundMessageState state, int fragment, PeerState peer) {
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] |= (UDPPacket.PAYLOAD_TYPE_DATA << 4);
// todo: add support for rekeying and extended options
off++;
long now = _context.clock().now() / 1000;
DataHelper.toLong(data, off, 4, now);
off += 4;
// ok, now for the body...
// just always ask for an ACK for now...
data[off] |= UDPPacket.DATA_FLAG_WANT_REPLY;
off++;
DataHelper.toLong(data, off, 1, 1); // only one fragment in this message
off++;
DataHelper.toLong(data, off, 4, state.getMessageId());
off += 4;
data[off] |= fragment << 3;
if (fragment == state.getFragmentCount() - 1)
data[off] |= 1 << 2; // isLast
off++;
DataHelper.toLong(data, off, 2, state.fragmentSize(fragment));
off += 2;
off += state.writeFragment(data, off, fragment);
// 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, peer.getCurrentCipherKey(), peer.getCurrentMACKey());
setTo(packet, peer.getRemoteIP(), peer.getRemotePort());
return packet;
}
public UDPPacket buildACK(PeerState peer, List ackedMessageIds) {
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] |= (UDPPacket.PAYLOAD_TYPE_DATA << 4);
// todo: add support for rekeying and extended options
off++;
long now = _context.clock().now() / 1000;
DataHelper.toLong(data, off, 4, now);
off += 4;
// ok, now for the body...
data[off] |= UDPPacket.DATA_FLAG_EXPLICIT_ACK;
// add ECN if (peer.getSomethingOrOther())
off++;
DataHelper.toLong(data, off, 1, ackedMessageIds.size());
off++;
for (int i = 0; i < ackedMessageIds.size(); i++) {
Long id = (Long)ackedMessageIds.get(i);
DataHelper.toLong(data, off, 4, id.longValue());
off += 4;
}
DataHelper.toLong(data, off, 1, 0); // no fragments in this message
off++;
// 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, peer.getCurrentCipherKey(), peer.getCurrentMACKey());
setTo(packet, peer.getRemoteIP(), peer.getRemotePort());
return packet;
}
/**
* full flag info for a sessionCreated message. this can be fixed,
* since we never rekey on startup, and don't need any extended options
*/
private static final byte SESSION_CREATED_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_SESSION_CREATED << 4);
/**
* Build a new SessionCreated packet for the given peer, encrypting it
* as necessary.
*
* @return ready to send packet, or null if there was a problem
*/
public UDPPacket buildSessionCreatedPacket(InboundEstablishState state) {
UDPPacket packet = UDPPacket.acquire(_context);
try {
packet.getPacket().setAddress(InetAddress.getByAddress(state.getSentIP()));
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.ERROR))
_log.error("How did we think this was a valid IP? " + state.getRemoteHostInfo());
return null;
}
state.prepareSessionCreated();
byte data[] = packet.getPacket().getData();
Arrays.fill(data, 0, data.length, (byte)0x0);
int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
// header
data[off] = SESSION_CREATED_FLAG_BYTE;
off++;
long now = _context.clock().now() / 1000;
DataHelper.toLong(data, off, 4, now);
off += 4;
// now for the body
System.arraycopy(state.getSentY(), 0, data, off, state.getSentY().length);
off += state.getSentY().length;
DataHelper.toLong(data, off, 1, state.getSentIP().length);
off += 1;
System.arraycopy(state.getSentIP(), 0, data, off, state.getSentIP().length);
off += state.getSentIP().length;
DataHelper.toLong(data, off, 2, state.getSentPort());
off += 2;
DataHelper.toLong(data, off, 4, state.getSentRelayTag());
off += 4;
DataHelper.toLong(data, off, 4, state.getSentSignedOnTime());
off += 4;
System.arraycopy(state.getSentSignature().getData(), 0, data, off, Signature.SIGNATURE_BYTES);
off += Signature.SIGNATURE_BYTES;
// ok, we need another 8 bytes of random padding
// (ok, this only gives us 63 bits, not 64)
long l = _context.random().nextLong();
if (l < 0) l = 0 - l;
DataHelper.toLong(data, off, 8, l);
off += 8;
if (_log.shouldLog(Log.DEBUG)) {
StringBuffer buf = new StringBuffer(128);
buf.append("Sending sessionCreated:");
buf.append(" AliceIP: ").append(Base64.encode(state.getSentIP()));
buf.append(" AlicePort: ").append(state.getSentPort());
buf.append(" BobIP: ").append(Base64.encode(state.getReceivedOurIP()));
buf.append(" BobPort: ").append(_transport.getExternalPort());
buf.append(" RelayTag: ").append(state.getSentRelayTag());
buf.append(" SignedOn: ").append(state.getSentSignedOnTime());
buf.append(" signature: ").append(Base64.encode(state.getSentSignature().getData()));
buf.append("\nRawCreated: ").append(Base64.encode(data, 0, off));
buf.append("\nsignedTime: ").append(Base64.encode(data, off-8-Signature.SIGNATURE_BYTES-4, 4));
_log.debug(buf.toString());
}
// ok, now the full data is in there, but we also need to encrypt
// the signature, which means we need the IV
ByteArray iv = _ivCache.acquire();
_context.random().nextBytes(iv.getData());
int encrWrite = Signature.SIGNATURE_BYTES + 8;
int sigBegin = off - encrWrite;
_context.aes().encrypt(data, sigBegin, data, sigBegin, state.getCipherKey(), iv.getData(), encrWrite);
// pad up so we're on the encryption boundary
if ( (off % 16) != 0)
off += 16 - (off % 16);
packet.getPacket().setLength(off);
authenticate(packet, _transport.getIntroKey(), _transport.getIntroKey(), iv);
setTo(packet, state.getSentIP(), state.getSentPort());
_ivCache.release(iv);
return packet;
}
/**
* full flag info for a sessionRequest message. this can be fixed,
* since we never rekey on startup, and don't need any extended options
*/
private static final byte SESSION_REQUEST_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_SESSION_REQUEST << 4);
/**
* Build a new SessionRequest packet for the given peer, encrypting it
* as necessary.
*
* @return ready to send packet, or null if there was a problem
*/
public UDPPacket buildSessionRequestPacket(OutboundEstablishState state) {
UDPPacket packet = UDPPacket.acquire(_context);
try {
packet.getPacket().setAddress(InetAddress.getByAddress(state.getSentIP()));
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.ERROR))
_log.error("How did we think this was a valid IP? " + state.getRemoteHostInfo());
return null;
}
byte data[] = packet.getPacket().getData();
Arrays.fill(data, 0, data.length, (byte)0x0);
int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
// header
data[off] = SESSION_REQUEST_FLAG_BYTE;
off++;
long now = _context.clock().now() / 1000;
DataHelper.toLong(data, off, 4, now);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending request with time = " + new Date(now*1000));
off += 4;
// now for the body
System.arraycopy(state.getSentX(), 0, data, off, state.getSentX().length);
off += state.getSentX().length;
DataHelper.toLong(data, off, 1, state.getSentIP().length);
off += 1;
System.arraycopy(state.getSentIP(), 0, data, off, state.getSentIP().length);
off += state.getSentIP().length;
DataHelper.toLong(data, off, 2, state.getSentPort());
off += 2;
// 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, state.getIntroKey(), state.getIntroKey());
setTo(packet, state.getSentIP(), state.getSentPort());
return packet;
}
private static final int MAX_IDENTITY_FRAGMENT_SIZE = 512;
/**
* Build a new series of SessionConfirmed packets for the given peer,
* encrypting it as necessary.
*
* @return ready to send packets, or null if there was a problem
*/
public UDPPacket[] buildSessionConfirmedPackets(OutboundEstablishState state) {
byte identity[] = _context.router().getRouterInfo().getIdentity().toByteArray();
int numFragments = identity.length / MAX_IDENTITY_FRAGMENT_SIZE;
if (numFragments * MAX_IDENTITY_FRAGMENT_SIZE != identity.length)
numFragments++;
UDPPacket packets[] = new UDPPacket[numFragments];
for (int i = 0; i < numFragments; i++)
packets[i] = buildSessionConfirmedPacket(state, i, numFragments, identity);
return packets;
}
/**
* full flag info for a sessionConfirmed message. this can be fixed,
* since we never rekey on startup, and don't need any extended options
*/
private static final byte SESSION_CONFIRMED_FLAG_BYTE = (UDPPacket.PAYLOAD_TYPE_SESSION_CONFIRMED << 4);
/**
* Build a new SessionConfirmed packet for the given peer
*
* @return ready to send packets, or null if there was a problem
*/
public UDPPacket buildSessionConfirmedPacket(OutboundEstablishState state, int fragmentNum, int numFragments, byte identity[]) {
UDPPacket packet = UDPPacket.acquire(_context);
try {
packet.getPacket().setAddress(InetAddress.getByAddress(state.getSentIP()));
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.ERROR))
_log.error("How did we think this was a valid IP? " + state.getRemoteHostInfo());
return null;
}
byte data[] = packet.getPacket().getData();
Arrays.fill(data, 0, data.length, (byte)0x0);
int off = UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
// header
data[off] = SESSION_CONFIRMED_FLAG_BYTE;
off++;
long now = _context.clock().now() / 1000;
DataHelper.toLong(data, off, 4, now);
off += 4;
// now for the body
data[off] |= fragmentNum << 4;
data[off] |= (numFragments & 0xF);
off++;
int curFragSize = MAX_IDENTITY_FRAGMENT_SIZE;
if (fragmentNum == numFragments-1) {
if (identity.length % MAX_IDENTITY_FRAGMENT_SIZE != 0)
curFragSize = identity.length % MAX_IDENTITY_FRAGMENT_SIZE;
}
DataHelper.toLong(data, off, 2, curFragSize);
off += 2;
int curFragOffset = fragmentNum * MAX_IDENTITY_FRAGMENT_SIZE;
System.arraycopy(identity, curFragOffset, data, off, curFragSize);
off += curFragSize;
if (fragmentNum == numFragments - 1) {
DataHelper.toLong(data, off, 4, state.getSentSignedOnTime());
off += 4;
int paddingRequired = 0;
// we need to pad this so we're at the encryption boundary
if ( (off + Signature.SIGNATURE_BYTES) % 16 != 0)
paddingRequired += 16 - ((off + Signature.SIGNATURE_BYTES) % 16);
// add an arbitrary number of 16byte pad blocks too...
for (int i = 0; i < paddingRequired; i++) {
data[off] = (byte)_context.random().nextInt(255);
off++;
}
System.arraycopy(state.getSentSignature().getData(), 0, data, off, Signature.SIGNATURE_BYTES);
packet.getPacket().setLength(off + Signature.SIGNATURE_BYTES);
authenticate(packet, state.getCipherKey(), state.getMACKey());
} else {
// nothing more to add beyond the identity fragment, though 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, state.getIntroKey(), state.getIntroKey());
}
setTo(packet, state.getSentIP(), state.getSentPort());
return packet;
}
private void setTo(UDPPacket packet, byte ip[], int port) {
try {
InetAddress to = InetAddress.getByAddress(ip);
packet.getPacket().setAddress(to);
packet.getPacket().setPort(port);
} catch (UnknownHostException uhe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Invalid IP? ", uhe);
}
}
/**
* Encrypt the packet with the cipher key and a new random IV, generate a
* MAC for that encrypted data and IV, and store the result in the packet.
*
* @param packet prepared packet with the first 32 bytes empty and a length
* whose size is mod 16
* @param cipherKey key to encrypt the payload
* @param macKey key to generate the, er, MAC
*/
private void authenticate(UDPPacket packet, SessionKey cipherKey, SessionKey macKey) {
ByteArray iv = _ivCache.acquire();
_context.random().nextBytes(iv.getData());
authenticate(packet, cipherKey, macKey, iv);
_ivCache.release(iv);
}
/**
* Encrypt the packet with the cipher key and the given IV, generate a
* MAC for that encrypted data and IV, and store the result in the packet.
* The MAC used is:
* HMAC-SHA256(payload || IV || payloadLength, macKey)[0:15]
*
* @param packet prepared packet with the first 32 bytes empty and a length
* whose size is mod 16
* @param cipherKey key to encrypt the payload
* @param macKey key to generate the, er, MAC
* @param iv IV to deliver
*/
private void authenticate(UDPPacket packet, SessionKey cipherKey, SessionKey macKey, ByteArray iv) {
int encryptOffset = packet.getPacket().getOffset() + UDPPacket.IV_SIZE + UDPPacket.MAC_SIZE;
int encryptSize = packet.getPacket().getLength() - UDPPacket.IV_SIZE - UDPPacket.MAC_SIZE - packet.getPacket().getOffset();
byte data[] = packet.getPacket().getData();
_context.aes().encrypt(data, encryptOffset, data, encryptOffset, cipherKey, iv.getData(), encryptSize);
// ok, now we need to prepare things for the MAC, which requires reordering
int off = packet.getPacket().getOffset();
System.arraycopy(data, encryptOffset, data, off, encryptSize);
off += encryptSize;
System.arraycopy(iv.getData(), 0, data, off, UDPPacket.IV_SIZE);
off += UDPPacket.IV_SIZE;
DataHelper.toLong(data, off, 2, encryptSize);
int hmacOff = packet.getPacket().getOffset();
int hmacLen = encryptSize + UDPPacket.IV_SIZE + 2;
Hash hmac = _context.hmac().calculate(macKey, data, hmacOff, hmacLen);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Authenticating " + packet.getPacket().getLength() +
"\nIV: " + Base64.encode(iv.getData()) +
"\nraw mac: " + hmac.toBase64() +
"\nMAC key: " + macKey.toBase64());
// ok, now lets put it back where it belongs...
System.arraycopy(data, hmacOff, data, encryptOffset, encryptSize);
System.arraycopy(hmac.getData(), 0, data, hmacOff, UDPPacket.MAC_SIZE);
System.arraycopy(iv.getData(), 0, data, hmacOff + UDPPacket.MAC_SIZE, UDPPacket.IV_SIZE);
}
}

View File

@ -0,0 +1,293 @@
package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.util.Date;
import net.i2p.data.Base64;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Pull inbound packets from the inbound receiver's queue, figure out what
* peer session they belong to (if any), authenticate and decrypt them
* with the appropriate keys, and push them to the appropriate handler.
* Data and ACK packets go to the InboundMessageFragments, the various
* establishment packets go to the EstablishmentManager, and, once implemented,
* relay packets will go to the relay manager. At the moment, this is
* an actual pool of packet handler threads, each pulling off the inbound
* receiver's queue and pushing them as necessary.
*
*/
public class PacketHandler implements Runnable {
private RouterContext _context;
private Log _log;
private UDPTransport _transport;
private UDPEndpoint _endpoint;
private UDPPacketReader _reader;
private EstablishmentManager _establisher;
private InboundMessageFragments _inbound;
private boolean _keepReading;
private static final int NUM_HANDLERS = 3;
public PacketHandler(RouterContext ctx, UDPTransport transport, UDPEndpoint endpoint, EstablishmentManager establisher, InboundMessageFragments inbound) {
_context = ctx;
_log = ctx.logManager().getLog(PacketHandler.class);
_transport = transport;
_endpoint = endpoint;
_establisher = establisher;
_inbound = inbound;
_reader = new UDPPacketReader(ctx);
_context.statManager().createRateStat("udp.handleTime", "How long it takes to handle a received packet after its been pulled off the queue", "udp", new long[] { 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("udp.queueTime", "How long after a packet is received can we begin handling it", "udp", new long[] { 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("udp.receivePacketSkew", "How long ago after the packet was sent did we receive it", "udp", new long[] { 10*60*1000, 60*60*1000 });
}
public void startup() {
_keepReading = true;
for (int i = 0; i < NUM_HANDLERS; i++) {
I2PThread t = new I2PThread(this, "Packet handler " + i + ": " + _endpoint.getListenPort());
t.setDaemon(true);
t.start();
}
}
public void shutdown() {
_keepReading = false;
}
public void run() {
while (_keepReading) {
UDPPacket packet = _endpoint.receive();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received the packet " + packet);
long queueTime = packet.getLifetime();
long handleStart = _context.clock().now();
handlePacket(packet);
long handleTime = _context.clock().now() - handleStart;
_context.statManager().addRateData("udp.handleTime", handleTime, packet.getLifetime());
_context.statManager().addRateData("udp.queueTime", queueTime, packet.getLifetime());
if (handleTime > 1000) {
if (_log.shouldLog(Log.WARN))
_log.warn("Took " + handleTime + " to process the packet "
+ packet + ": " + _reader);
}
// back to the cache with thee!
packet.release();
}
}
private void handlePacket(UDPPacket packet) {
if (packet == null) return;
InetAddress remAddr = packet.getPacket().getAddress();
int remPort = packet.getPacket().getPort();
PeerState state = _transport.getPeerState(remAddr, remPort);
if (state == null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received is not for a connected peer");
InboundEstablishState est = _establisher.getInboundState(remAddr, remPort);
if (est != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received IS for an inbound establishment");
receivePacket(packet, est);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received is not for an inbound establishment");
OutboundEstablishState oest = _establisher.getOutboundState(remAddr, remPort);
if (oest != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received IS for an outbound establishment");
receivePacket(packet, oest);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received is not for an inbound or outbound establishment");
// ok, not already known establishment, try as a new one
receivePacket(packet);
}
}
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Packet received IS for an existing peer");
receivePacket(packet, state);
}
}
private void receivePacket(UDPPacket packet, PeerState state) {
boolean isValid = packet.validate(state.getCurrentMACKey());
if (!isValid) {
if (state.getNextMACKey() != null)
isValid = packet.validate(state.getNextMACKey());
if (!isValid) {
if (_log.shouldLog(Log.WARN))
_log.warn("Failed validation with existing con, trying as new con: " + packet);
isValid = packet.validate(_transport.getIntroKey());
if (isValid) {
// this is a stray packet from an inbound establishment
// 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");
packet.decrypt(_transport.getIntroKey());
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Validation with existing con failed, and validation as reestablish failed too. DROP");
return;
}
} else {
packet.decrypt(state.getNextCipherKey());
}
} else {
packet.decrypt(state.getCurrentCipherKey());
}
handlePacket(packet, state, null, null);
}
private void receivePacket(UDPPacket packet) {
boolean isValid = packet.validate(_transport.getIntroKey());
if (!isValid) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid introduction packet received: " + packet, new Exception("path"));
return;
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Valid introduction packet received: " + packet);
}
packet.decrypt(_transport.getIntroKey());
handlePacket(packet, null, null, null);
}
private void receivePacket(UDPPacket packet, InboundEstablishState state) {
if ( (state != null) && (_log.shouldLog(Log.DEBUG)) ) {
StringBuffer buf = new StringBuffer(128);
buf.append("Attempting to receive a packet on a known inbound state: ");
buf.append(state);
buf.append(" MAC key: ").append(state.getMACKey());
buf.append(" intro key: ").append(_transport.getIntroKey());
_log.debug(buf.toString());
}
boolean isValid = false;
if (state.getMACKey() != null) {
isValid = packet.validate(state.getMACKey());
if (isValid) {
if (_log.shouldLog(Log.WARN))
_log.warn("Valid introduction packet received for inbound con: " + packet);
packet.decrypt(state.getCipherKey());
handlePacket(packet, null, null, null);
return;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid introduction packet received for inbound con, falling back: " + packet);
}
}
// ok, we couldn't handle it with the established stuff, so fall back
// on earlier state packets
receivePacket(packet);
}
private void receivePacket(UDPPacket packet, OutboundEstablishState state) {
if ( (state != null) && (_log.shouldLog(Log.DEBUG)) ) {
StringBuffer buf = new StringBuffer(128);
buf.append("Attempting to receive a packet on a known outbound state: ");
buf.append(state);
buf.append(" MAC key: ").append(state.getMACKey());
buf.append(" intro key: ").append(state.getIntroKey());
_log.debug(buf.toString());
}
boolean isValid = false;
if (state.getMACKey() != null) {
isValid = packet.validate(state.getMACKey());
if (isValid) {
if (_log.shouldLog(Log.WARN))
_log.warn("Valid introduction packet received for outbound established con: " + packet);
packet.decrypt(state.getCipherKey());
handlePacket(packet, null, state, null);
return;
}
}
// keys not yet exchanged, lets try it with the peer's intro key
isValid = packet.validate(state.getIntroKey());
if (isValid) {
if (_log.shouldLog(Log.WARN))
_log.warn("Valid introduction packet received for outbound established con with old intro key: " + packet);
packet.decrypt(state.getIntroKey());
handlePacket(packet, null, state, null);
return;
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid introduction packet received for outbound established con with old intro key, falling back: " + packet);
}
// ok, we couldn't handle it with the established stuff, so fall back
// on earlier state packets
receivePacket(packet);
}
/** let packets be up to 30s slow */
private static final long GRACE_PERIOD = Router.CLOCK_FUDGE_FACTOR + 30*1000;
/**
* Parse out the interesting bits and honor what it says
*/
private void handlePacket(UDPPacket packet, PeerState state, OutboundEstablishState outState, InboundEstablishState inState) {
_reader.initialize(packet);
long now = _context.clock().now();
long when = _reader.readTimestamp() * 1000;
long skew = now - when;
if (skew > GRACE_PERIOD) {
if (_log.shouldLog(Log.WARN))
_log.warn("Packet too far in the future: " + new Date(when) + ": " + packet);
return;
} else if (skew < 0 - GRACE_PERIOD) {
if (_log.shouldLog(Log.WARN))
_log.warn("Packet too far in the past: " + new Date(when) + ": " + packet);
return;
}
_context.statManager().addRateData("udp.receivePacketSkew", skew, packet.getLifetime());
InetAddress fromHost = packet.getPacket().getAddress();
int fromPort = packet.getPacket().getPort();
String from = PeerState.calculateRemoteHostString(fromHost.getAddress(), fromPort);
switch (_reader.readPayloadType()) {
case UDPPacket.PAYLOAD_TYPE_SESSION_REQUEST:
_establisher.receiveSessionRequest(from, fromHost, fromPort, _reader);
break;
case UDPPacket.PAYLOAD_TYPE_SESSION_CONFIRMED:
_establisher.receiveSessionConfirmed(from, _reader);
break;
case UDPPacket.PAYLOAD_TYPE_SESSION_CREATED:
_establisher.receiveSessionCreated(from, _reader);
break;
case UDPPacket.PAYLOAD_TYPE_DATA:
if (outState != null)
state = _establisher.receiveData(outState);
handleData(packet, state);
break;
default:
if (_log.shouldLog(Log.WARN))
_log.warn("Unknown payload type: " + _reader.readPayloadType());
return;
}
}
private void handleData(UDPPacket packet, PeerState peer) {
if (_log.shouldLog(Log.INFO))
_log.info("Received new DATA packet from " + peer + ": " + packet);
_inbound.receiveData(peer, _reader.getDataReader());
}
}

View File

@ -0,0 +1,43 @@
package net.i2p.router.transport.udp;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Blocking thread to grab new packets off the outbound fragment
* pool and toss 'em onto the outbound packet queue
*
*/
public class PacketPusher implements Runnable {
private RouterContext _context;
private Log _log;
private OutboundMessageFragments _fragments;
private UDPSender _sender;
private boolean _alive;
public PacketPusher(RouterContext ctx, OutboundMessageFragments fragments, UDPSender sender) {
_context = ctx;
_log = ctx.logManager().getLog(PacketPusher.class);
_fragments = fragments;
_sender = sender;
}
public void startup() {
_alive = true;
I2PThread t = new I2PThread(this, "UDP packet pusher");
t.setDaemon(true);
t.start();
}
public void shutdown() { _alive = false; }
public void run() {
while (_alive) {
UDPPacket packet = _fragments.getNextPacket();
if (packet != null)
_sender.add(packet, true); // blocks
}
}
}

View File

@ -0,0 +1,438 @@
package net.i2p.router.transport.udp;
import java.util.ArrayList;
import java.util.List;
import java.net.InetAddress;
import net.i2p.I2PAppContext;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
/**
* Contain all of the state about a UDP connection to a peer
*
*/
public class PeerState {
private I2PAppContext _context;
private Log _log;
/**
* The peer are we talking to. This should be set as soon as this
* state is created if we are initiating a connection, but if we are
* receiving the connection this will be set only after the connection
* is established.
*/
private Hash _remotePeer;
/**
* The AES key used to verify packets, set only after the connection is
* established.
*/
private SessionKey _currentMACKey;
/**
* The AES key used to encrypt/decrypt packets, set only after the
* connection is established.
*/
private SessionKey _currentCipherKey;
/**
* The pending AES key for verifying packets if we are rekeying the
* connection, or null if we are not in the process of rekeying.
*/
private SessionKey _nextMACKey;
/**
* The pending AES key for encrypting/decrypting packets if we are
* rekeying the connection, or null if we are not in the process
* of rekeying.
*/
private SessionKey _nextCipherKey;
/**
* The keying material used for the rekeying, or null if we are not in
* the process of rekeying.
*/
private byte[] _nextKeyingMaterial;
/** true if we began the current rekeying, false otherwise */
private boolean _rekeyBeganLocally;
/** when were the current cipher and MAC keys established/rekeyed? */
private long _keyEstablishedTime;
/** how far off is the remote peer from our clock, in seconds? */
private short _clockSkew;
/** what is the current receive second, for congestion control? */
private long _currentReceiveSecond;
/** when did we last send them a packet? */
private long _lastSendTime;
/** when did we last receive a packet from them? */
private long _lastReceiveTime;
/** how many seconds have we sent packets without any ACKs received? */
private int _consecutiveSendingSecondsWithoutACKs;
/** list of messageIds (Long) that we have received but not yet sent */
private List _currentACKs;
/** when did we last send ACKs to the peer? */
private long _lastACKSend;
/** have we received a packet with the ECN bit set in the current second? */
private boolean _currentSecondECNReceived;
/**
* have all of the packets received in the current second requested that
* the previous second's ACKs be sent?
*/
private boolean _remoteWantsPreviousACKs;
/** how many bytes should we send to the peer in a second */
private int _sendWindowBytes;
/** how many bytes can we send to the peer in the current second */
private int _sendWindowBytesRemaining;
/** what IP is the peer sending and receiving packets on? */
private byte[] _remoteIP;
/** what port is the peer sending and receiving packets on? */
private int _remotePort;
/** cached remoteIP + port, used to find the peerState by remote info */
private String _remoteHostString;
/** if we need to contact them, do we need to talk to an introducer? */
private boolean _remoteRequiresIntroduction;
/**
* if we are serving as an introducer to them, this is the the tag that
* they can publish that, when presented to us, will cause us to send
* a relay introduction to the current peer
*/
private long _weRelayToThemAs;
/**
* If they have offered to serve as an introducer to us, this is the tag
* we can use to publish that fact.
*/
private long _theyRelayToUsAs;
/** what is the largest packet we can send to the peer? */
private int _mtu;
/** when did we last check the MTU? */
private long _mtuLastChecked;
private long _messagesReceived;
private long _messagesSent;
private static final int DEFAULT_SEND_WINDOW_BYTES = 16*1024;
private static final int MINIMUM_WINDOW_BYTES = DEFAULT_SEND_WINDOW_BYTES;
private static final int DEFAULT_MTU = 512;
public PeerState(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(PeerState.class);
_remotePeer = null;
_currentMACKey = null;
_currentCipherKey = null;
_nextMACKey = null;
_nextCipherKey = null;
_nextKeyingMaterial = null;
_rekeyBeganLocally = false;
_keyEstablishedTime = -1;
_clockSkew = Short.MIN_VALUE;
_currentReceiveSecond = -1;
_lastSendTime = -1;
_lastReceiveTime = -1;
_currentACKs = new ArrayList(8);
_currentSecondECNReceived = false;
_remoteWantsPreviousACKs = false;
_sendWindowBytes = DEFAULT_SEND_WINDOW_BYTES;
_sendWindowBytesRemaining = DEFAULT_SEND_WINDOW_BYTES;
_remoteIP = null;
_remotePort = -1;
_remoteRequiresIntroduction = false;
_weRelayToThemAs = 0;
_theyRelayToUsAs = 0;
_mtu = DEFAULT_MTU;
_mtuLastChecked = -1;
_lastACKSend = -1;
_messagesReceived = 0;
_messagesSent = 0;
}
/**
* The peer are we talking to. This should be set as soon as this
* state is created if we are initiating a connection, but if we are
* receiving the connection this will be set only after the connection
* is established.
*/
public Hash getRemotePeer() { return _remotePeer; }
/**
* The AES key used to verify packets, set only after the connection is
* established.
*/
public SessionKey getCurrentMACKey() { return _currentMACKey; }
/**
* The AES key used to encrypt/decrypt packets, set only after the
* connection is established.
*/
public SessionKey getCurrentCipherKey() { return _currentCipherKey; }
/**
* The pending AES key for verifying packets if we are rekeying the
* connection, or null if we are not in the process of rekeying.
*/
public SessionKey getNextMACKey() { return _nextMACKey; }
/**
* The pending AES key for encrypting/decrypting packets if we are
* rekeying the connection, or null if we are not in the process
* of rekeying.
*/
public SessionKey getNextCipherKey() { return _nextCipherKey; }
/**
* The keying material used for the rekeying, or null if we are not in
* the process of rekeying.
*/
public byte[] getNextKeyingMaterial() { return _nextKeyingMaterial; }
/** true if we began the current rekeying, false otherwise */
public boolean getRekeyBeganLocally() { return _rekeyBeganLocally; }
/** when were the current cipher and MAC keys established/rekeyed? */
public long getKeyEstablishedTime() { return _keyEstablishedTime; }
/** how far off is the remote peer from our clock, in seconds? */
public short getClockSkew() { return _clockSkew; }
/** what is the current receive second, for congestion control? */
public long getCurrentReceiveSecond() { return _currentReceiveSecond; }
/** when did we last send them a packet? */
public long getLastSendTime() { return _lastSendTime; }
/** when did we last receive a packet from them? */
public long getLastReceiveTime() { return _lastReceiveTime; }
/** how many seconds have we sent packets without any ACKs received? */
public int getConsecutiveSendingSecondsWithoutACKS() { return _consecutiveSendingSecondsWithoutACKs; }
/** have we received a packet with the ECN bit set in the current second? */
public boolean getCurrentSecondECNReceived() { return _currentSecondECNReceived; }
/**
* have all of the packets received in the current second requested that
* the previous second's ACKs be sent?
*/
public boolean getRemoteWantsPreviousACKs() { return _remoteWantsPreviousACKs; }
/** how many bytes should we send to the peer in a second */
public int getSendWindowBytes() { return _sendWindowBytes; }
/** how many bytes can we send to the peer in the current second */
public int getSendWindowBytesRemaining() { return _sendWindowBytesRemaining; }
/** what IP is the peer sending and receiving packets on? */
public byte[] getRemoteIP() { return _remoteIP; }
/** what port is the peer sending and receiving packets on? */
public int getRemotePort() { return _remotePort; }
/** if we need to contact them, do we need to talk to an introducer? */
public boolean getRemoteRequiresIntroduction() { return _remoteRequiresIntroduction; }
/**
* if we are serving as an introducer to them, this is the the tag that
* they can publish that, when presented to us, will cause us to send
* a relay introduction to the current peer
*/
public long getWeRelayToThemAs() { return _weRelayToThemAs; }
/**
* If they have offered to serve as an introducer to us, this is the tag
* we can use to publish that fact.
*/
public long getTheyRelayToUsAs() { return _theyRelayToUsAs; }
/** what is the largest packet we can send to the peer? */
public int getMTU() { return _mtu; }
/** when did we last check the MTU? */
public long getMTULastChecked() { return _mtuLastChecked; }
/**
* The peer are we talking to. This should be set as soon as this
* state is created if we are initiating a connection, but if we are
* receiving the connection this will be set only after the connection
* is established.
*/
public void setRemotePeer(Hash peer) { _remotePeer = peer; }
/**
* The AES key used to verify packets, set only after the connection is
* established.
*/
public void setCurrentMACKey(SessionKey key) { _currentMACKey = key; }
/**
* The AES key used to encrypt/decrypt packets, set only after the
* connection is established.
*/
public void setCurrentCipherKey(SessionKey key) { _currentCipherKey = key; }
/**
* The pending AES key for verifying packets if we are rekeying the
* connection, or null if we are not in the process of rekeying.
*/
public void setNextMACKey(SessionKey key) { _nextMACKey = key; }
/**
* The pending AES key for encrypting/decrypting packets if we are
* rekeying the connection, or null if we are not in the process
* of rekeying.
*/
public void setNextCipherKey(SessionKey key) { _nextCipherKey = key; }
/**
* The keying material used for the rekeying, or null if we are not in
* the process of rekeying.
*/
public void setNextKeyingMaterial(byte data[]) { _nextKeyingMaterial = data; }
/** true if we began the current rekeying, false otherwise */
public void setRekeyBeganLocally(boolean local) { _rekeyBeganLocally = local; }
/** when were the current cipher and MAC keys established/rekeyed? */
public void setKeyEstablishedTime(long when) { _keyEstablishedTime = when; }
/** how far off is the remote peer from our clock, in seconds? */
public void setClockSkew(short skew) { _clockSkew = skew; }
/** what is the current receive second, for congestion control? */
public void setCurrentReceiveSecond(long sec) { _currentReceiveSecond = sec; }
/** when did we last send them a packet? */
public void setLastSendTime(long when) { _lastSendTime = when; }
/** when did we last receive a packet from them? */
public void setLastReceiveTime(long when) { _lastReceiveTime = when; }
public void incrementConsecutiveSendingSecondsWithoutACKS() { _consecutiveSendingSecondsWithoutACKs++; }
public void resetConsecutiveSendingSecondsWithoutACKS() { _consecutiveSendingSecondsWithoutACKs = 0; }
/*
public void migrateACKs(List NACKs, long newSecond) {
_previousSecondACKs = _currentSecondACKs;
if (_currentSecondECNReceived)
_sendWindowBytes /= 2;
if (_sendWindowBytes < MINIMUM_WINDOW_BYTES)
_sendWindowBytes = MINIMUM_WINDOW_BYTES;
_sendWindowBytesRemaining = _sendWindowBytes;
_currentSecondECNReceived = false;
_remoteWantsPreviousACKs = true;
_currentReceiveSecond = newSecond;
}
*/
/**
* have all of the packets received in the current second requested that
* the previous second's ACKs be sent?
*/
public void remoteDoesNotWantPreviousACKs() { _remoteWantsPreviousACKs = false; }
/**
* Decrement the remaining bytes in the current period's window,
* returning true if the full size can be decremented, false if it
* cannot. If it is not decremented, the window size remaining is
* not adjusted at all.
*/
public boolean allocateSendingBytes(int size) {
long now = _context.clock().now();
if (_lastSendTime > 0) {
if (_lastSendTime + 1000 <= now)
_sendWindowBytesRemaining = _sendWindowBytes;
}
if (size <= _sendWindowBytesRemaining) {
_sendWindowBytesRemaining -= size;
_lastSendTime = now;
return true;
} else {
return false;
}
}
/** what IP+port is the peer sending and receiving packets on? */
public void setRemoteAddress(byte ip[], int port) {
_remoteIP = ip;
_remotePort = port;
_remoteHostString = calculateRemoteHostString(ip, port);
}
/** if we need to contact them, do we need to talk to an introducer? */
public void setRemoteRequiresIntroduction(boolean required) { _remoteRequiresIntroduction = required; }
/**
* if we are serving as an introducer to them, this is the the tag that
* they can publish that, when presented to us, will cause us to send
* a relay introduction to the current peer
*/
public void setWeRelayToThemAs(long tag) { _weRelayToThemAs = tag; }
/**
* If they have offered to serve as an introducer to us, this is the tag
* we can use to publish that fact.
*/
public void setTheyRelayToUsAs(long tag) { _theyRelayToUsAs = tag; }
/** what is the largest packet we can send to the peer? */
public void setMTU(int mtu) {
_mtu = mtu;
_mtuLastChecked = _context.clock().now();
}
/** we received the message specified completely */
public void messageFullyReceived(Long messageId) {
synchronized (_currentACKs) {
if (!_currentACKs.contains(messageId))
_currentACKs.add(messageId);
}
_messagesReceived++;
}
/**
* either they told us to back off, or we had to resend to get
* the data through.
*
*/
public void congestionOccurred() {
_sendWindowBytes /= 2;
if (_sendWindowBytes < MINIMUM_WINDOW_BYTES)
_sendWindowBytes = MINIMUM_WINDOW_BYTES;
}
/** pull off the ACKs (Long) to send to the peer */
public List retrieveACKs() {
List rv = null;
synchronized (_currentACKs) {
rv = new ArrayList(_currentACKs);
_currentACKs.clear();
}
return rv;
}
/** we sent a message which was ACKed containing the given # of bytes */
public void messageACKed(int bytesACKed) {
_consecutiveSendingSecondsWithoutACKs = 0;
_sendWindowBytes += bytesACKed;
_lastReceiveTime = _context.clock().now();
_messagesSent++;
}
public long getMessagesSent() { return _messagesSent; }
public long getMessagesReceived() { return _messagesReceived; }
/**
* we received a backoff request, so cut our send window
*/
public void ECNReceived() {
congestionOccurred();
_currentSecondECNReceived = true;
_lastReceiveTime = _context.clock().now();
}
public void dataReceived() {
_lastReceiveTime = _context.clock().now();
}
/** when did we last send an ACK to the peer? */
public long getLastACKSend() { return _lastACKSend; }
public void setLastACKSend(long when) { _lastACKSend = when; }
public String getRemoteHostString() { return _remoteHostString; }
public static String calculateRemoteHostString(byte ip[], int port) {
StringBuffer buf = new StringBuffer(ip.length * 4 + 5);
for (int i = 0; i < ip.length; i++)
buf.append((int)ip[i]).append('.');
buf.append(port);
return buf.toString();
}
public static String calculateRemoteHostString(UDPPacket packet) {
InetAddress remAddr = packet.getPacket().getAddress();
int remPort = packet.getPacket().getPort();
return calculateRemoteHostString(remAddr.getAddress(), remPort);
}
public int hashCode() {
if (_remotePeer != null)
return _remotePeer.hashCode();
else
return super.hashCode();
}
public boolean equals(Object o) {
if (o == null) return false;
if (o instanceof PeerState) {
PeerState s = (PeerState)o;
if (_remotePeer == null)
return o == this;
else
return _remotePeer.equals(s.getRemotePeer());
} else {
return false;
}
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append(_remoteHostString);
if (_remotePeer != null)
buf.append(" ").append(_remotePeer.toBase64().substring(0,6));
return buf.toString();
}
}

View File

@ -0,0 +1,24 @@
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

@ -0,0 +1,226 @@
package net.i2p.router.transport.udp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Weighted priority queue implementation for the outbound messages, coupled
* with code to fail messages that expire.
*
*/
public class TimedWeightedPriorityMessageQueue implements MessageQueue {
private RouterContext _context;
private Log _log;
/** FIFO queue of messages in a particular priority */
private List _queue[];
/** all messages in the indexed queue are at or below the given priority. */
private int _priorityLimits[];
/** weighting for each queue */
private int _weighting[];
/** how many bytes are enqueued */
private long _bytesQueued[];
/** how many messages have been pushed out in this pass */
private int _messagesFlushed[];
/** how many bytes total have been pulled off the given queue */
private long _bytesTransferred[];
/** lock to notify message enqueue/removal (and block for getNext()) */
private Object _nextLock;
/** have we shut down or are we still alive? */
private boolean _alive;
/** which queue should we pull out of next */
private int _nextQueue;
/** true if a message is enqueued while the getNext() call is in progress */
private volatile boolean _addedSincePassBegan;
private Expirer _expirer;
private FailedListener _listener;
/**
* Build up a new queue
*
* @param priorityLimits ordered breakpoint for the different message
* priorities, with the lowest limit first.
* @param weighting how much to prefer a given priority grouping.
* specifically, this means how many messages in this queue
* should be pulled off in a row before moving on to the next.
*/
public TimedWeightedPriorityMessageQueue(RouterContext ctx, int[] priorityLimits, int[] weighting, FailedListener lsnr) {
_context = ctx;
_log = ctx.logManager().getLog(TimedWeightedPriorityMessageQueue.class);
_queue = new List[weighting.length];
_priorityLimits = new int[weighting.length];
_weighting = new int[weighting.length];
_bytesQueued = new long[weighting.length];
_bytesTransferred = new long[weighting.length];
_messagesFlushed = new int[weighting.length];
for (int i = 0; i < weighting.length; i++) {
_queue[i] = new ArrayList(8);
_weighting[i] = weighting[i];
_priorityLimits[i] = priorityLimits[i];
_messagesFlushed[i] = 0;
_bytesQueued[i] = 0;
_bytesTransferred[i] = 0;
}
_alive = true;
_nextLock = this;
_nextQueue = 0;
_listener = lsnr;
_context.statManager().createRateStat("udp.timeToEntrance", "Message lifetime until it reaches the UDP system", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("udp.messageQueueSize", "How many messages are on the current class queue at removal", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_expirer = new Expirer();
I2PThread t = new I2PThread(_expirer, "UDP outbound expirer");
t.setDaemon(true);
t.start();
}
public void add(OutNetMessage message) {
if (message == null) return;
_context.statManager().addRateData("udp.timeToEntrance", message.getLifetime(), message.getLifetime());
int queue = pickQueue(message);
long size = message.getMessageSize();
synchronized (_queue[queue]) {
_queue[queue].add(message);
_bytesQueued[queue] += size;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Added a " + size + " byte message to queue " + queue);
synchronized (_nextLock) {
_addedSincePassBegan = true;
_nextLock.notifyAll();
}
}
/**
* Grab the next message out of the next queue. This only advances
* the _nextQueue var after pushing _weighting[currentQueue] messages
* or the queue is empty. This call blocks until either a message
* becomes available or the queue is shut down.
*
* @param blockUntil expiration, or -1 if indefinite
* @return message dequeued, or null if the queue was shut down
*/
public OutNetMessage getNext(long blockUntil) {
while (_alive) {
_addedSincePassBegan = false;
for (int i = 0; i < _queue.length; i++) {
int currentQueue = (_nextQueue + i) % _queue.length;
synchronized (_queue[currentQueue]) {
if (_queue[currentQueue].size() > 0) {
OutNetMessage msg = (OutNetMessage)_queue[currentQueue].remove(0);
long size = msg.getMessageSize();
_bytesQueued[currentQueue] -= size;
_bytesTransferred[currentQueue] += size;
_messagesFlushed[currentQueue]++;
if (_messagesFlushed[currentQueue] >= _weighting[currentQueue]) {
_messagesFlushed[currentQueue] = 0;
_nextQueue = (currentQueue + 1) % _queue.length;
}
_context.statManager().addRateData("udp.messageQueueSize", _queue[currentQueue].size(), currentQueue);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Pulling a message off queue " + currentQueue + " with "
+ _queue[currentQueue].size() + " remaining");
return msg;
} else {
// nothing waiting
_messagesFlushed[currentQueue] = 0;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Nothing on queue " + currentQueue);
}
}
}
long remaining = blockUntil - _context.clock().now();
if ( (blockUntil > 0) && (remaining < 0) ) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Nonblocking, or block time has expired");
return null;
}
try {
synchronized (_nextLock) {
if (!_addedSincePassBegan && _alive) {
// nothing added since we begun iterating through,
// so we can safely wait for the full period. otoh,
// even if this is true, we might be able to safely
// wait, but it doesn't hurt to loop again.
if (_log.shouldLog(Log.DEBUG))
_log.debug("Wait for activity (up to " + remaining + "ms)");
if (blockUntil < 0)
_nextLock.wait();
else
_nextLock.wait(remaining);
}
}
} catch (InterruptedException ie) {}
}
return null;
}
public void shutdown() {
_alive = false;
synchronized (_nextLock) {
_nextLock.notifyAll();
}
}
private int pickQueue(OutNetMessage message) {
int target = message.getPriority();
for (int i = 0; i < _priorityLimits.length; i++) {
if (_priorityLimits[i] <= target) {
if (i == 0)
return 0;
else
return i - 1;
}
}
return _priorityLimits.length-1;
}
public interface FailedListener {
public void failed(OutNetMessage msg);
}
/**
* Drop expired messages off the queues
*/
private class Expirer implements Runnable {
public void run() {
List removed = new ArrayList(1);
while (_alive) {
long now = _context.clock().now();
for (int i = 0; i < _queue.length; i++) {
synchronized (_queue[i]) {
for (int j = 0; j < _queue[i].size(); j++) {
OutNetMessage m = (OutNetMessage)_queue[i].get(j);
if (m.getExpiration() < now) {
_bytesQueued[i] -= m.getMessageSize();
removed.add(m);
_queue[i].remove(j);
j--;
continue;
}
}
}
}
for (int i = 0; i < removed.size(); i++) {
OutNetMessage m = (OutNetMessage)removed.get(i);
_listener.failed(m);
}
removed.clear();
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
}
}
}
}

View File

@ -0,0 +1,56 @@
package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Properties;
import net.i2p.data.Base64;
import net.i2p.data.RouterAddress;
/**
* basic helper to parse out peer info from a udp address
*/
public class UDPAddress {
private String _host;
private InetAddress _hostAddress;
private int _port;
private byte[] _introKey;
public static final String PROP_PORT = "port";
public static final String PROP_HOST = "host";
public static final String PROP_INTRO_KEY = "key";
public UDPAddress(RouterAddress addr) {
parse(addr);
}
private void parse(RouterAddress addr) {
Properties opts = addr.getOptions();
_host = opts.getProperty(PROP_HOST);
if (_host != null) _host = _host.trim();
try {
String port = opts.getProperty(PROP_PORT);
if (port != null)
_port = Integer.parseInt(port);
} catch (NumberFormatException nfe) {
_port = -1;
}
String key = opts.getProperty(PROP_INTRO_KEY);
if (key != null)
_introKey = Base64.decode(key.trim());
}
public String getHost() { return _host; }
public InetAddress getHostAddress() {
if (_hostAddress == null) {
try {
_hostAddress = InetAddress.getByName(_host);
} catch (UnknownHostException uhe) {
_hostAddress = null;
}
}
return _hostAddress;
}
public int getPort() { return _port; }
public byte[] getIntroKey() { return _introKey; }
}

View File

@ -0,0 +1,80 @@
package net.i2p.router.transport.udp;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
* Coordinate the low level datagram socket, managing the UDPSender and
* UDPReceiver
*/
public class UDPEndpoint {
private RouterContext _context;
private Log _log;
private int _listenPort;
private UDPSender _sender;
private UDPReceiver _receiver;
public UDPEndpoint(RouterContext ctx, int listenPort) throws SocketException {
_context = ctx;
_log = ctx.logManager().getLog(UDPEndpoint.class);
_listenPort = listenPort;
}
public void startup() {
shutdown();
try {
DatagramSocket socket = new DatagramSocket(_listenPort);
_sender = new UDPSender(_context, socket, "UDPSend on " + _listenPort);
_receiver = new UDPReceiver(_context, socket, "UDPReceive on " + _listenPort);
_sender.startup();
_receiver.startup();
} catch (SocketException se) {
if (_log.shouldLog(Log.ERROR))
_log.error("Unable to bind on " + _listenPort);
}
}
public void shutdown() {
if (_sender != null) {
_sender.shutdown();
_receiver.shutdown();
_sender = null;
_receiver = null;
}
}
public void updateListenPort(int newPort) {
if (newPort == _listenPort) return;
try {
DatagramSocket socket = new DatagramSocket(newPort);
_sender.updateListeningPort(socket, newPort);
// note: this closes the old socket, so call this after the sender!
_receiver.updateListeningPort(socket, newPort);
_listenPort = newPort;
} catch (SocketException se) {
if (_log.shouldLog(Log.ERROR))
_log.error("Unable to bind on " + _listenPort);
}
}
public int getListenPort() { return _listenPort; }
public UDPSender getSender() { return _sender; }
/**
* Add the packet to the outobund queue to be sent ASAP (as allowed by
* the bandwidth limiter)
*
* @return number of packets in the send queue
*/
public int send(UDPPacket packet) { return _sender.add(packet); }
/**
* Blocking call to receive the next inbound UDP packet from any peer.
*/
public UDPPacket receive() { return _receiver.receiveNext(); }
}

View File

@ -0,0 +1,113 @@
package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
*
*/
public class UDPEndpointTest {
private RouterContext _context;
private Log _log;
private UDPEndpoint _endpoints[];
private boolean _beginTest;
public UDPEndpointTest(RouterContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(UDPEndpointTest.class);
}
public void runTest(int numPeers) {
RouterContext ctx = new RouterContext(null);
try {
_endpoints = new UDPEndpoint[numPeers];
int base = 2000 + ctx.random().nextInt(10000);
for (int i = 0; i < numPeers; i++) {
_log.debug("Building " + i);
UDPEndpoint endpoint = new UDPEndpoint(ctx, base + i);
_endpoints[i] = endpoint;
endpoint.startup();
I2PThread read = new I2PThread(new TestRead(endpoint), "Test read " + i);
I2PThread write = new I2PThread(new TestWrite(endpoint), "Test write " + i);
//read.setDaemon(true);
read.start();
//write.setDaemon(true);
write.start();
}
} catch (SocketException se) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error initializing", se);
return;
}
_beginTest = true;
_log.debug("Test begin");
}
private class TestRead implements Runnable {
private UDPEndpoint _endpoint;
public TestRead(UDPEndpoint peer) {
_endpoint = peer;
}
public void run() {
while (!_beginTest) {
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
}
_log.debug("Beginning to read");
long start = System.currentTimeMillis();
int received = 0;
while (true) {
UDPPacket packet = _endpoint.receive();
received++;
if (received == 10000) {
long time = System.currentTimeMillis() - start;
_log.debug("Received 10000 in " + time);
}
}
}
}
private class TestWrite implements Runnable {
private UDPEndpoint _endpoint;
public TestWrite(UDPEndpoint peer) {
_endpoint = peer;
}
public void run() {
while (!_beginTest) {
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
}
_log.debug("Beginning to write");
for (int curPacket = 0; curPacket < 10000; curPacket++) {
byte data[] = new byte[1024];
_context.random().nextBytes(data);
int curPeer = (curPacket % _endpoints.length);
if (_endpoints[curPeer] == _endpoint)
curPeer++;
if (curPeer >= _endpoints.length)
curPeer = 0;
short priority = 1;
long expiration = -1;
try {
UDPPacket packet = UDPPacket.acquire(_context);
packet.initialize(priority, expiration, InetAddress.getLocalHost(), _endpoints[curPeer].getListenPort());
packet.writeData(data, 0, 1024);
_endpoint.send(packet);
} catch (UnknownHostException uhe) {
_log.error("foo!", uhe);
}
//if (_log.shouldLog(Log.DEBUG)) {
// _log.debug("Sent to " + _endpoints[curPeer].getListenPort() + " from " + _endpoint.getListenPort());
//}
}
}
}
public static void main(String args[]) {
UDPEndpointTest test = new UDPEndpointTest(new RouterContext(null));
test.runTest(2);
}
}

View File

@ -0,0 +1,188 @@
package net.i2p.router.transport.udp;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**
* Basic delivery unit containing the datagram. This also maintains a cache
* of object instances to allow rapid reuse.
*
*/
public class UDPPacket {
private I2PAppContext _context;
private Log _log;
private DatagramPacket _packet;
private short _priority;
private long _initializeTime;
private long _expiration;
private byte[] _data;
private static final List _packetCache;
static {
_packetCache = new ArrayList(256);
}
private static final boolean CACHE = false;
private static final int MAX_PACKET_SIZE = 2048;
public static final int IV_SIZE = 16;
public static final int MAC_SIZE = 16;
public static final int PAYLOAD_TYPE_SESSION_REQUEST = 0;
public static final int PAYLOAD_TYPE_SESSION_CREATED = 1;
public static final int PAYLOAD_TYPE_SESSION_CONFIRMED = 2;
public static final int PAYLOAD_TYPE_RELAY_REQUEST = 3;
public static final int PAYLOAD_TYPE_RELAY_RESPONSE = 4;
public static final int PAYLOAD_TYPE_RELAY_INTRO = 5;
public static final int PAYLOAD_TYPE_DATA = 6;
// various flag fields for use in the data packets
public static final byte DATA_FLAG_EXPLICIT_ACK = (byte)(1 << 7);
public static final byte DATA_FLAG_EXPLICIT_NACK = (1 << 6);
public static final byte DATA_FLAG_NUMACKS = (1 << 5);
public static final byte DATA_FLAG_ECN = (1 << 4);
public static final byte DATA_FLAG_WANT_ACKS = (1 << 3);
public static final byte DATA_FLAG_WANT_REPLY = (1 << 2);
public static final byte DATA_FLAG_EXTENDED = (1 << 1);
private static final int MAX_VALIDATE_SIZE = MAX_PACKET_SIZE;
private static final ByteCache _validateCache = ByteCache.getInstance(16, MAX_VALIDATE_SIZE);
private static final ByteCache _ivCache = ByteCache.getInstance(16, IV_SIZE);
private UDPPacket(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(UDPPacket.class);
_data = new byte[MAX_PACKET_SIZE];
_packet = new DatagramPacket(_data, MAX_PACKET_SIZE);
_initializeTime = _context.clock().now();
}
public void initialize(short priority, long expiration, InetAddress host, int port) {
_priority = priority;
_expiration = expiration;
resetBegin();
Arrays.fill(_data, (byte)0x00);
_packet.setLength(0);
_packet.setAddress(host);
_packet.setPort(port);
}
public void writeData(byte src[], int offset, int len) {
System.arraycopy(src, offset, _data, 0, len);
_packet.setLength(len);
resetBegin();
}
public DatagramPacket getPacket() { return _packet; }
public short getPriority() { return _priority; }
public long getExpiration() { return _expiration; }
public long getLifetime() { return _context.clock().now() - _initializeTime; }
public void resetBegin() { _initializeTime = _context.clock().now(); }
/**
* Validate the packet against the MAC specified, returning true if the
* MAC matches, false otherwise.
*
*/
public boolean validate(SessionKey macKey) {
boolean eq = false;
ByteArray buf = _validateCache.acquire();
// validate by comparing _data[0:15] and
// HMAC(payload + IV + payloadLength, macKey)
int payloadLength = _packet.getLength() - MAC_SIZE - IV_SIZE;
if (payloadLength > 0) {
int off = 0;
System.arraycopy(_data, _packet.getOffset() + MAC_SIZE + IV_SIZE, buf.getData(), off, payloadLength);
off += payloadLength;
System.arraycopy(_data, _packet.getOffset() + MAC_SIZE, buf.getData(), off, IV_SIZE);
off += IV_SIZE;
DataHelper.toLong(buf.getData(), off, 2, payloadLength);
off += 2;
Hash calculated = _context.hmac().calculate(macKey, buf.getData(), 0, off);
if (_log.shouldLog(Log.DEBUG)) {
StringBuffer str = new StringBuffer(128);
str.append(_packet.getLength()).append(" byte packet received, payload length ");
str.append(payloadLength);
str.append("\nIV: ").append(Base64.encode(buf.getData(), payloadLength, IV_SIZE));
str.append("\nIV2: ").append(Base64.encode(_data, MAC_SIZE, IV_SIZE));
str.append("\nlen: ").append(DataHelper.fromLong(buf.getData(), payloadLength + IV_SIZE, 2));
str.append("\nMAC key: ").append(macKey.toBase64());
str.append("\ncalc HMAC: ").append(calculated.toBase64());
str.append("\nread HMAC: ").append(Base64.encode(_data, _packet.getOffset(), MAC_SIZE));
str.append("\nraw: ").append(Base64.encode(_data, _packet.getOffset(), _packet.getLength()));
_log.debug(str.toString());
}
eq = DataHelper.eq(calculated.getData(), 0, _data, _packet.getOffset(), MAC_SIZE);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Payload length is " + payloadLength);
}
_validateCache.release(buf);
return eq;
}
/**
* Decrypt this valid packet, overwriting the _data buffer's payload
* with the decrypted data (leaving the MAC and IV unaltered)
*
*/
public void decrypt(SessionKey cipherKey) {
ByteArray iv = _ivCache.acquire();
System.arraycopy(_data, MAC_SIZE, iv.getData(), 0, IV_SIZE);
_context.aes().decrypt(_data, _packet.getOffset() + MAC_SIZE + IV_SIZE, _data, _packet.getOffset() + MAC_SIZE + IV_SIZE, cipherKey, iv.getData(), _packet.getLength() - MAC_SIZE - IV_SIZE);
_ivCache.release(iv);
}
public String toString() {
StringBuffer buf = new StringBuffer(64);
buf.append(_packet.getLength());
buf.append(" byte packet with ");
buf.append(_packet.getAddress().getHostAddress()).append(":");
buf.append(_packet.getPort());
return buf.toString();
}
public static UDPPacket acquire(I2PAppContext ctx) {
if (CACHE) {
synchronized (_packetCache) {
if (_packetCache.size() > 0) {
UDPPacket rv = (UDPPacket)_packetCache.remove(0);
rv._context = ctx;
rv._log = ctx.logManager().getLog(UDPPacket.class);
rv.resetBegin();
Arrays.fill(rv._data, (byte)0x00);
return rv;
}
}
}
return new UDPPacket(ctx);
}
public void release() {
if (!CACHE) return;
synchronized (_packetCache) {
_packet.setLength(0);
_packet.setPort(1);
if (_packetCache.size() <= 64)
_packetCache.add(this);
}
}
}

View File

@ -0,0 +1,448 @@
package net.i2p.router.transport.udp;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.data.Signature;
import net.i2p.util.Log;
/**
* To read a packet, initialize this reader with the data and fetch out
* the appropriate fields. If the interesting bits are in message specific
* elements, grab the appropriate subreader.
*
*/
public class UDPPacketReader {
private I2PAppContext _context;
private Log _log;
private byte _message[];
private int _payloadBeginOffset;
private int _payloadLength;
private SessionRequestReader _sessionRequestReader;
private SessionCreatedReader _sessionCreatedReader;
private SessionConfirmedReader _sessionConfirmedReader;
private DataReader _dataReader;
private static final int KEYING_MATERIAL_LENGTH = 64;
public UDPPacketReader(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(UDPPacketReader.class);
_sessionRequestReader = new SessionRequestReader();
_sessionCreatedReader = new SessionCreatedReader();
_sessionConfirmedReader = new SessionConfirmedReader();
_dataReader = new DataReader();
}
public void initialize(UDPPacket packet) {
int off = packet.getPacket().getOffset();
int len = packet.getPacket().getLength();
off += UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
len -= UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
initialize(packet.getPacket().getData(), off, len);
}
public void initialize(byte message[], int payloadOffset, int payloadLength) {
_message = message;
_payloadBeginOffset = payloadOffset;
_payloadLength = payloadLength;
}
/** what type of payload is in here? */
public int readPayloadType() {
// 3 highest order bits == payload type
return _message[_payloadBeginOffset] >>> 4;
}
/** does this packet include rekeying data? */
public boolean readRekeying() {
return (_message[_payloadBeginOffset] & (1 << 3)) != 0;
}
public boolean readExtendedOptionsIncluded() {
return (_message[_payloadBeginOffset] & (1 << 2)) != 0;
}
public long readTimestamp() {
return DataHelper.fromLong(_message, _payloadBeginOffset + 1, 4);
}
public void readKeyingMaterial(byte target[], int targetOffset) {
if (!readRekeying())
throw new IllegalStateException("This packet is not rekeying!");
System.arraycopy(_message, _payloadBeginOffset + 1 + 4, target, targetOffset, KEYING_MATERIAL_LENGTH);
}
/** index into the message where the body begins */
private int readBodyOffset() {
int offset = _payloadBeginOffset + 1 + 4;
if (readRekeying())
offset += KEYING_MATERIAL_LENGTH;
if (readExtendedOptionsIncluded()) {
int optionsSize = (int)DataHelper.fromLong(_message, offset, 1);
offset += optionsSize + 1;
}
return offset;
}
public SessionRequestReader getSessionRequestReader() { return _sessionRequestReader; }
public SessionCreatedReader getSessionCreatedReader() { return _sessionCreatedReader; }
public SessionConfirmedReader getSessionConfirmedReader() { return _sessionConfirmedReader; }
public DataReader getDataReader() { return _dataReader; }
public String toString() {
switch (readPayloadType()) {
case UDPPacket.PAYLOAD_TYPE_DATA:
return _dataReader.toString();
case UDPPacket.PAYLOAD_TYPE_SESSION_CONFIRMED:
return "Session confirmed packet";
case UDPPacket.PAYLOAD_TYPE_SESSION_CREATED:
return "Session created packet";
case UDPPacket.PAYLOAD_TYPE_SESSION_REQUEST:
return "Session request packet";
default:
return "Other packet type...";
}
}
/** Help read the SessionRequest payload */
public class SessionRequestReader {
public static final int X_LENGTH = 256;
public void readX(byte target[], int targetOffset) {
int readOffset = readBodyOffset();
System.arraycopy(_message, readOffset, target, targetOffset, X_LENGTH);
}
public int readIPSize() {
int offset = readBodyOffset() + X_LENGTH;
return (int)DataHelper.fromLong(_message, offset, 1);
}
/** what IP bob is reachable on */
public void readIP(byte target[], int targetOffset) {
int offset = readBodyOffset() + X_LENGTH;
int size = (int)DataHelper.fromLong(_message, offset, 1);
offset++;
System.arraycopy(_message, offset, target, targetOffset, size);
}
}
/** Help read the SessionCreated payload */
public class SessionCreatedReader {
public static final int Y_LENGTH = 256;
public void readY(byte target[], int targetOffset) {
int readOffset = readBodyOffset();
System.arraycopy(_message, readOffset, target, targetOffset, Y_LENGTH);
}
/** sizeof(IP) */
public int readIPSize() {
int offset = readBodyOffset() + Y_LENGTH;
return (int)DataHelper.fromLong(_message, offset, 1);
}
/** what IP do they think we are coming on? */
public void readIP(byte target[], int targetOffset) {
int offset = readBodyOffset() + Y_LENGTH;
int size = (int)DataHelper.fromLong(_message, offset, 1);
offset++;
System.arraycopy(_message, offset, target, targetOffset, size);
}
/** what port do they think we are coming from? */
public int readPort() {
int offset = readBodyOffset() + Y_LENGTH + 1 + readIPSize();
return (int)DataHelper.fromLong(_message, offset, 2);
}
/** write out the 4 byte relayAs tag */
public long readRelayTag() {
int offset = readBodyOffset() + Y_LENGTH + 1 + readIPSize() + 2;
return DataHelper.fromLong(_message, offset, 4);
}
public long readSignedOnTime() {
int offset = readBodyOffset() + Y_LENGTH + 1 + readIPSize() + 2 + 4;
long rv = DataHelper.fromLong(_message, offset, 4);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Signed on time offset: " + offset + " val: " + rv
+ "\nRawCreated: " + Base64.encode(_message, _payloadBeginOffset, _payloadLength));
return rv;
}
public void readEncryptedSignature(byte target[], int targetOffset) {
int offset = readBodyOffset() + Y_LENGTH + 1 + readIPSize() + 2 + 4 + 4;
System.arraycopy(_message, offset, target, targetOffset, Signature.SIGNATURE_BYTES + 8);
}
public void readIV(byte target[], int targetOffset) {
int offset = _payloadBeginOffset - UDPPacket.IV_SIZE;
System.arraycopy(_message, offset, target, targetOffset, UDPPacket.IV_SIZE);
}
}
/** parse out the confirmed message */
public class SessionConfirmedReader {
/** which fragment is this? */
public int readCurrentFragmentNum() {
int readOffset = readBodyOffset();
return _message[readOffset] >>> 4;
}
/** how many fragments will there be? */
public int readTotalFragmentNum() {
int readOffset = readBodyOffset();
return (_message[readOffset] & 0xF);
}
public int readCurrentFragmentSize() {
int readOffset = readBodyOffset() + 1;
return (int)DataHelper.fromLong(_message, readOffset, 2);
}
/** read the fragment data from the nonterminal sessionConfirmed packet */
public void readFragmentData(byte target[], int targetOffset) {
int readOffset = readBodyOffset() + 1 + 2;
int len = readCurrentFragmentSize();
System.arraycopy(_message, readOffset, target, targetOffset, len);
}
/** read the time at which the signature was generated */
public long readFinalFragmentSignedOnTime() {
if (readCurrentFragmentNum() != readTotalFragmentNum()-1)
throw new IllegalStateException("This is not the final fragment");
int readOffset = readBodyOffset() + 1 + 2 + readCurrentFragmentSize();
return DataHelper.fromLong(_message, readOffset, 4);
}
/** read the signature from the final sessionConfirmed packet */
public void readFinalSignature(byte target[], int targetOffset) {
if (readCurrentFragmentNum() != readTotalFragmentNum()-1)
throw new IllegalStateException("This is not the final fragment");
int readOffset = _payloadBeginOffset + _payloadLength - Signature.SIGNATURE_BYTES;
System.arraycopy(_message, readOffset, target, targetOffset, Signature.SIGNATURE_BYTES);
}
}
/** parse out the data message */
public class DataReader {
public boolean readACKsIncluded() {
return flagSet(UDPPacket.DATA_FLAG_EXPLICIT_ACK);
}
public boolean readNACKsIncluded() {
return flagSet(UDPPacket.DATA_FLAG_EXPLICIT_NACK);
}
public boolean readNumACKsIncluded() {
return flagSet(UDPPacket.DATA_FLAG_NUMACKS);
}
public boolean readECN() {
return flagSet(UDPPacket.DATA_FLAG_ECN);
}
public boolean readWantPreviousACKs() {
return flagSet(UDPPacket.DATA_FLAG_WANT_ACKS);
}
public boolean readReplyRequested() {
return flagSet(UDPPacket.DATA_FLAG_WANT_REPLY);
}
public boolean readExtendedDataIncluded() {
return flagSet(UDPPacket.DATA_FLAG_EXTENDED);
}
public long[] readACKs() {
if (!readACKsIncluded()) return null;
int off = readBodyOffset() + 1;
int num = (int)DataHelper.fromLong(_message, off, 1);
off++;
long rv[] = new long[num];
for (int i = 0; i < num; i++) {
rv[i] = DataHelper.fromLong(_message, off, 4);
off += 4;
}
return rv;
}
public long[] readNACKs() {
if (!readNACKsIncluded()) return null;
int off = readBodyOffset() + 1;
if (readACKsIncluded()) {
int numACKs = (int)DataHelper.fromLong(_message, off, 1);
off++;
off += 4 * numACKs;
}
int numNACKs = (int)DataHelper.fromLong(_message, off, 1);
off++;
long rv[] = new long[numNACKs];
for (int i = 0; i < numNACKs; i++) {
rv[i] = DataHelper.fromLong(_message, off, 4);
off += 4;
}
return rv;
}
public int readNumACKs() {
if (!readNumACKsIncluded()) return -1;
int off = readBodyOffset() + 1;
if (readACKsIncluded()) {
int numACKs = (int)DataHelper.fromLong(_message, off, 1);
off++;
off += 4 * numACKs;
}
if (readNACKsIncluded()) {
int numNACKs = (int)DataHelper.fromLong(_message, off, 1);
off++;
off += 4 * numNACKs;
}
return (int)DataHelper.fromLong(_message, off, 2);
}
public int readFragmentCount() {
int off = readBodyOffset() + 1;
if (readACKsIncluded()) {
int numACKs = (int)DataHelper.fromLong(_message, off, 1);
off++;
off += 4 * numACKs;
}
if (readNACKsIncluded()) {
int numNACKs = (int)DataHelper.fromLong(_message, off, 1);
off++;
off += 4 * numNACKs;
}
if (readNumACKsIncluded())
off += 2;
if (readExtendedDataIncluded()) {
int size = (int)DataHelper.fromLong(_message, off, 1);
off++;
off += size;
}
return (int)_message[off];
}
public long readMessageId(int fragmentNum) {
int fragmentBegin = getFragmentBegin(fragmentNum);
return DataHelper.fromLong(_message, fragmentBegin, 4);
}
public int readMessageFragmentNum(int fragmentNum) {
int off = getFragmentBegin(fragmentNum);
off += 4; // messageId
return _message[off] >>> 3;
}
public boolean readMessageIsLast(int fragmentNum) {
int off = getFragmentBegin(fragmentNum);
off += 4; // messageId
return ((_message[off] & (1 << 2)) != 0);
}
public int readMessageFragmentSize(int fragmentNum) {
int off = getFragmentBegin(fragmentNum);
off += 4; // messageId
off++; // fragment info
return (int)DataHelper.fromLong(_message, off, 2);
}
public void readMessageFragment(int fragmentNum, byte target[], int targetOffset) {
int off = getFragmentBegin(fragmentNum);
off += 4; // messageId
off++; // fragment info
int size = (int)DataHelper.fromLong(_message, off, 2);
off += 2;
System.arraycopy(_message, off, target, targetOffset, size);
}
private int getFragmentBegin(int fragmentNum) {
int off = readBodyOffset() + 1;
if (readACKsIncluded()) {
int numACKs = (int)DataHelper.fromLong(_message, off, 1);
off++;
off += 4 * numACKs;
}
if (readNACKsIncluded()) {
int numNACKs = (int)DataHelper.fromLong(_message, off, 1);
off++;
off += 5 * numNACKs;
}
if (readNumACKsIncluded())
off += 2;
if (readExtendedDataIncluded()) {
int size = (int)DataHelper.fromLong(_message, off, 1);
off++;
off += size;
}
off++; // # fragments
if (fragmentNum == 0) {
return off;
} else {
for (int i = 0; i < fragmentNum; i++) {
off += 5; // messageId+info
off += (int)DataHelper.fromLong(_message, off, 2);
off += 2;
}
return off;
}
}
private boolean flagSet(byte flag) {
int flagOffset = readBodyOffset();
return ((_message[flagOffset] & flag) != 0);
}
public String toString() {
StringBuffer buf = new StringBuffer(256);
long msAgo = _context.clock().now() - readTimestamp()*1000;
buf.append("Data packet sent ").append(msAgo).append("ms ago ");
buf.append("IV ");
buf.append(Base64.encode(_message, _payloadBeginOffset-UDPPacket.IV_SIZE, UDPPacket.IV_SIZE));
buf.append(" ");
int off = readBodyOffset() + 1;
if (readACKsIncluded()) {
int numACKs = (int)DataHelper.fromLong(_message, off, 1);
off++;
buf.append("with ACKs for ");
for (int i = 0; i < numACKs; i++) {
buf.append(DataHelper.fromLong(_message, off, 4)).append(' ');
off += 4;
}
}
if (readNACKsIncluded()) {
int numNACKs = (int)DataHelper.fromLong(_message, off, 1);
off++;
buf.append("with NACKs for ");
for (int i = 0; i < numNACKs; i++) {
buf.append(DataHelper.fromLong(_message, off, 4)).append(' ');
off += 5;
}
off += 5 * numNACKs;
}
if (readNumACKsIncluded()) {
buf.append("with numACKs of ");
buf.append(DataHelper.fromLong(_message, off, 2));
buf.append(' ');
off += 2;
}
if (readExtendedDataIncluded()) {
int size = (int)DataHelper.fromLong(_message, off, 1);
off++;
buf.append("with extended size of ");
buf.append(size);
buf.append(' ');
off += size;
}
int numFragments = (int)DataHelper.fromLong(_message, off, 1);
off++;
buf.append("with fragmentCount of ");
buf.append(numFragments);
buf.append(' ');
for (int i = 0; i < numFragments; i++) {
buf.append("containing messageId ");
buf.append(DataHelper.fromLong(_message, off, 4));
off += 5; // messageId+info
int size = (int)DataHelper.fromLong(_message, off, 2);
buf.append(" with ").append(size).append(" bytes");
buf.append(' ');
off += size;
off += 2;
}
return buf.toString();
}
}
}

View File

@ -0,0 +1,166 @@
package net.i2p.router.transport.udp;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.util.ArrayList;
import java.util.List;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Lowest level component to pull raw UDP datagrams off the wire as fast
* as possible, controlled by both the bandwidth limiter and the router's
* throttle. If the inbound queue gets too large or packets have been
* waiting around too long, they are dropped. Packets should be pulled off
* from the queue ASAP by a {@link PacketHandler}
*
*/
public class UDPReceiver {
private RouterContext _context;
private Log _log;
private DatagramSocket _socket;
private String _name;
private List _inboundQueue;
private boolean _keepRunning;
private Runner _runner;
public UDPReceiver(RouterContext ctx, DatagramSocket socket, String name) {
_context = ctx;
_log = ctx.logManager().getLog(UDPReceiver.class);
_name = name;
_inboundQueue = new ArrayList(128);
_socket = socket;
_runner = new Runner();
_context.statManager().createRateStat("udp.receivePacketSize", "How large packets received are", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_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 });
}
public void startup() {
_keepRunning = true;
I2PThread t = new I2PThread(_runner, _name);
t.setDaemon(true);
t.start();
}
public void shutdown() {
_keepRunning = false;
synchronized (_inboundQueue) {
_inboundQueue.clear();
_inboundQueue.notifyAll();
}
}
/**
* Replace the old listen port with the new one, returning the old.
* NOTE: this closes the old socket so that blocking calls unblock!
*
*/
public DatagramSocket updateListeningPort(DatagramSocket socket, int newPort) {
return _runner.updateListeningPort(socket, newPort);
}
/** if a packet been sitting in the queue for 2 seconds, drop subsequent packets */
private static final long MAX_QUEUE_PERIOD = 2*1000;
private void receive(UDPPacket packet) {
synchronized (_inboundQueue) {
int queueSize = _inboundQueue.size();
if (queueSize > 0) {
long headPeriod = ((UDPPacket)_inboundQueue.get(0)).getLifetime();
if (headPeriod > MAX_QUEUE_PERIOD) {
_context.statManager().addRateData("udp.droppedInbound", queueSize, headPeriod);
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping inbound packet with " + queueSize + " queued for " + headPeriod);
_inboundQueue.notifyAll();
return;
}
}
_inboundQueue.add(packet);
_inboundQueue.notifyAll();
}
}
/**
* Blocking call to retrieve the next inbound packet, or null if we have
* shut down.
*
*/
public UDPPacket receiveNext() {
while (_keepRunning) {
synchronized (_inboundQueue) {
if (_inboundQueue.size() <= 0) {
try {
_inboundQueue.wait();
} catch (InterruptedException ie) {}
}
if (_inboundQueue.size() > 0)
return (UDPPacket)_inboundQueue.remove(0);
}
}
return null;
}
private class Runner implements Runnable {
private boolean _socketChanged;
public void run() {
_socketChanged = false;
while (_keepRunning) {
if (_socketChanged) {
Thread.currentThread().setName(_name);
_socketChanged = false;
}
UDPPacket packet = UDPPacket.acquire(_context);
// block before we read...
while (!_context.throttle().acceptNetworkMessage())
try { Thread.sleep(10); } catch (InterruptedException ie) {}
try {
synchronized (Runner.this) {
_socket.receive(packet.getPacket());
}
int size = packet.getPacket().getLength();
packet.resetBegin();
_context.statManager().addRateData("udp.receivePacketSize", size, 0);
// and block after we know how much we read but before
// we release the packet to the inbound queue
if (size > 0) {
FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestInbound(size, "UDP receiver");
while (req.getPendingInboundRequested() > 0)
req.waitForNextAllocation();
}
receive(packet);
} catch (IOException ioe) {
if (_socketChanged) {
if (_log.shouldLog(Log.INFO))
_log.info("Changing ports...");
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Error receiving", ioe);
}
packet.release();
}
}
}
public DatagramSocket updateListeningPort(DatagramSocket socket, int newPort) {
_name = "UDPReceive on " + newPort;
DatagramSocket old = null;
synchronized (Runner.this) {
old = _socket;
_socket = socket;
}
_socketChanged = true;
// ok, its switched, now lets break any blocking calls
old.close();
return old;
}
}
}

View File

@ -0,0 +1,174 @@
package net.i2p.router.transport.udp;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.util.ArrayList;
import java.util.List;
import net.i2p.data.Base64;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Lowest level packet sender, pushes anything on its queue ASAP.
*
*/
public class UDPSender {
private RouterContext _context;
private Log _log;
private DatagramSocket _socket;
private String _name;
private List _outboundQueue;
private boolean _keepRunning;
private Runner _runner;
private static final int MAX_QUEUED = 64;
public UDPSender(RouterContext ctx, DatagramSocket socket, String name) {
_context = ctx;
_log = ctx.logManager().getLog(UDPSender.class);
_outboundQueue = new ArrayList(128);
_socket = socket;
_runner = new Runner();
_name = name;
_context.statManager().createRateStat("udp.pushTime", "How long a UDP packet takes to get pushed out", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("udp.sendPacketSize", "How large packets sent are", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
}
public void startup() {
_keepRunning = true;
I2PThread t = new I2PThread(_runner, _name);
t.setDaemon(true);
t.start();
}
public void shutdown() {
_keepRunning = false;
synchronized (_outboundQueue) {
_outboundQueue.clear();
_outboundQueue.notifyAll();
}
}
public DatagramSocket updateListeningPort(DatagramSocket socket, int newPort) {
return _runner.updateListeningPort(socket, newPort);
}
/**
* Add the packet to the queue. This may block until there is space
* available, if requested, otherwise it returns immediately
*
* @return number of packets queued
*/
public int add(UDPPacket packet, boolean blocking) {
int remaining = -1;
while ( (_keepRunning) && (remaining < 0) ) {
try {
synchronized (_outboundQueue) {
if (_outboundQueue.size() < MAX_QUEUED) {
_outboundQueue.add(packet);
remaining = _outboundQueue.size();
_outboundQueue.notifyAll();
} else {
if (blocking) {
_outboundQueue.wait();
} else {
remaining = _outboundQueue.size();
}
}
}
} catch (InterruptedException ie) {}
}
return remaining;
}
/**
*
* @return number of packets in the queue
*/
public int add(UDPPacket packet) {
int size = 0;
synchronized (_outboundQueue) {
_outboundQueue.add(packet);
size = _outboundQueue.size();
_outboundQueue.notifyAll();
}
return size;
}
private class Runner implements Runnable {
private boolean _socketChanged;
public void run() {
_socketChanged = false;
while (_keepRunning) {
if (_socketChanged) {
Thread.currentThread().setName(_name);
_socketChanged = false;
}
UDPPacket packet = getNextPacket();
if (packet != null) {
int size = packet.getPacket().getLength();
if (size > 0) {
FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestOutbound(size, "UDP sender");
while (req.getPendingOutboundRequested() > 0)
req.waitForNextAllocation();
}
if (_log.shouldLog(Log.DEBUG)) {
int len = packet.getPacket().getLength();
//if (len > 128)
// len = 128;
_log.debug("Sending packet: \nraw: " + Base64.encode(packet.getPacket().getData(), 0, len));
}
try {
synchronized (Runner.this) {
// synchronization lets us update safely
_socket.send(packet.getPacket());
}
_context.statManager().addRateData("udp.pushTime", packet.getLifetime(), packet.getLifetime());
_context.statManager().addRateData("udp.sendPacketSize", packet.getPacket().getLength(), packet.getLifetime());
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error sending", ioe);
}
// back to the cache
//packet.release();
}
}
}
private UDPPacket getNextPacket() {
UDPPacket packet = null;
while ( (_keepRunning) && (packet == null) ) {
try {
synchronized (_outboundQueue) {
if (_outboundQueue.size() <= 0) {
_outboundQueue.wait();
} else {
packet = (UDPPacket)_outboundQueue.remove(0);
_outboundQueue.notifyAll();
}
}
} catch (InterruptedException ie) {}
}
return packet;
}
public DatagramSocket updateListeningPort(DatagramSocket socket, int newPort) {
_name = "UDPSend on " + newPort;
DatagramSocket old = null;
synchronized (Runner.this) {
old = _socket;
_socket = socket;
}
_socketChanged = true;
return old;
}
}
}

View File

@ -0,0 +1,546 @@
package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.data.SessionKey;
import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.Transport;
import net.i2p.router.transport.TransportImpl;
import net.i2p.router.transport.TransportBid;
import net.i2p.util.Log;
/**
*
*/
public class UDPTransport extends TransportImpl implements TimedWeightedPriorityMessageQueue.FailedListener {
private RouterContext _context;
private Log _log;
private UDPEndpoint _endpoint;
/** Peer (Hash) to PeerState */
private Map _peersByIdent;
/** Remote host (ip+port as a string) to PeerState */
private Map _peersByRemoteHost;
/** Relay tag (base64 String) to PeerState */
private Map _peersByRelayTag;
private PacketHandler _handler;
private EstablishmentManager _establisher;
private MessageQueue _outboundMessages;
private OutboundMessageFragments _fragments;
private OutboundRefiller _refiller;
private PacketPusher _pusher;
private InboundMessageFragments _inboundFragments;
/** 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 */
private int _externalListenPort;
/** IP address of externally reachable host, or null */
private InetAddress _externalListenHost;
/** introduction key */
private SessionKey _introKey;
/** shared fast bid for connected peers */
private TransportBid _fastBid;
/** shared slow bid for unconnected peers */
private TransportBid _slowBid;
public static final String STYLE = "udp";
public static final String PROP_INTERNAL_PORT = "i2np.udp.internalPort";
/** define this to explicitly set an external IP address */
public static final String PROP_EXTERNAL_HOST = "i2np.udp.host";
/** define this to explicitly set an external port */
public static final String PROP_EXTERNAL_PORT = "i2np.udp.port";
/** how many relays offered to us will we use at a time? */
public static final int PUBLIC_RELAY_COUNT = 3;
/** configure the priority queue with the given split points */
private static final int PRIORITY_LIMITS[] = new int[] { 100, 200, 300, 400, 500, 1000 };
/** configure the priority queue with the given weighting per priority group */
private static final int PRIORITY_WEIGHT[] = new int[] { 1, 1, 1, 1, 1, 2 };
public UDPTransport(RouterContext ctx) {
super(ctx);
_context = ctx;
_log = ctx.logManager().getLog(UDPTransport.class);
_peersByIdent = new HashMap(128);
_peersByRemoteHost = new HashMap(128);
_peersByRelayTag = new HashMap(128);
_endpoint = null;
_outboundMessages = new TimedWeightedPriorityMessageQueue(ctx, PRIORITY_LIMITS, PRIORITY_WEIGHT, this);
_relayPeers = new ArrayList(1);
_fastBid = new SharedBid(50);
_slowBid = new SharedBid(100);
_fragments = new OutboundMessageFragments(_context, this);
_inboundFragments = new InboundMessageFragments(_context, _fragments, this);
}
public void startup() {
if (_fragments != null)
_fragments.shutdown();
if (_pusher != null)
_pusher.shutdown();
if (_handler != null)
_handler.shutdown();
if (_endpoint != null)
_endpoint.shutdown();
if (_establisher != null)
_establisher.shutdown();
if (_refiller != null)
_refiller.shutdown();
if (_inboundFragments != null)
_inboundFragments.shutdown();
_introKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
System.arraycopy(_context.routerHash().getData(), 0, _introKey.getData(), 0, SessionKey.KEYSIZE_BYTES);
rebuildExternalAddress();
if (_endpoint == null) {
int port = -1;
if (_externalListenPort <= 0) {
// no explicit external port, so lets try an internal one
String portStr = _context.getProperty(PROP_INTERNAL_PORT);
if (portStr != null) {
try {
port = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Invalid port specified [" + portStr + "]");
}
}
if (port <= 0) {
port = 1024 + _context.random().nextInt(31*1024);
if (_log.shouldLog(Log.INFO))
_log.info("Selecting a random port to bind to: " + port);
}
} else {
port = _externalListenPort;
if (_log.shouldLog(Log.INFO))
_log.info("Binding to the explicitly specified external port: " + port);
}
try {
_endpoint = new UDPEndpoint(_context, port);
} catch (SocketException se) {
if (_log.shouldLog(Log.CRIT))
_log.log(Log.CRIT, "Unable to listen on the UDP port (" + port + ")", se);
return;
}
}
if (_establisher == null)
_establisher = new EstablishmentManager(_context, this);
if (_handler == null)
_handler = new PacketHandler(_context, this, _endpoint, _establisher, _inboundFragments);
if (_refiller == null)
_refiller = new OutboundRefiller(_context, _fragments, _outboundMessages);
_endpoint.startup();
_establisher.startup();
_handler.startup();
_fragments.startup();
_inboundFragments.startup();
_pusher = new PacketPusher(_context, _fragments, _endpoint.getSender());
_pusher.startup();
_refiller.startup();
}
public void shutdown() {
if (_refiller != null)
_refiller.shutdown();
if (_handler != null)
_handler.shutdown();
if (_endpoint != null)
_endpoint.shutdown();
if (_fragments != null)
_fragments.shutdown();
if (_pusher != null)
_pusher.shutdown();
if (_establisher != null)
_establisher.shutdown();
if (_inboundFragments != null)
_inboundFragments.shutdown();
}
/**
* Introduction key that people should use to contact us
*
*/
public SessionKey getIntroKey() { return _introKey; }
public int getLocalPort() { return _externalListenPort; }
public InetAddress getLocalAddress() { return _externalListenHost; }
public int getExternalPort() { return _externalListenPort; }
/**
* Someone we tried to contact gave us what they think our IP address is.
* Right now, we just blindly trust them, changing our IP and port on a
* whim. this is not good ;)
*
*/
void externalAddressReceived(byte ourIP[], int ourPort) {
if (_log.shouldLog(Log.WARN))
_log.debug("External address received: " + Base64.encode(ourIP) + ":" + ourPort);
if (explicitAddressSpecified())
return;
synchronized (this) {
if ( (_externalListenHost == null) ||
(!eq(_externalListenHost.getAddress(), _externalListenPort, ourIP, ourPort)) ) {
try {
_externalListenHost = InetAddress.getByAddress(ourIP);
_externalListenPort = ourPort;
rebuildExternalAddress();
replaceAddress(_externalAddress);
} catch (UnknownHostException uhe) {
_externalListenHost = null;
}
}
}
}
private static final boolean eq(byte laddr[], int lport, byte raddr[], int rport) {
return (rport == lport) && DataHelper.eq(laddr, raddr);
}
/**
* get the state for the peer at the given remote host/port, or null
* if no state exists
*/
public PeerState getPeerState(InetAddress remoteHost, int remotePort) {
String hostInfo = PeerState.calculateRemoteHostString(remoteHost.getAddress(), remotePort);
synchronized (_peersByRemoteHost) {
return (PeerState)_peersByRemoteHost.get(hostInfo);
}
}
/**
* get the state for the peer with the given ident, or null
* if no state exists
*/
public PeerState getPeerState(Hash remotePeer) {
synchronized (_peersByIdent) {
return (PeerState)_peersByIdent.get(remotePeer);
}
}
/**
* 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);
}
}
/**
* add the peer info, returning true if it went in properly, false if
* it was rejected (causes include peer ident already connected, or no
* remote host info known
*
*/
boolean addRemotePeerState(PeerState peer) {
if (_log.shouldLog(Log.WARN))
_log.debug("Add remote peer state: " + peer);
if (peer.getRemotePeer() != null) {
synchronized (_peersByIdent) {
PeerState oldPeer = (PeerState)_peersByIdent.put(peer.getRemotePeer(), peer);
if ( (oldPeer != null) && (oldPeer != peer) ) {
_peersByIdent.put(oldPeer.getRemotePeer(), oldPeer);
return false;
}
}
}
String remoteString = peer.getRemoteHostString();
if (remoteString == null) return false;
synchronized (_peersByRemoteHost) {
PeerState oldPeer = (PeerState)_peersByRemoteHost.put(remoteString, peer);
if ( (oldPeer != null) && (oldPeer != peer) ) {
_peersByRemoteHost.put(remoteString, oldPeer);
return false;
}
}
_context.shitlist().unshitlistRouter(peer.getRemotePeer());
return true;
}
int send(UDPPacket packet) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending packet " + packet);
return _endpoint.send(packet);
}
public TransportBid bid(RouterInfo toAddress, long dataSize) {
Hash to = toAddress.getIdentity().calculateHash();
PeerState peer = getPeerState(to);
if (peer != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("bidding on a message to an established peer: " + peer);
return _fastBid;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("bidding on a message to an unestablished peer: " + to.toBase64());
return _slowBid;
}
}
public String getStyle() { return STYLE; }
public void send(OutNetMessage msg) {
Hash to = msg.getTarget().getIdentity().calculateHash();
if (getPeerState(to) != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending outbound message to an established peer: " + to.toBase64());
_outboundMessages.add(msg);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending outbound message to an unestablished peer: " + to.toBase64());
_establisher.establish(msg);
}
}
void send(I2NPMessage msg, PeerState peer) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Injecting a data message to a new peer: " + peer);
OutboundMessageState state = new OutboundMessageState(_context);
state.initialize(msg, peer);
_fragments.add(state);
}
public OutNetMessage getNextMessage() { return getNextMessage(-1); }
/**
* Get the next message, blocking until one is found or the expiration
* reached.
*
* @param blockUntil expiration, or -1 if indefinite
*/
public OutNetMessage getNextMessage(long blockUntil) {
return _outboundMessages.getNext(blockUntil);
}
// we don't need the following, since we have our own queueing
protected void outboundMessageReady() { throw new UnsupportedOperationException("Not used for UDP"); }
public RouterAddress startListening() {
startup();
return _externalAddress;
}
public void stopListening() {
shutdown();
}
void setExternalListenPort(int port) { _externalListenPort = port; }
void setExternalListenHost(InetAddress addr) { _externalListenHost = addr; }
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() {
if (explicitAddressSpecified()) {
try {
String host = _context.getProperty(PROP_EXTERNAL_HOST);
String port = _context.getProperty(PROP_EXTERNAL_PORT);
_externalListenHost = InetAddress.getByName(host);
_externalListenPort = Integer.parseInt(port);
} catch (UnknownHostException uhe) {
_externalListenHost = null;
} catch (NumberFormatException nfe) {
_externalListenPort = -1;
}
}
Properties options = new Properties();
if ( (_externalListenPort > 0) && (_externalListenHost != null) ) {
options.setProperty(UDPAddress.PROP_PORT, String.valueOf(_externalListenPort));
options.setProperty(UDPAddress.PROP_HOST, _externalListenHost.getHostAddress());
} 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;
}
options.setProperty(UDPAddress.PROP_INTRO_KEY, _introKey.toBase64());
RouterAddress addr = new RouterAddress();
addr.setCost(5);
addr.setExpiration(null);
addr.setTransportStyle(STYLE);
addr.setOptions(options);
_externalAddress = addr;
replaceAddress(addr);
}
public void failed(OutNetMessage msg) {
if (msg == null) return;
if (_log.shouldLog(Log.WARN))
_log.warn("Sending message failed: " + msg, new Exception("failed from"));
super.afterSend(msg, false);
}
public void succeeded(OutNetMessage msg) {
if (msg == null) return;
if (_log.shouldLog(Log.INFO))
_log.info("Sending message succeeded: " + msg);
super.afterSend(msg, true);
}
public int countActivePeers() {
long now = _context.clock().now();
int active = 0;
int inactive = 0;
synchronized (_peersByIdent) {
for (Iterator iter = _peersByIdent.values().iterator(); iter.hasNext(); ) {
PeerState peer = (PeerState)iter.next();
if (now-peer.getLastReceiveTime() > 5*60*1000)
inactive++;
else
active++;
}
}
return active;
}
public void renderStatusHTML(Writer out) throws IOException {
List peers = null;
synchronized (_peersByIdent) {
peers = new ArrayList(_peersByIdent.values());
}
StringBuffer buf = new StringBuffer(512);
buf.append("<b>UDP connections: ").append(peers.size()).append("</b><br />\n");
buf.append("<table border=\"1\">\n");
buf.append(" <tr><td><b>Peer</b></td><td><b>Location</b></td>\n");
buf.append(" <td><b>Last send</b></td><td><b>Last recv</b></td>\n");
buf.append(" <td><b>Lifetime</b></td><td><b>Window size</b></td>\n");
buf.append(" <td><b>Sent</b></td><td><b>Received</b></td>\n");
buf.append(" </tr>\n");
out.write(buf.toString());
buf.setLength(0);
long now = _context.clock().now();
for (int i = 0; i < peers.size(); i++) {
PeerState peer = (PeerState)peers.get(i);
if (now-peer.getLastReceiveTime() > 60*60*1000)
continue; // don't include old peers
buf.append("<tr>");
buf.append("<td>");
buf.append(peer.getRemotePeer().toBase64().substring(0,6));
buf.append("</td>");
buf.append("<td>");
byte ip[] = peer.getRemoteIP();
for (int j = 0; j < ip.length; j++) {
if (ip[j] < 0)
buf.append(ip[j] + 255);
else
buf.append(ip[j]);
if (j + 1 < ip.length)
buf.append('.');
}
buf.append(':').append(peer.getRemotePort());
buf.append("</td>");
buf.append("<td>");
buf.append(DataHelper.formatDuration(now-peer.getLastSendTime()));
buf.append("</td>");
buf.append("<td>");
buf.append(DataHelper.formatDuration(now-peer.getLastReceiveTime()));
buf.append("</td>");
buf.append("<td>");
buf.append(DataHelper.formatDuration(now-peer.getKeyEstablishedTime()));
buf.append("</td>");
buf.append("<td>");
buf.append(peer.getSendWindowBytes());
buf.append("</td>");
buf.append("<td>");
buf.append(peer.getMessagesSent());
buf.append("</td>");
buf.append("<td>");
buf.append(peer.getMessagesReceived());
buf.append("</td>");
buf.append("</tr>");
out.write(buf.toString());
buf.setLength(0);
}
out.write("</table>\n");
}
/**
* Cache the bid to reduce object churn
*/
private class SharedBid extends TransportBid {
private int _ms;
public SharedBid(int ms) { _ms = ms; }
public int getLatency() { return _ms; }
public Transport getTransport() { return UDPTransport.this; }
}
}

View File

@ -68,8 +68,8 @@ public class TunnelParticipant {
ok = _inboundEndpointProcessor.retrievePreprocessedData(msg.getData(), 0, msg.getData().length, recvFrom);
if (!ok) {
if (_log.shouldLog(Log.ERROR))
_log.error("Failed to dispatch " + msg + ": processor=" + _processor
if (_log.shouldLog(Log.WARN))
_log.warn("Failed to dispatch " + msg + ": processor=" + _processor
+ " inboundEndpoint=" + _inboundEndpointProcessor);
return;
}