forked from I2P_Developers/i2p.i2p
SSU2: Add token support to EstablishmentManager
This commit is contained in:
@@ -29,6 +29,7 @@ import net.i2p.router.util.DecayingHashSet;
|
||||
import net.i2p.router.util.DecayingBloomFilter;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.LHMCache;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
@@ -45,6 +46,12 @@ class EstablishmentManager {
|
||||
private final PacketBuilder _builder;
|
||||
private final int _networkID;
|
||||
|
||||
// SSU 2
|
||||
private final PacketBuilder2 _builder2;
|
||||
private final boolean _enableSSU2;
|
||||
private final Map<Hash, Token> _outboundTokens;
|
||||
private final Map<RemoteHostId, Token> _inboundTokens;
|
||||
|
||||
/** map of RemoteHostId to InboundEstablishState */
|
||||
private final ConcurrentHashMap<RemoteHostId, InboundEstablishState> _inboundStates;
|
||||
|
||||
@@ -138,6 +145,10 @@ class EstablishmentManager {
|
||||
private static final String VERSION_ALLOW_EXTENDED_OPTIONS = "0.9.24";
|
||||
private static final String PROP_DISABLE_EXT_OPTS = "i2np.udp.disableExtendedOptions";
|
||||
|
||||
// SSU 2
|
||||
private static final int MAX_TOKENS = 512;
|
||||
public static final long IB_TOKEN_EXPIRATION = 60*60*1000L;
|
||||
|
||||
|
||||
public EstablishmentManager(RouterContext ctx, UDPTransport transport) {
|
||||
_context = ctx;
|
||||
@@ -145,12 +156,22 @@ class EstablishmentManager {
|
||||
_networkID = ctx.router().getNetworkID();
|
||||
_transport = transport;
|
||||
_builder = transport.getBuilder();
|
||||
_builder2 = transport.getBuilder2();
|
||||
_enableSSU2 = _builder2 != null;
|
||||
_inboundStates = new ConcurrentHashMap<RemoteHostId, InboundEstablishState>();
|
||||
_outboundStates = new ConcurrentHashMap<RemoteHostId, OutboundEstablishState>();
|
||||
_queuedOutbound = new ConcurrentHashMap<RemoteHostId, List<OutNetMessage>>();
|
||||
_liveIntroductions = new ConcurrentHashMap<Long, OutboundEstablishState>();
|
||||
_outboundByClaimedAddress = new ConcurrentHashMap<RemoteHostId, OutboundEstablishState>();
|
||||
_outboundByHash = new ConcurrentHashMap<Hash, OutboundEstablishState>();
|
||||
if (_enableSSU2) {
|
||||
_inboundTokens = new LHMCache<RemoteHostId, Token>(MAX_TOKENS);
|
||||
_outboundTokens = new LHMCache<Hash, Token>(MAX_TOKENS);
|
||||
} else {
|
||||
_inboundTokens = null;
|
||||
_outboundTokens = null;
|
||||
}
|
||||
|
||||
_activityLock = new Object();
|
||||
_replayFilter = new DecayingHashSet(ctx, 10*60*1000, 8, "SSU-DH-X");
|
||||
DEFAULT_MAX_CONCURRENT_ESTABLISH = Math.max(DEFAULT_LOW_MAX_CONCURRENT_ESTABLISH,
|
||||
@@ -1428,6 +1449,86 @@ class EstablishmentManager {
|
||||
}
|
||||
}
|
||||
|
||||
//// SSU 2 ////
|
||||
|
||||
/**
|
||||
* Remember a token that can be used later to connect to the peer
|
||||
*
|
||||
* @param token nonzero
|
||||
* @since 0.9.54
|
||||
*/
|
||||
public void addOutboundToken(Hash peer, long token, long expires) {
|
||||
if (expires < _context.clock().now())
|
||||
return;
|
||||
Token tok = new Token(token, expires);
|
||||
synchronized(_outboundTokens) {
|
||||
_outboundTokens.put(peer, tok);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a token to connect to the peer
|
||||
*
|
||||
* @return 0 if none available
|
||||
* @since 0.9.54
|
||||
*/
|
||||
public long getOutboundToken(Hash peer) {
|
||||
Token tok;
|
||||
synchronized(_outboundTokens) {
|
||||
tok = _outboundTokens.remove(peer);
|
||||
}
|
||||
if (tok == null)
|
||||
return 0;
|
||||
if (tok.expires < _context.clock().now())
|
||||
return 0;
|
||||
return tok.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember a token that can be used later for the peer to connect to us
|
||||
*
|
||||
* @param token nonzero
|
||||
* @since 0.9.54
|
||||
*/
|
||||
public void addInboundToken(RemoteHostId peer, long token) {
|
||||
long expires = _context.clock().now() + IB_TOKEN_EXPIRATION;
|
||||
Token tok = new Token(token, expires);
|
||||
synchronized(_inboundTokens) {
|
||||
_inboundTokens.put(peer, tok);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the token from this peer valid?
|
||||
*
|
||||
* @return valid
|
||||
* @since 0.9.54
|
||||
*/
|
||||
public boolean isInboundTokenValid(RemoteHostId peer, long token) {
|
||||
if (token == 0)
|
||||
return false;
|
||||
Token tok;
|
||||
synchronized(_inboundTokens) {
|
||||
tok = _inboundTokens.get(peer);
|
||||
if (tok == null)
|
||||
return false;
|
||||
if (tok.token != token)
|
||||
return false;
|
||||
_inboundTokens.remove(peer);
|
||||
}
|
||||
return tok.expires >= _context.clock().now();
|
||||
}
|
||||
|
||||
private static class Token {
|
||||
public final long token, expires;
|
||||
public Token(long tok, long exp) {
|
||||
token = tok; expires = exp;
|
||||
}
|
||||
}
|
||||
|
||||
//// End SSU 2 ////
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -50,15 +50,15 @@ class InboundEstablishState {
|
||||
private byte _receivedSignature[];
|
||||
private boolean _verificationAttempted;
|
||||
// sig not verified
|
||||
private RouterIdentity _receivedUnconfirmedIdentity;
|
||||
protected RouterIdentity _receivedUnconfirmedIdentity;
|
||||
// identical to uncomfirmed, but sig now verified
|
||||
private RouterIdentity _receivedConfirmedIdentity;
|
||||
protected RouterIdentity _receivedConfirmedIdentity;
|
||||
// general status
|
||||
private final long _establishBegin;
|
||||
//private long _lastReceive;
|
||||
protected long _lastSend;
|
||||
protected long _nextSend;
|
||||
private final RemoteHostId _remoteHostId;
|
||||
protected final RemoteHostId _remoteHostId;
|
||||
protected InboundState _currentState;
|
||||
private final Queue<OutNetMessage> _queuedMessages;
|
||||
// count for backoff
|
||||
|
||||
@@ -32,17 +32,21 @@ import net.i2p.util.Log;
|
||||
* @since 0.9.54
|
||||
*/
|
||||
class InboundEstablishState2 extends InboundEstablishState implements SSU2Payload.PayloadCallback {
|
||||
private final UDPTransport _transport;
|
||||
private final InetSocketAddress _aliceSocketAddress;
|
||||
private final long _rcvConnID;
|
||||
private final long _sendConnID;
|
||||
private final long _token;
|
||||
private final long _nextToken;
|
||||
private final HandshakeState _handshakeState;
|
||||
private byte[] _sendHeaderEncryptKey1;
|
||||
private final byte[] _rcvHeaderEncryptKey1;
|
||||
private byte[] _sendHeaderEncryptKey2;
|
||||
private byte[] _rcvHeaderEncryptKey2;
|
||||
|
||||
// testing
|
||||
private static final boolean ENFORCE_TOKEN = false;
|
||||
|
||||
|
||||
/**
|
||||
* @param localPort Must be our external port, otherwise the signature of the
|
||||
* SessionCreated message will be bad if the external port != the internal port.
|
||||
@@ -51,6 +55,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
public InboundEstablishState2(RouterContext ctx, UDPTransport transport,
|
||||
UDPPacket packet) throws GeneralSecurityException {
|
||||
super(ctx, (InetSocketAddress) packet.getPacket().getSocketAddress());
|
||||
_transport = transport;
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
_aliceSocketAddress = (InetSocketAddress) pkt.getSocketAddress();
|
||||
_handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK_SSU2, HandshakeState.RESPONDER, transport.getXDHFactory());
|
||||
@@ -68,9 +73,6 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
int off = pkt.getOffset();
|
||||
int len = pkt.getLength();
|
||||
byte data[] = pkt.getData();
|
||||
// fast MSB check for key < 2^255
|
||||
if ((data[off + 32 + 32 - 1] & 0x80) != 0)
|
||||
throw new GeneralSecurityException("Bad PK msg 1");
|
||||
_rcvConnID = DataHelper.fromLong8(data, off);
|
||||
_sendConnID = DataHelper.fromLong8(data, off + 16);
|
||||
if (_rcvConnID == _sendConnID)
|
||||
@@ -85,7 +87,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
token = ctx.random().nextLong();
|
||||
} while (token == 0);
|
||||
_token = token;
|
||||
} else if (type == 0 && token == 0) { // || token not valid
|
||||
} else if (type == 0 &&
|
||||
(token == 0 ||
|
||||
(ENFORCE_TOKEN && !_transport.getEstablisher().isInboundTokenValid(_remoteHostId, token)))) {
|
||||
_currentState = InboundState.IB_STATE_REQUEST_BAD_TOKEN_RECEIVED;
|
||||
_sendHeaderEncryptKey2 = introKey;
|
||||
do {
|
||||
@@ -93,6 +97,9 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
} while (token == 0);
|
||||
_token = token;
|
||||
} else {
|
||||
// fast MSB check for key < 2^255
|
||||
if ((data[off + 32 + 32 - 1] & 0x80) != 0)
|
||||
throw new GeneralSecurityException("Bad PK msg 1");
|
||||
// probably don't need again
|
||||
_token = token;
|
||||
_handshakeState.start();
|
||||
@@ -116,7 +123,6 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
_sendHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
|
||||
_currentState = InboundState.IB_STATE_REQUEST_RECEIVED;
|
||||
}
|
||||
_nextToken = ctx.random().nextLong();
|
||||
packetReceived();
|
||||
}
|
||||
|
||||
@@ -149,6 +155,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
System.out.println("Got RI block: " + ri);
|
||||
if (isHandshake)
|
||||
throw new DataFormatException("RI in Sess Req");
|
||||
_receivedUnconfirmedIdentity = ri.getIdentity();
|
||||
List<RouterAddress> addrs = ri.getTargetAddresses("SSU", "SSU2");
|
||||
RouterAddress ra = null;
|
||||
for (RouterAddress addr : addrs) {
|
||||
@@ -179,6 +186,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
if (!"2".equals(ra.getOption("v")))
|
||||
throw new DataFormatException("bad SSU2 v");
|
||||
|
||||
_receivedConfirmedIdentity = _receivedUnconfirmedIdentity;
|
||||
_sendHeaderEncryptKey1 = ik;
|
||||
//_sendHeaderEncryptKey2 calculated below
|
||||
|
||||
@@ -207,19 +215,25 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
}
|
||||
|
||||
public void gotToken(long token, long expires) {
|
||||
System.out.println("Got NEW TOKEN block " + token + " expires " + DataHelper.formatTime(expires));
|
||||
if (_receivedConfirmedIdentity == null)
|
||||
throw new IllegalStateException("RI must be first");
|
||||
_transport.getEstablisher().addOutboundToken(_receivedConfirmedIdentity.calculateHash(), token, expires);
|
||||
}
|
||||
|
||||
public void gotI2NP(I2NPMessage msg) {
|
||||
System.out.println("Got I2NP block: " + msg);
|
||||
if (getState() != InboundState.IB_STATE_CREATED_SENT)
|
||||
throw new IllegalStateException("I2NP in Sess Req");
|
||||
if (_receivedConfirmedIdentity == null)
|
||||
throw new IllegalStateException("RI must be first");
|
||||
}
|
||||
|
||||
public void gotFragment(byte[] data, int off, int len, long messageID, int frag, boolean isLast) throws DataFormatException {
|
||||
System.out.println("Got FRAGMENT block: " + messageID);
|
||||
if (getState() != InboundState.IB_STATE_CREATED_SENT)
|
||||
throw new IllegalStateException("I2NP in Sess Req");
|
||||
if (_receivedConfirmedIdentity == null)
|
||||
throw new IllegalStateException("RI must be first");
|
||||
}
|
||||
|
||||
public void gotACK(long ackThru, int acks, byte[] ranges) {
|
||||
@@ -244,7 +258,15 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
public long getSendConnID() { return _sendConnID; }
|
||||
public long getRcvConnID() { return _rcvConnID; }
|
||||
public long getToken() { return _token; }
|
||||
public long getNextToken() { return _nextToken; }
|
||||
public long getNextToken() {
|
||||
// generate on the fly, this will only be called once
|
||||
long token;
|
||||
do {
|
||||
token = _context.random().nextLong();
|
||||
} while (token == 0);
|
||||
_transport.getEstablisher().addInboundToken(_remoteHostId, token);
|
||||
return token;
|
||||
}
|
||||
public HandshakeState getHandshakeState() { return _handshakeState; }
|
||||
public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
|
||||
public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }
|
||||
|
||||
@@ -55,7 +55,7 @@ class OutboundEstablishState {
|
||||
private long _nextSend;
|
||||
protected RemoteHostId _remoteHostId;
|
||||
private final RemoteHostId _claimedAddress;
|
||||
private final RouterIdentity _remotePeer;
|
||||
protected final RouterIdentity _remotePeer;
|
||||
private final boolean _allowExtendedOptions;
|
||||
private final boolean _needIntroduction;
|
||||
private final SessionKey _introKey;
|
||||
|
||||
@@ -37,7 +37,6 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
private final long _sendConnID;
|
||||
private final long _rcvConnID;
|
||||
private long _token;
|
||||
private final long _nextToken;
|
||||
private HandshakeState _handshakeState;
|
||||
private final byte[] _sendHeaderEncryptKey1;
|
||||
private final byte[] _rcvHeaderEncryptKey1;
|
||||
@@ -96,7 +95,6 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
} while (_token == 0);
|
||||
}
|
||||
_rcvConnID = rcid;
|
||||
_nextToken = ctx.random().nextLong();
|
||||
byte[] ik = introKey.getData();
|
||||
_sendHeaderEncryptKey1 = ik;
|
||||
_rcvHeaderEncryptKey1 = ik;
|
||||
@@ -173,7 +171,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
}
|
||||
|
||||
public void gotToken(long token, long expires) {
|
||||
System.out.println("Got NEW TOKEN block " + token + " expires " + DataHelper.formatTime(expires));
|
||||
_transport.getEstablisher().addOutboundToken(_remotePeer.calculateHash(), token, expires);
|
||||
}
|
||||
|
||||
public void gotI2NP(I2NPMessage msg) {
|
||||
@@ -208,7 +206,15 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
public long getSendConnID() { return _sendConnID; }
|
||||
public long getRcvConnID() { return _rcvConnID; }
|
||||
public long getToken() { return _token; }
|
||||
public long getNextToken() { return _nextToken; }
|
||||
public long getNextToken() {
|
||||
// generate on the fly, this will only be called once
|
||||
long token;
|
||||
do {
|
||||
token = _context.random().nextLong();
|
||||
} while (token == 0);
|
||||
_transport.getEstablisher().addInboundToken(_remoteHostId, token);
|
||||
return token;
|
||||
}
|
||||
public HandshakeState getHandshakeState() { return _handshakeState; }
|
||||
public byte[] getSendHeaderEncryptKey1() { return _sendHeaderEncryptKey1; }
|
||||
public byte[] getRcvHeaderEncryptKey1() { return _rcvHeaderEncryptKey1; }
|
||||
|
||||
@@ -795,7 +795,7 @@ class PacketBuilder2 {
|
||||
blocks.add(block);
|
||||
}
|
||||
if (token > 0) {
|
||||
block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + 6*24*60*60*1000L);
|
||||
block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + EstablishmentManager.IB_TOKEN_EXPIRATION);
|
||||
len += block.getTotalLength();
|
||||
blocks.add(block);
|
||||
}
|
||||
@@ -923,7 +923,7 @@ class PacketBuilder2 {
|
||||
blocks.add(riblock);
|
||||
if (token > 0) {
|
||||
// TODO only if room
|
||||
Block block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + 6*24*60*60*1000L);
|
||||
Block block = new SSU2Payload.NewTokenBlock(token, _context.clock().now() + EstablishmentManager.IB_TOKEN_EXPIRATION);
|
||||
len += block.getTotalLength();
|
||||
blocks.add(block);
|
||||
}
|
||||
|
||||
@@ -223,6 +223,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
|
||||
}
|
||||
|
||||
public void gotToken(long token, long expires) {
|
||||
_transport.getEstablisher().addOutboundToken(_remotePeer, token, expires);
|
||||
}
|
||||
|
||||
public void gotI2NP(I2NPMessage msg) {
|
||||
|
||||
Reference in New Issue
Block a user