forked from I2P_Developers/i2p.i2p
SSU2: Send termination on inbound session/token request
(rate limited) at conn limits or when alice is banned. Process retry payload even if token is 0, to get termination reason Wait longer for session request after retry, allowing for at least 2 retransmissions, to reduce IES2 failures Ban peer if he bans us in retry Remove unused writePayload() Javadoc fixes Log tweaks
This commit is contained in:
@@ -54,6 +54,7 @@ import net.i2p.util.HexDump;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.LHMCache;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.SystemVersion;
|
||||
import net.i2p.util.VersionComparator;
|
||||
@@ -76,6 +77,7 @@ class EstablishmentManager {
|
||||
private final boolean _enableSSU2;
|
||||
private final Map<RemoteHostId, Token> _outboundTokens;
|
||||
private final Map<RemoteHostId, Token> _inboundTokens;
|
||||
private final ObjectCounter<RemoteHostId> _terminationCounter;
|
||||
|
||||
/** map of RemoteHostId to InboundEstablishState */
|
||||
private final ConcurrentHashMap<RemoteHostId, InboundEstablishState> _inboundStates;
|
||||
@@ -185,6 +187,8 @@ class EstablishmentManager {
|
||||
public static final long IB_TOKEN_EXPIRATION = 60*60*1000L;
|
||||
private static final long MAX_SKEW = 2*60*1000;
|
||||
private static final String TOKEN_FILE = "ssu2tokens.txt";
|
||||
// max immediate terminations to send to a peer every FAILSAFE_INTERVAL
|
||||
private static final int MAX_TERMINATIONS = 2;
|
||||
|
||||
|
||||
public EstablishmentManager(RouterContext ctx, UDPTransport transport) {
|
||||
@@ -207,9 +211,11 @@ class EstablishmentManager {
|
||||
int tokenCacheSize = Math.max(MIN_TOKENS, Math.min(MAX_TOKENS, 3 * _transport.getMaxConnections() / 4));
|
||||
_inboundTokens = new InboundTokens(tokenCacheSize);
|
||||
_outboundTokens = new LHMCache<RemoteHostId, Token>(tokenCacheSize);
|
||||
_terminationCounter = new ObjectCounter<RemoteHostId>();
|
||||
} else {
|
||||
_inboundTokens = null;
|
||||
_outboundTokens = null;
|
||||
_terminationCounter = null;
|
||||
}
|
||||
|
||||
_activityLock = new Object();
|
||||
@@ -700,29 +706,38 @@ class EstablishmentManager {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Dropping inbound establish, increase " + PROP_MAX_CONCURRENT_ESTABLISH);
|
||||
_context.statManager().addRateData("udp.establishDropped", 1);
|
||||
return; // drop the packet
|
||||
sendTerminationPacket(from, packet, REASON_LIMITS);
|
||||
return;
|
||||
}
|
||||
if (_context.blocklist().isBlocklisted(from.getIP())) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Receive session request from blocklisted IP: " + from);
|
||||
_context.statManager().addRateData("udp.establishBadIP", 1);
|
||||
return; // drop the packet
|
||||
if (!_context.commSystem().isInStrictCountry())
|
||||
sendTerminationPacket(from, packet, REASON_BANNED);
|
||||
// else drop the packet
|
||||
return;
|
||||
}
|
||||
synchronized (_inboundBans) {
|
||||
Long exp = _inboundBans.get(from);
|
||||
if (exp != null) {
|
||||
if (exp.longValue() >= _context.clock().now()) {
|
||||
// this is common, finally get a packet after the IES2 timeout
|
||||
if (_log.shouldInfo())
|
||||
_log.info("SSU 2 session request from temp. blocked peer: " + from);
|
||||
_context.statManager().addRateData("udp.establishBadIP", 1);
|
||||
return; // drop the packet
|
||||
// use this code for a temp ban
|
||||
sendTerminationPacket(from, packet, REASON_MSG1);
|
||||
return;
|
||||
}
|
||||
// expired
|
||||
_inboundBans.remove(from);
|
||||
}
|
||||
}
|
||||
if (!_transport.allowConnection())
|
||||
return; // drop the packet
|
||||
if (!_transport.allowConnection()) {
|
||||
sendTerminationPacket(from, packet, REASON_LIMITS);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
state = new InboundEstablishState2(_context, _transport, packet);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
@@ -781,6 +796,50 @@ class EstablishmentManager {
|
||||
}
|
||||
notifyActivity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Retry packet with a termination code, for a rejection
|
||||
* of a session/token request. No InboundEstablishState2 required.
|
||||
*
|
||||
* SSU 2 only.
|
||||
* The inbound packet was superficially validated for type, netID, and version,
|
||||
* so we have basic probing resistance.
|
||||
* The Retry packet encryption is low-cost, chacha only.
|
||||
*
|
||||
* Rate limited to MAX_TERMINATIONS per peer every FAILSAFE_INTERVAL
|
||||
*
|
||||
* @param fromPacket header already decrypted, must be session or token request
|
||||
* @param terminationCode nonzero
|
||||
* @since 0.9.57
|
||||
*/
|
||||
private void sendTerminationPacket(RemoteHostId to, UDPPacket fromPacket, int terminationCode) {
|
||||
int count = _terminationCounter.increment(to);
|
||||
if (count > MAX_TERMINATIONS) {
|
||||
// not everybody listens or backs off...
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Rate limit " + count + " not sending termination to: " + to);
|
||||
return;
|
||||
}
|
||||
// very basic validation that this is probably in response to a good packet.
|
||||
// we don't bother to decrypt the packet, even if it's only a token request
|
||||
DatagramPacket pkt = fromPacket.getPacket();
|
||||
int off = pkt.getOffset();
|
||||
int len = pkt.getLength();
|
||||
if (len < MIN_LONG_DATA_LEN)
|
||||
return;
|
||||
byte data[] = pkt.getData();
|
||||
int type = data[off + TYPE_OFFSET] & 0xff;
|
||||
if (type == SSU2Util.SESSION_REQUEST_FLAG_BYTE && len < MIN_SESSION_REQUEST_LEN)
|
||||
return;
|
||||
long rcvConnID = DataHelper.fromLong8(data, off);
|
||||
long sendConnID = DataHelper.fromLong8(data, off + SRC_CONN_ID_OFFSET);
|
||||
if (rcvConnID == 0 || sendConnID == 0 || rcvConnID == sendConnID)
|
||||
return;
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Send immediate termination " + terminationCode + " on type " + type + " to: " + to);
|
||||
UDPPacket packet = _builder2.buildRetryPacket(to, pkt.getSocketAddress(), sendConnID, rcvConnID, terminationCode);
|
||||
_transport.send(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* got a SessionConfirmed (should only happen as part of an inbound
|
||||
@@ -3155,6 +3214,7 @@ class EstablishmentManager {
|
||||
}
|
||||
if (count > 0 && _log.shouldDebug())
|
||||
_log.debug("Expired " + count + " outbound tokens");
|
||||
_terminationCounter.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,9 +513,11 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
|
||||
throw new IllegalStateException("Bad state for Retry Sent: " + _currentState);
|
||||
_currentState = InboundState.IB_STATE_RETRY_SENT;
|
||||
_lastSend = _context.clock().now();
|
||||
// Won't really be retransmitted, they have 9 sec to respond or
|
||||
// Won't really be retransmitted, they have 5 sec to respond or
|
||||
// EstablishmentManager.handleInbound() will fail the connection
|
||||
_nextSend = _lastSend + (3 * RETRANSMIT_DELAY);
|
||||
// Alice will retransmit at 1 and 3 seconds, so wait 5
|
||||
// We're not going to wait for the 3rd retx at 7 seconds.
|
||||
_nextSend = _lastSend + (5 * RETRANSMIT_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -346,6 +346,13 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
// this sets the state to FAILED
|
||||
fail();
|
||||
_transport.getEstablisher().receiveSessionDestroy(_remoteHostId, this);
|
||||
if (reason == REASON_BANNED) {
|
||||
_context.banlist().banlistRouter(_remotePeer.calculateHash(), "They banned us", null, null, _context.clock().now() + 2*60*60*1000);
|
||||
} else if (reason == REASON_MSG1) {
|
||||
// this is like a short ban
|
||||
_context.banlist().banlistRouter(_remotePeer.calculateHash(), "They banned us", null, null, _context.clock().now() + 20*60*1000);
|
||||
}
|
||||
// TODO handle other cases - skew?
|
||||
}
|
||||
|
||||
public void gotPathChallenge(RemoteHostId from, byte[] data) {
|
||||
@@ -470,9 +477,9 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
if (sid != _sendConnID)
|
||||
throw new GeneralSecurityException("Conn ID mismatch: 1: " + _sendConnID + " 2: " + sid);
|
||||
long token = DataHelper.fromLong8(data, off + TOKEN_OFFSET);
|
||||
if (token == 0)
|
||||
throw new GeneralSecurityException("Bad token 0 in retry");
|
||||
_token = token;
|
||||
// continue and decrypt even if token == 0 to get and log termination reason
|
||||
if (token != 0)
|
||||
_token = token;
|
||||
_timeReceived = 0;
|
||||
ChaChaPolyCipherState chacha = new ChaChaPolyCipherState();
|
||||
chacha.initializeKey(_rcvHeaderEncryptKey1, 0);
|
||||
@@ -491,6 +498,9 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
|
||||
// termination block received
|
||||
return;
|
||||
}
|
||||
// generally will be with termination, so do this check after
|
||||
if (token == 0)
|
||||
throw new GeneralSecurityException("Bad token 0 in retry");
|
||||
if (_timeReceived == 0)
|
||||
throw new GeneralSecurityException("No DateTime block in Retry");
|
||||
// _nextSend is now(), from packetReceived()
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.i2p.router.transport.udp;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
@@ -379,8 +380,8 @@ class PacketBuilder2 {
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
public UDPPacket buildTokenRequestPacket(OutboundEstablishState2 state) {
|
||||
long n = _context.random().signedNextInt() & 0xFFFFFFFFL;
|
||||
@@ -400,8 +401,8 @@ class PacketBuilder2 {
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
public UDPPacket buildSessionRequestPacket(OutboundEstablishState2 state) {
|
||||
long n = _context.random().signedNextInt() & 0xFFFFFFFFL;
|
||||
@@ -421,8 +422,8 @@ class PacketBuilder2 {
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
public UDPPacket buildSessionCreatedPacket(InboundEstablishState2 state) {
|
||||
long n = _context.random().signedNextInt() & 0xFFFFFFFFL;
|
||||
@@ -443,13 +444,13 @@ class PacketBuilder2 {
|
||||
state.createdPacketSent(pkt);
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a new Retry packet for the given peer, encrypting it
|
||||
* as necessary.
|
||||
*
|
||||
*
|
||||
* @param terminationCode 0 normally, nonzero to send termination block
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
public UDPPacket buildRetryPacket(InboundEstablishState2 state, int terminationCode) {
|
||||
long n = _context.random().signedNextInt() & 0xFFFFFFFFL;
|
||||
@@ -470,6 +471,28 @@ class PacketBuilder2 {
|
||||
state.retryPacketSent();
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new Retry packet with a termination code, for a rejection
|
||||
* direct from the EstablishmentManager. No InboundEstablishState2 required.
|
||||
*
|
||||
* @param terminationCode must be greater than zero
|
||||
* @return ready to send packet, non-null
|
||||
* @since 0.9.57
|
||||
*/
|
||||
public UDPPacket buildRetryPacket(RemoteHostId to, SocketAddress toAddr, long destID, long srcID, int terminationCode) {
|
||||
long n = _context.random().signedNextInt() & 0xFFFFFFFFL;
|
||||
UDPPacket packet = buildLongPacketHeader(destID, n, RETRY_FLAG_BYTE, srcID, 0);
|
||||
DatagramPacket pkt = packet.getPacket();
|
||||
pkt.setLength(LONG_HEADER_SIZE);
|
||||
byte[] introKey = _transport.getSSU2StaticIntroKey();
|
||||
encryptRetry(packet, introKey, n, introKey, introKey,
|
||||
to.getIP(), to.getPort(), terminationCode);
|
||||
pkt.setSocketAddress(toAddr);
|
||||
packet.setMessageType(TYPE_CREAT);
|
||||
packet.setPriority(PRIORITY_LOW);
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new series of SessionConfirmed packets for the given peer,
|
||||
@@ -479,8 +502,8 @@ class PacketBuilder2 {
|
||||
* a single Session Confirmed message. The remaining RI blocks will be passed to
|
||||
* the establish state via confirmedPacketsSent(), and the state will
|
||||
* transmit them via the new PeerState2.
|
||||
*
|
||||
* @return ready to send packets, or null if there was a problem
|
||||
*
|
||||
* @return ready to send packets, non-null
|
||||
*/
|
||||
public UDPPacket[] buildSessionConfirmedPackets(OutboundEstablishState2 state, RouterInfo ourInfo) {
|
||||
boolean gzip = false;
|
||||
@@ -540,8 +563,8 @@ class PacketBuilder2 {
|
||||
|
||||
/**
|
||||
* Build a single new SessionConfirmed packet for the given peer, unfragmented.
|
||||
*
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
*
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
private UDPPacket buildSessionConfirmedPacket(OutboundEstablishState2 state, SSU2Payload.RIBlock block) {
|
||||
UDPPacket packet = buildShortPacketHeader(state.getSendConnID(), 0, SESSION_CONFIRMED_FLAG_BYTE);
|
||||
@@ -657,8 +680,8 @@ class PacketBuilder2 {
|
||||
/**
|
||||
* Build a packet as Alice, to Bob to begin a peer test.
|
||||
* In-session, message 1.
|
||||
*
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
*
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
public UDPPacket buildPeerTestFromAlice(byte[] signedData, PeerState2 bob) {
|
||||
Block block = new SSU2Payload.PeerTestBlock(1, 0, null, signedData);
|
||||
@@ -670,8 +693,8 @@ class PacketBuilder2 {
|
||||
/**
|
||||
* Build a packet as Alice to Charlie.
|
||||
* Out-of-session, message 6.
|
||||
*
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
*
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
public UDPPacket buildPeerTestFromAlice(InetAddress toIP, int toPort, SessionKey introKey,
|
||||
long sendID, long rcvID, byte[] signedData) {
|
||||
@@ -694,7 +717,7 @@ class PacketBuilder2 {
|
||||
* In-session, message 4.
|
||||
*
|
||||
* @param charlieHash fake hash (all zeros) if rejected by bob
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
public UDPPacket buildPeerTestToAlice(int code, Hash charlieHash, byte[] signedData, PeerState2 alice) {
|
||||
Block block = new SSU2Payload.PeerTestBlock(4, code, charlieHash, signedData);
|
||||
@@ -706,8 +729,8 @@ class PacketBuilder2 {
|
||||
/**
|
||||
* Build a packet as Charlie to Alice.
|
||||
* Out-of-session, messages 5 and 7.
|
||||
*
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
*
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
public UDPPacket buildPeerTestToAlice(InetAddress aliceIP, int alicePort, SessionKey introKey,
|
||||
boolean firstSend,
|
||||
@@ -729,8 +752,8 @@ class PacketBuilder2 {
|
||||
/**
|
||||
* Build a packet as Bob to Charlie to help test Alice.
|
||||
* In-session, message 2.
|
||||
*
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
*
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
public UDPPacket buildPeerTestToCharlie(Hash aliceHash, byte[] signedData, PeerState2 charlie) {
|
||||
Block block = new SSU2Payload.PeerTestBlock(2, 0, aliceHash, signedData);
|
||||
@@ -742,8 +765,8 @@ class PacketBuilder2 {
|
||||
/**
|
||||
* Build a packet as Charlie to Bob verifying that we will help test Alice.
|
||||
* In-session, message 3.
|
||||
*
|
||||
* @return ready to send packet, or null if there was a problem
|
||||
*
|
||||
* @return ready to send packet, non-null
|
||||
*/
|
||||
public UDPPacket buildPeerTestToBob(int code, byte[] signedData, PeerState2 bob) {
|
||||
Block block = new SSU2Payload.PeerTestBlock(3, code, null, signedData);
|
||||
@@ -757,7 +780,7 @@ class PacketBuilder2 {
|
||||
* In-session.
|
||||
*
|
||||
* @param signedData flag + signed data
|
||||
* @return null on failure
|
||||
* @return non-null
|
||||
*/
|
||||
UDPPacket buildRelayRequest(byte[] signedData, PeerState2 bob) {
|
||||
Block block = new SSU2Payload.RelayRequestBlock(signedData);
|
||||
@@ -772,7 +795,7 @@ class PacketBuilder2 {
|
||||
* In-session.
|
||||
*
|
||||
* @param signedData flag + alice hash + signed data
|
||||
* @return null on failure
|
||||
* @return non-null
|
||||
*/
|
||||
UDPPacket buildRelayIntro(byte[] signedData, PeerState2 charlie) {
|
||||
Block block = new SSU2Payload.RelayIntroBlock(signedData);
|
||||
@@ -787,7 +810,7 @@ class PacketBuilder2 {
|
||||
*
|
||||
* @param signedData flag + response code + signed data + optional token
|
||||
* @param state Alice or Bob
|
||||
* @return null on failure
|
||||
* @return non-null
|
||||
*/
|
||||
UDPPacket buildRelayResponse(byte[] signedData, PeerState2 state) {
|
||||
Block block = new SSU2Payload.RelayResponseBlock(signedData);
|
||||
@@ -823,8 +846,8 @@ class PacketBuilder2 {
|
||||
* @return a packet with the first 32 bytes filled in
|
||||
*/
|
||||
private UDPPacket buildLongPacketHeader(long destID, long pktNum, byte type, long srcID, long token) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Building long header destID " + destID + " pkt num " + pktNum + " type " + type + " srcID " + srcID + " token " + token);
|
||||
//if (_log.shouldDebug())
|
||||
// _log.debug("Building long header destID " + destID + " pkt num " + pktNum + " type " + type + " srcID " + srcID + " token " + token);
|
||||
UDPPacket packet = buildShortPacketHeader(destID, pktNum, type);
|
||||
byte data[] = packet.getPacket().getData();
|
||||
data[13] = PROTOCOL_VERSION;
|
||||
@@ -980,7 +1003,7 @@ class PacketBuilder2 {
|
||||
|
||||
/**
|
||||
* Also used for hole punch with a relay request block.
|
||||
* Also used for retry with ptBlock = null
|
||||
* Also used for retry with (usually) ptBlock = null
|
||||
*
|
||||
* @param packet containing only 32 byte header
|
||||
* @param ptBlock Peer Test or Relay Request block. Null for retry.
|
||||
@@ -1193,8 +1216,4 @@ class PacketBuilder2 {
|
||||
}
|
||||
return new SSU2Payload.PaddingBlock(padlen);
|
||||
}
|
||||
|
||||
private void writePayload(List<Block> blocks, byte[] data, int off) {
|
||||
SSU2Payload.writePayload(data, off, blocks);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user