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:
@ -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;
|
||||
|
@ -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[]);
|
||||
}
|
||||
|
@ -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]));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
44
router/java/src/net/i2p/router/transport/udp/ACKSender.java
Normal file
44
router/java/src/net/i2p/router/transport/udp/ACKSender.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
445
router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
Normal file
445
router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
Normal 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);
|
||||
}
|
||||
}
|
293
router/java/src/net/i2p/router/transport/udp/PacketHandler.java
Normal file
293
router/java/src/net/i2p/router/transport/udp/PacketHandler.java
Normal 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());
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
438
router/java/src/net/i2p/router/transport/udp/PeerState.java
Normal file
438
router/java/src/net/i2p/router/transport/udp/PeerState.java
Normal 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();
|
||||
}
|
||||
}
|
24
router/java/src/net/i2p/router/transport/udp/RelayPeer.java
Normal file
24
router/java/src/net/i2p/router/transport/udp/RelayPeer.java
Normal 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; }
|
||||
}
|
@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
router/java/src/net/i2p/router/transport/udp/UDPAddress.java
Normal file
56
router/java/src/net/i2p/router/transport/udp/UDPAddress.java
Normal 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; }
|
||||
}
|
@ -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(); }
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
188
router/java/src/net/i2p/router/transport/udp/UDPPacket.java
Normal file
188
router/java/src/net/i2p/router/transport/udp/UDPPacket.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
166
router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
Normal file
166
router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
174
router/java/src/net/i2p/router/transport/udp/UDPSender.java
Normal file
174
router/java/src/net/i2p/router/transport/udp/UDPSender.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
546
router/java/src/net/i2p/router/transport/udp/UDPTransport.java
Normal file
546
router/java/src/net/i2p/router/transport/udp/UDPTransport.java
Normal 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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user