package net.i2p.router.transport.udp;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.router.util.CDQEntry;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
import net.i2p.util.SystemVersion;
/**
* Basic delivery unit containing the datagram. This also maintains a cache
* of object instances to allow rapid reuse.
*
*/
class UDPPacket implements CDQEntry {
private RouterContext _context;
private final DatagramPacket _packet;
private volatile short _priority;
private volatile long _initializeTime;
//private volatile long _expiration;
private final byte[] _data;
private final byte[] _validateBuf;
private final byte[] _ivBuf;
private volatile int _markedType;
private RemoteHostId _remoteHost;
private boolean _released;
//private volatile Exception _releasedBy;
//private volatile Exception _acquiredBy;
private long _enqueueTime;
private long _receivedTime;
//private long _beforeValidate;
//private long _afterValidate;
//private long _beforeReceiveFragments;
//private long _afterHandlingTime;
private int _validateCount;
// private boolean _isInbound;
private FIFOBandwidthLimiter.Request _bandwidthRequest;
// Warning - this mixes contexts in a multi-router JVM
private static final Queue<UDPPacket> _packetCache;
private static final boolean CACHE = true;
private static final int MIN_CACHE_SIZE = 64;
private static final int MAX_CACHE_SIZE = 256;
static {
if (CACHE) {
long maxMemory = SystemVersion.getMaxMemory();
int csize = (int) Math.max(MIN_CACHE_SIZE, Math.min(MAX_CACHE_SIZE, maxMemory / (1024*1024)));
_packetCache = new LinkedBlockingQueue<UDPPacket>(csize);
} else {
_packetCache = null;
}
}
/**
* Actually it is one less than this, we assume
* if a received packet is this big it is truncated.
* This is bigger than PeerState.LARGE_MTU, as the far-end's
* LARGE_MTU may be larger than ours.
*
* Due to longstanding bugs, a packet may be larger than LARGE_MTU
* (acks and padding). Together with an increase in the LARGE_MTU to
* 1492 in release 0.8.9, routers from 0.8.9 - 0.8.11 can generate
* packets up to 1536. Data packets are always a multiple of 16,
* so make this 4 + a multiple of 16.
*/
static final int MAX_PACKET_SIZE = 1572;
public static final int IV_SIZE = 16;
public static final int MAC_SIZE = 16;
/** Message types, 4 bits max */
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;
public static final int PAYLOAD_TYPE_TEST = 7;
/** @since 0.8.1 */
public static final int PAYLOAD_TYPE_SESSION_DESTROY = 8;
public static final int MAX_PAYLOAD_TYPE = PAYLOAD_TYPE_SESSION_DESTROY;
// various flag fields for use in the header
/**
* Defined in the spec from the beginning, Unused
* @since 0.9.24
*/
public static final byte HEADER_FLAG_REKEY = (1 << 3);
/**
* Defined in the spec from the beginning, Used starting in 0.9.24
* @since 0.9.24
*/
public static final byte HEADER_FLAG_EXTENDED_OPTIONS = (1 << 2);
// Extended options for session request
public static final int SESS_REQ_MIN_EXT_OPTIONS_LENGTH = 2;
// bytes 0-1 are flags
/**
* set to 1 to request a session tag, i.e. we want him to be an introducer for us
*/
public static final int SESS_REQ_EXT_FLAG_REQUEST_RELAY_TAG = 0x01;
// 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_ACK_BITFIELDS = (1 << 6);
/** unused */
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);
/** unused */
public static final byte DATA_FLAG_EXTENDED = (1 << 1);
public static final byte BITFIELD_CONTINUATION = (byte)(1 << 7);
private static final int MAX_VALIDATE_SIZE = MAX_PACKET_SIZE;
private UDPPacket(RouterContext ctx) {
//ctx.statManager().createRateStat("udp.fetchRemoteSlow", "How long it takes to grab the remote ip info", "udp", UDPTransport.RATES);
// the data buffer is clobbered on init(..), but we need it to bootstrap
_data = new byte[MAX_PACKET_SIZE];
_packet = new DatagramPacket(_data, MAX_PACKET_SIZE);
_validateBuf = new byte[MAX_VALIDATE_SIZE];
_ivBuf = new byte[IV_SIZE];
init(ctx);
}
private synchronized void init(RouterContext ctx) {
_context = ctx;
//_dataBuf = _dataCache.acquire();
Arrays.fill(_data, (byte)0);
//_packet = new DatagramPacket(_data, MAX_PACKET_SIZE);
//
// WARNING -
// Doesn't seem like we should have to do this every time,
// from reading the DatagramPacket javadocs,
// but we get massive corruption without it.
_packet.setData(_data);
// _isInbound = inbound;
_initializeTime = _context.clock().now();
_markedType = -1;
_validateCount = 0;
_remoteHost = null;
_released = false;
// clear out some values to make debugging easier via toString()
_messageType = -1;
_enqueueTime = 0;
_receivedTime = 0;
_fragmentCount = 0;
}
/****
public void writeData(byte src[], int offset, int len) {
verifyNotReleased();
System.arraycopy(src, offset, _data, 0, len);
_packet.setLength(len);
resetBegin();
}
****/
/** */
public synchronized DatagramPacket getPacket() { verifyNotReleased(); return _packet; }
public synchronized short getPriority() { verifyNotReleased(); return _priority; }
//public long getExpiration() { verifyNotReleased(); return _expiration; }
public synchronized long getBegin() { verifyNotReleased(); return _initializeTime; }
public long getLifetime() { /** verifyNotReleased(); */ return _context.clock().now() - _initializeTime; }
public synchronized void resetBegin() { _initializeTime = _context.clock().now(); }
/** flag this packet as a particular type for accounting purposes */
public synchronized void markType(int type) { verifyNotReleased(); _markedType = type; }
/**
* flag this packet as a particular type for accounting purposes, with
* 1 implying the packet is an ACK, otherwise it is a data packet
*
*/
public synchronized int getMarkedType() { verifyNotReleased(); return _markedType; }
private int _messageType;
private int _fragmentCount;
/** only for debugging and stats, does not go on the wire */
int getMessageType() { return _messageType; }
/** only for debugging and stats, does not go on the wire */
void setMessageType(int type) { _messageType = type; }
/** only for debugging and stats */
int getFragmentCount() { return _fragmentCount; }
/** only for debugging and stats */
void setFragmentCount(int count) { _fragmentCount = count; }
synchronized RemoteHostId getRemoteHost() {
if (_remoteHost == null) {
//long before = System.currentTimeMillis();
InetAddress addr = _packet.getAddress();
byte ip[] = addr.getAddress();
int port = _packet.getPort();
_remoteHost = new RemoteHostId(ip, port);
//long timeToFetch = System.currentTimeMillis() - before;
//if (timeToFetch > 50)
// _context.statManager().addRateData("udp.fetchRemoteSlow", timeToFetch, getLifetime());
}
return _remoteHost;
}
/**
* Validate the packet against the MAC specified, returning true if the
* MAC matches, false otherwise.
*
*/
public synchronized boolean validate(SessionKey macKey) {
verifyNotReleased();
//_beforeValidate = _context.clock().now();
boolean eq = false;
Arrays.fill(_validateBuf, (byte)0);
// validate by comparing _data[0:15] and
// HMAC(payload + IV + (payloadLength ^ protocolVersion), macKey)
int payloadLength = _packet.getLength() - MAC_SIZE - IV_SIZE;
if (payloadLength > 0) {
int off = 0;
System.arraycopy(_data, _packet.getOffset() + MAC_SIZE + IV_SIZE, _validateBuf, off, payloadLength);
off += payloadLength;
System.arraycopy(_data, _packet.getOffset() + MAC_SIZE, _validateBuf, off, IV_SIZE);
off += IV_SIZE;
DataHelper.toLong(_validateBuf, off, 2, payloadLength /* ^ PacketBuilder.PROTOCOL_VERSION */ );
off += 2;
eq = _context.hmac().verify(macKey, _validateBuf, 0, off, _data, _packet.getOffset(), MAC_SIZE);
if (!eq) {
// this is relatively frequent, as you can get old keys in PacketHandler.
Log log = _context.logManager().getLog(UDPPacket.class);
if (log.shouldLog(Log.INFO)) {
byte[] calc = new byte[32];
_context.hmac().calculate(macKey, _validateBuf, 0, off, calc, 0);
StringBuilder str = new StringBuilder(512);
str.append("Bad HMAC:\n\t");
str.append(_packet.getLength()).append(" byte pkt, ");
str.append(payloadLength).append(" byte payload");
str.append("\n\tFrom: ").append(getRemoteHost().toString());
str.append("\n\tIV: ").append(Base64.encode(_validateBuf, payloadLength, IV_SIZE));
str.append("\n\tIV2: ").append(Base64.encode(_data, MAC_SIZE, IV_SIZE));
str.append("\n\tGiven Len: ").append(DataHelper.fromLong(_validateBuf, payloadLength + IV_SIZE, 2));
str.append("\n\tCalc HMAC: ").append(Base64.encode(calc, 0, MAC_SIZE));
str.append("\n\tRead HMAC: ").append(Base64.encode(_data, _packet.getOffset(), MAC_SIZE));
str.append("\n\tUsing key: ").append(macKey.toBase64());
if (DataHelper.eq(macKey.getData(), 0, _context.routerHash().getData(), 0, 32))
str.append(" (Intro)");
else
str.append(" (Session)");
str.append("\n\tRaw: ").append(Base64.encode(_data, _packet.getOffset(), _packet.getLength()));
log.info(str.toString(), new Exception());
}
}
} else {
Log log = _context.logManager().getLog(UDPPacket.class);
if (log.shouldLog(Log.WARN))
log.warn("Payload length is " + payloadLength + ", too short! From: " + getRemoteHost() + '\n' +
net.i2p.util.HexDump.dump(_data, _packet.getOffset(), _packet.getLength()));
}
//_afterValidate = _context.clock().now();
_validateCount++;
return eq;
}
/**
* Decrypt this valid packet, overwriting the _data buffer's payload
* with the decrypted data (leaving the MAC and IV unaltered)
*
*/
public synchronized void decrypt(SessionKey cipherKey) {
verifyNotReleased();
System.arraycopy(_data, MAC_SIZE, _ivBuf, 0, IV_SIZE);
int len = _packet.getLength();
// As of 0.9.7, ignore padding beyond the last mod 16,
// it could otherwise blow up in decryption.
// This allows for better obfuscation.
// Probably works without this since _data is bigger than necessary, but let's not
// bother decrypting and risk overrun.
int rem = len & 0x0f;
if (rem != 0)
len -= rem;
int off = _packet.getOffset() + MAC_SIZE + IV_SIZE;
_context.aes().decrypt(_data, off, _data, off, cipherKey, _ivBuf, len - MAC_SIZE - IV_SIZE);
}
/**
* For CDQ
* @since 0.9.3
*/
public void setEnqueueTime(long now) { _enqueueTime = now; }
/** a packet handler has pulled it off the inbound queue */
synchronized void received() { _receivedTime = _context.clock().now(); }
/** a packet handler has decrypted and verified the packet and is about to parse out the good bits */
//void beforeReceiveFragments() { _beforeReceiveFragments = _context.clock().now(); }
/** a packet handler has finished parsing out the good bits */
//void afterHandling() { _afterHandlingTime = _context.clock().now(); }
/**
* For CDQ
* @since 0.9.3
*/
public long getEnqueueTime() { return _enqueueTime; }
/** a packet handler has pulled it off the inbound queue */
synchronized long getTimeSinceReceived() { return (_receivedTime > 0 ? _context.clock().now() - _receivedTime : 0); }
/** a packet handler has decrypted and verified the packet and is about to parse out the good bits */
//long getTimeSinceReceiveFragments() { return (_beforeReceiveFragments > 0 ? _context.clock().now() - _beforeReceiveFragments : 0); }
/** a packet handler has finished parsing out the good bits */
//long getTimeSinceHandling() { return (_afterHandlingTime > 0 ? _context.clock().now() - _afterHandlingTime : 0); }
/**
* So that we can compete with NTCP, we want to request bandwidth
* in parallel, on the way into the queue, not on the way out.
* Call before enqueueing.
* @since 0.9.21
* @deprecated unused
*/
@Deprecated
public synchronized void requestInboundBandwidth() {
verifyNotReleased();
_bandwidthRequest = _context.bandwidthLimiter().requestInbound(_packet.getLength(), "UDP receiver");
}
/**
* So that we can compete with NTCP, we want to request bandwidth
* in parallel, on the way into the queue, not on the way out.
* Call before enqueueing.
* @since 0.9.21
*/
public synchronized void requestOutboundBandwidth() {
verifyNotReleased();
_bandwidthRequest = _context.bandwidthLimiter().requestOutbound(_packet.getLength(), 0, "UDP sender");
}
/**
* So that we can compete with NTCP, we want to request bandwidth
* in parallel, on the way into the queue, not on the way out.
* Call after dequeueing.
* @since 0.9.21
*/
public synchronized FIFOBandwidthLimiter.Request getBandwidthRequest() {
verifyNotReleased();
return _bandwidthRequest;
}
// Following 5: All used only for stats in PacketHandler, commented out
/** when it was pulled off the endpoint receive queue */
//long getReceivedTime() { return _receivedTime; }
/** when we began validate() */
//long getBeforeValidate() { return _beforeValidate; }
/** when we finished validate() */
//long getAfterValidate() { return _afterValidate; }
/** how many times we tried to validate the packet */
//int getValidateCount() { return _validateCount; }
@Override
public String toString() {
verifyNotReleased();
StringBuilder buf = new StringBuilder(256);
buf.append(_packet.getLength());
buf.append(" byte pkt with ");
buf.append(Addresses.toString(_packet.getAddress().getAddress(), _packet.getPort()));
//buf.append(" id=").append(System.identityHashCode(this));
if (_messageType >= 0)
buf.append(" msgType=").append(_messageType);
if (_markedType >= 0)
buf.append(" markType=").append(_markedType);
if (_fragmentCount > 0)
buf.append(" fragCount=").append(_fragmentCount);
if (_enqueueTime > 0)
buf.append(" sinceEnqueued=").append(_context.clock().now() - _enqueueTime);
if (_receivedTime > 0)
buf.append(" sinceReceived=").append(_context.clock().now() - _receivedTime);
//buf.append(" beforeReceiveFragments=").append((_beforeReceiveFragments > 0 ? _context.clock().now()-_beforeReceiveFragments : -1));
//buf.append(" sinceHandled=").append((_afterHandlingTime > 0 ? _context.clock().now()-_afterHandlingTime : -1));
//buf.append("\ndata=").append(Base64.encode(_packet.getData(), _packet.getOffset(), _packet.getLength()));
return buf.toString();
}
/**
* @param inbound unused
*/
public static UDPPacket acquire(RouterContext ctx, boolean inbound) {
UDPPacket rv = null;
if (CACHE) {
rv = _packetCache.poll();
if (rv != null) {
synchronized(rv) {
if (!rv._released) {
Log log = rv._context.logManager().getLog(UDPPacket.class);
log.error("Unreleased cached packet", new Exception());
rv = null;
} else {
rv.init(ctx);
}
}
}
}
if (rv == null)
rv = new UDPPacket(ctx);
return rv;
}
/**
* For CDQ
* @since 0.9.3
*/
public void drop() {
release();
}
public synchronized void release() {
verifyNotReleased();
_released = true;
//_releasedBy = new Exception("released by");
//_acquiredBy = null;
//
//_dataCache.release(_dataBuf);
if (_bandwidthRequest != null) {
synchronized(_bandwidthRequest) {
if (_bandwidthRequest.getPendingRequested() > 0)
_bandwidthRequest.abort();
}
_bandwidthRequest = null;
}
if (!CACHE)
return;
_packetCache.offer(this);
}
/**
* Call at shutdown/startup to not hold ctx refs
* @since 0.9.2
*/
public static void clearCache() {
if (CACHE)
_packetCache.clear();
}
private synchronized void verifyNotReleased() {
if (!CACHE) return;
if (_released) {
Log log = _context.logManager().getLog(UDPPacket.class);
log.error("Already released", new Exception());
//log.log(Log.CRIT, "Released by: ", _releasedBy);
//log.log(Log.CRIT, "Acquired by: ", _acquiredBy);
}
}
}