* Streaming:
- Detect and drop dup SYNs rather than create a duplicate connection - will hopefully fix "Received a syn with the wrong IDs" - Send reset for a SYN ACK with the wrong IDs - Don't send a reset to a null dest - Logging tweaks - Cleanups
This commit is contained in:
@ -385,7 +385,7 @@ public class Connection {
|
|||||||
* Process the acks and nacks received in a packet
|
* Process the acks and nacks received in a packet
|
||||||
* @return List of packets acked or null
|
* @return List of packets acked or null
|
||||||
*/
|
*/
|
||||||
List ackPackets(long ackThrough, long nacks[]) {
|
List<PacketLocal> ackPackets(long ackThrough, long nacks[]) {
|
||||||
if (ackThrough < _highestAckedThrough) {
|
if (ackThrough < _highestAckedThrough) {
|
||||||
// dupack which won't tell us anything
|
// dupack which won't tell us anything
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,7 +2,6 @@ package net.i2p.client.streaming;
|
|||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.data.ByteArray;
|
import net.i2p.data.ByteArray;
|
||||||
import net.i2p.data.DataHelper;
|
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,7 +186,7 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
|||||||
packet.setOptionalFrom(con.getSession().getMyDestination());
|
packet.setOptionalFrom(con.getSession().getMyDestination());
|
||||||
packet.setOptionalMaxSize(con.getOptions().getMaxMessageSize());
|
packet.setOptionalMaxSize(con.getOptions().getMaxMessageSize());
|
||||||
}
|
}
|
||||||
if (DataHelper.eq(con.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) {
|
if (con.getSendStreamId() == Packet.STREAM_ID_UNKNOWN) {
|
||||||
packet.setFlag(Packet.FLAG_NO_ACK);
|
packet.setFlag(Packet.FLAG_NO_ACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SimpleScheduler;
|
import net.i2p.util.SimpleScheduler;
|
||||||
import net.i2p.util.SimpleTimer;
|
import net.i2p.util.SimpleTimer;
|
||||||
@ -73,8 +74,8 @@ public class ConnectionHandler {
|
|||||||
sendReset(packet);
|
sendReset(packet);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.INFO))
|
||||||
_log.debug("Receive new SYN: " + packet + ": timeout in " + _acceptTimeout);
|
_log.info("Receive new SYN: " + packet + ": timeout in " + _acceptTimeout);
|
||||||
// also check if expiration of the head is long past for overload detection with peek() ?
|
// also check if expiration of the head is long past for overload detection with peek() ?
|
||||||
boolean success = _synQueue.offer(packet); // fail immediately if full
|
boolean success = _synQueue.offer(packet); // fail immediately if full
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -145,10 +146,29 @@ public class ConnectionHandler {
|
|||||||
if (syn.getOptionalDelay() == PoisonPacket.POISON_MAX_DELAY_REQUEST)
|
if (syn.getOptionalDelay() == PoisonPacket.POISON_MAX_DELAY_REQUEST)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// deal with forged / invalid syn packets
|
// deal with forged / invalid syn packets in _manager.receiveConnection()
|
||||||
|
|
||||||
// Handle both SYN and non-SYN packets in the queue
|
// Handle both SYN and non-SYN packets in the queue
|
||||||
if (syn.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
if (syn.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||||
|
// We are single-threaded here, so this is
|
||||||
|
// a good place to check for dup SYNs and drop them
|
||||||
|
Destination from = syn.getOptionalFrom();
|
||||||
|
if (from == null) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Dropping SYN packet with no FROM: " + syn);
|
||||||
|
// drop it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Connection oldcon = _manager.getConnectionByOutboundId(syn.getReceiveStreamId());
|
||||||
|
if (oldcon != null) {
|
||||||
|
// His ID not guaranteed to be unique to us, but probably is...
|
||||||
|
// only drop it on a destination match too
|
||||||
|
if (from.equals(oldcon.getRemotePeer())) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Dropping dup SYN: " + syn);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
Connection con = _manager.receiveConnection(syn);
|
Connection con = _manager.receiveConnection(syn);
|
||||||
if (con != null)
|
if (con != null)
|
||||||
return con;
|
return con;
|
||||||
|
@ -9,7 +9,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.I2PException;
|
import net.i2p.I2PException;
|
||||||
import net.i2p.client.I2PSession;
|
import net.i2p.client.I2PSession;
|
||||||
import net.i2p.data.DataHelper;
|
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.data.SessionKey;
|
import net.i2p.data.SessionKey;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
@ -83,7 +82,7 @@ public class ConnectionManager {
|
|||||||
*/
|
*/
|
||||||
Connection getConnectionByOutboundId(long id) {
|
Connection getConnectionByOutboundId(long id) {
|
||||||
for (Connection con : _connectionByInboundId.values()) {
|
for (Connection con : _connectionByInboundId.values()) {
|
||||||
if (DataHelper.eq(con.getSendStreamId(), id))
|
if (con.getSendStreamId() == id)
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -169,6 +168,7 @@ public class ConnectionManager {
|
|||||||
|
|
||||||
con.setReceiveStreamId(receiveId);
|
con.setReceiveStreamId(receiveId);
|
||||||
try {
|
try {
|
||||||
|
// This validates the packet, and sets the con's SendStreamID and RemotePeer
|
||||||
con.getPacketHandler().receivePacket(synPacket, con);
|
con.getPacketHandler().receivePacket(synPacket, con);
|
||||||
} catch (I2PException ie) {
|
} catch (I2PException ie) {
|
||||||
_connectionByInboundId.remove(Long.valueOf(receiveId));
|
_connectionByInboundId.remove(Long.valueOf(receiveId));
|
||||||
|
@ -359,7 +359,11 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
|||||||
if (_rtt > 60*1000)
|
if (_rtt > 60*1000)
|
||||||
_rtt = 60*1000;
|
_rtt = 60*1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRTO() { return _rto; }
|
public int getRTO() { return _rto; }
|
||||||
|
|
||||||
|
/** for debugging @since 0.7.13 */
|
||||||
|
int getRTTDev() { return _rttDev; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If we have 3 consecutive rtt increases, we are trending upwards (1), or if we have
|
* If we have 3 consecutive rtt increases, we are trending upwards (1), or if we have
|
||||||
|
@ -4,7 +4,6 @@ import java.util.List;
|
|||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.I2PException;
|
import net.i2p.I2PException;
|
||||||
import net.i2p.data.DataHelper;
|
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SimpleScheduler;
|
import net.i2p.util.SimpleScheduler;
|
||||||
@ -239,17 +238,17 @@ public class ConnectionPacketHandler {
|
|||||||
boolean firstAck = isNew && con.getHighestAckedThrough() < 0;
|
boolean firstAck = isNew && con.getHighestAckedThrough() < 0;
|
||||||
|
|
||||||
int numResends = 0;
|
int numResends = 0;
|
||||||
List acked = null;
|
List<PacketLocal> acked = null;
|
||||||
// if we don't know the streamIds for both sides of the connection, there's no way we
|
// if we don't know the streamIds for both sides of the connection, there's no way we
|
||||||
// could actually be acking data (this fixes the buggered up ack of packet 0 problem).
|
// could actually be acking data (this fixes the buggered up ack of packet 0 problem).
|
||||||
// this is called after packet verification, which places the stream IDs as necessary if
|
// this is called after packet verification, which places the stream IDs as necessary if
|
||||||
// the SYN verifies (so if we're acking w/out stream IDs, no SYN has been received yet)
|
// the SYN verifies (so if we're acking w/out stream IDs, no SYN has been received yet)
|
||||||
if ( (packet != null) && (packet.getSendStreamId() > 0) && (packet.getReceiveStreamId() > 0) &&
|
if ( (packet != null) && (packet.getSendStreamId() > 0) && (packet.getReceiveStreamId() > 0) &&
|
||||||
(con != null) && (con.getSendStreamId() > 0) && (con.getReceiveStreamId() > 0) &&
|
(con != null) && (con.getSendStreamId() > 0) && (con.getReceiveStreamId() > 0) &&
|
||||||
(!DataHelper.eq(packet.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
|
(packet.getSendStreamId() != Packet.STREAM_ID_UNKNOWN) &&
|
||||||
(!DataHelper.eq(packet.getReceiveStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
|
(packet.getReceiveStreamId() != Packet.STREAM_ID_UNKNOWN) &&
|
||||||
(!DataHelper.eq(con.getSendStreamId(), Packet.STREAM_ID_UNKNOWN)) &&
|
(con.getSendStreamId() != Packet.STREAM_ID_UNKNOWN) &&
|
||||||
(!DataHelper.eq(con.getReceiveStreamId(), Packet.STREAM_ID_UNKNOWN)) )
|
(con.getReceiveStreamId() != Packet.STREAM_ID_UNKNOWN) )
|
||||||
acked = con.ackPackets(ackThrough, nacks);
|
acked = con.ackPackets(ackThrough, nacks);
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
@ -261,7 +260,7 @@ public class ConnectionPacketHandler {
|
|||||||
// and the highest rtt lets us set our resend delay properly
|
// and the highest rtt lets us set our resend delay properly
|
||||||
int highestRTT = -1;
|
int highestRTT = -1;
|
||||||
for (int i = 0; i < acked.size(); i++) {
|
for (int i = 0; i < acked.size(); i++) {
|
||||||
PacketLocal p = (PacketLocal)acked.get(i);
|
PacketLocal p = acked.get(i);
|
||||||
if (p.getAckTime() > highestRTT) {
|
if (p.getAckTime() > highestRTT) {
|
||||||
//if (p.getNumSends() <= 1)
|
//if (p.getNumSends() <= 1)
|
||||||
highestRTT = p.getAckTime();
|
highestRTT = p.getAckTime();
|
||||||
@ -282,7 +281,15 @@ public class ConnectionPacketHandler {
|
|||||||
_log.debug("Packet acked after " + p.getAckTime() + "ms: " + p);
|
_log.debug("Packet acked after " + p.getAckTime() + "ms: " + p);
|
||||||
}
|
}
|
||||||
if (highestRTT > 0) {
|
if (highestRTT > 0) {
|
||||||
|
int oldrtt = con.getOptions().getRTT();
|
||||||
|
int oldrto = con.getOptions().getRTO();
|
||||||
|
int olddev = con.getOptions().getRTTDev();
|
||||||
con.getOptions().updateRTT(highestRTT);
|
con.getOptions().updateRTT(highestRTT);
|
||||||
|
if (_log.shouldLog(Log.INFO))
|
||||||
|
_log.info("acked: " + acked.size() + " highestRTT: " + highestRTT +
|
||||||
|
" RTT: " + oldrtt + " -> " + con.getOptions().getRTT() +
|
||||||
|
" RTO: " + oldrto + " -> " + con.getOptions().getRTO() +
|
||||||
|
" Dev: " + olddev + " -> " + con.getOptions().getRTTDev());
|
||||||
if (firstAck) {
|
if (firstAck) {
|
||||||
if (con.isInbound())
|
if (con.isInbound())
|
||||||
_context.statManager().addRateData("stream.con.initialRTT.in", highestRTT, 0);
|
_context.statManager().addRateData("stream.con.initialRTT.in", highestRTT, 0);
|
||||||
@ -357,7 +364,7 @@ public class ConnectionPacketHandler {
|
|||||||
// integers, so lets use a random distribution instead
|
// integers, so lets use a random distribution instead
|
||||||
int shouldIncrement = _context.random().nextInt(con.getOptions().getCongestionAvoidanceGrowthRateFactor()*newWindowSize);
|
int shouldIncrement = _context.random().nextInt(con.getOptions().getCongestionAvoidanceGrowthRateFactor()*newWindowSize);
|
||||||
if (shouldIncrement < acked)
|
if (shouldIncrement < acked)
|
||||||
newWindowSize += 1;
|
newWindowSize++;
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("cong. avoid acks = " + acked + " for " + con);
|
_log.debug("cong. avoid acks = " + acked + " for " + con);
|
||||||
}
|
}
|
||||||
@ -387,6 +394,11 @@ public class ConnectionPacketHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure this packet is ok and that we can continue processing its data.
|
* Make sure this packet is ok and that we can continue processing its data.
|
||||||
|
*
|
||||||
|
* SIDE EFFECT:
|
||||||
|
* Sets the SendStreamId and RemotePeer for the con,
|
||||||
|
* using the packet's ReceiveStreamId and OptionalFrom,
|
||||||
|
* If this is a SYN packet and the con's SendStreamId is not set.
|
||||||
*
|
*
|
||||||
* @return true if the packet is ok for this connection, false if we shouldn't
|
* @return true if the packet is ok for this connection, false if we shouldn't
|
||||||
* continue processing.
|
* continue processing.
|
||||||
@ -415,7 +427,7 @@ public class ConnectionPacketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) {
|
if (con.getSendStreamId() != packet.getReceiveStreamId()) {
|
||||||
if (_log.shouldLog(Log.ERROR))
|
if (_log.shouldLog(Log.ERROR))
|
||||||
_log.error("Packet received with the wrong reply stream id: "
|
_log.error("Packet received with the wrong reply stream id: "
|
||||||
+ con + " / " + packet);
|
+ con + " / " + packet);
|
||||||
@ -431,7 +443,7 @@ public class ConnectionPacketHandler {
|
|||||||
* Make sure this RST packet is valid, and if it is, act on it.
|
* Make sure this RST packet is valid, and if it is, act on it.
|
||||||
*/
|
*/
|
||||||
private void verifyReset(Packet packet, Connection con) {
|
private void verifyReset(Packet packet, Connection con) {
|
||||||
if (DataHelper.eq(con.getReceiveStreamId(), packet.getSendStreamId())) {
|
if (con.getReceiveStreamId() == packet.getSendStreamId()) {
|
||||||
boolean ok = packet.verifySignature(_context, packet.getOptionalFrom(), null);
|
boolean ok = packet.verifySignature(_context, packet.getOptionalFrom(), null);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
if (_log.shouldLog(Log.ERROR))
|
if (_log.shouldLog(Log.ERROR))
|
||||||
|
@ -7,7 +7,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.I2PException;
|
import net.i2p.I2PException;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,15 +19,15 @@ public class PacketHandler {
|
|||||||
private ConnectionManager _manager;
|
private ConnectionManager _manager;
|
||||||
private I2PAppContext _context;
|
private I2PAppContext _context;
|
||||||
private Log _log;
|
private Log _log;
|
||||||
private int _lastDelay;
|
//private int _lastDelay;
|
||||||
private int _dropped;
|
//private int _dropped;
|
||||||
|
|
||||||
public PacketHandler(I2PAppContext ctx, ConnectionManager mgr) {
|
public PacketHandler(I2PAppContext ctx, ConnectionManager mgr) {
|
||||||
_manager = mgr;
|
_manager = mgr;
|
||||||
_context = ctx;
|
_context = ctx;
|
||||||
_dropped = 0;
|
//_dropped = 0;
|
||||||
_log = ctx.logManager().getLog(PacketHandler.class);
|
_log = ctx.logManager().getLog(PacketHandler.class);
|
||||||
_lastDelay = _context.random().nextInt(30*1000);
|
//_lastDelay = _context.random().nextInt(30*1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** what is the point of this ? */
|
/** what is the point of this ? */
|
||||||
@ -167,7 +167,7 @@ public class PacketHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ( (con.getSendStreamId() <= 0) ||
|
if ( (con.getSendStreamId() <= 0) ||
|
||||||
(DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) ||
|
(con.getSendStreamId() == packet.getReceiveStreamId()) ||
|
||||||
(packet.getSequenceNum() <= ConnectionOptions.MIN_WINDOW_SIZE) ) { // its in flight from the first batch
|
(packet.getSequenceNum() <= ConnectionOptions.MIN_WINDOW_SIZE) ) { // its in flight from the first batch
|
||||||
long oldId = con.getSendStreamId();
|
long oldId = con.getSendStreamId();
|
||||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||||
@ -179,6 +179,7 @@ public class PacketHandler {
|
|||||||
} else {
|
} else {
|
||||||
if (_log.shouldLog(Log.ERROR))
|
if (_log.shouldLog(Log.ERROR))
|
||||||
_log.error("Received a syn with the wrong IDs, con=" + con + " packet=" + packet);
|
_log.error("Received a syn with the wrong IDs, con=" + con + " packet=" + packet);
|
||||||
|
sendReset(packet);
|
||||||
packet.releasePayload();
|
packet.releasePayload();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -199,16 +200,18 @@ public class PacketHandler {
|
|||||||
} else {
|
} else {
|
||||||
if (!con.getResetSent()) {
|
if (!con.getResetSent()) {
|
||||||
// someone is sending us a packet on the wrong stream
|
// someone is sending us a packet on the wrong stream
|
||||||
|
// It isn't a SYN so it isn't likely to have a FROM to send a reset back to
|
||||||
if (_log.shouldLog(Log.ERROR)) {
|
if (_log.shouldLog(Log.ERROR)) {
|
||||||
Set cons = _manager.listConnections();
|
Set cons = _manager.listConnections();
|
||||||
StringBuilder buf = new StringBuilder(512);
|
StringBuilder buf = new StringBuilder(512);
|
||||||
buf.append("Received a packet on the wrong stream: ");
|
buf.append("Received a packet on the wrong stream: ");
|
||||||
buf.append(packet);
|
buf.append(packet);
|
||||||
buf.append(" connection: ");
|
buf.append("\nthis connection:\n");
|
||||||
buf.append(con);
|
buf.append(con);
|
||||||
|
buf.append("\nall connections:");
|
||||||
for (Iterator iter = cons.iterator(); iter.hasNext();) {
|
for (Iterator iter = cons.iterator(); iter.hasNext();) {
|
||||||
Connection cur = (Connection)iter.next();
|
Connection cur = (Connection)iter.next();
|
||||||
buf.append(" ").append(cur);
|
buf.append('\n').append(cur);
|
||||||
}
|
}
|
||||||
_log.error(buf.toString(), new Exception("Wrong stream"));
|
_log.error(buf.toString(), new Exception("Wrong stream"));
|
||||||
}
|
}
|
||||||
@ -219,8 +222,22 @@ public class PacketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This sends a reset back to the place this packet came from.
|
||||||
|
* If the packet has no 'optional from' or valid signature, this does nothing.
|
||||||
|
* This is not associated with a connection, so no con stats are updated.
|
||||||
|
*/
|
||||||
private void sendReset(Packet packet) {
|
private void sendReset(Packet packet) {
|
||||||
PacketLocal reply = new PacketLocal(_context, packet.getOptionalFrom());
|
Destination from = packet.getOptionalFrom();
|
||||||
|
if (from == null)
|
||||||
|
return;
|
||||||
|
boolean ok = packet.verifySignature(_context, from, null);
|
||||||
|
if (!ok) {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn("Can't send reset after recv spoofed packet: " + packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PacketLocal reply = new PacketLocal(_context, from);
|
||||||
reply.setFlag(Packet.FLAG_RESET);
|
reply.setFlag(Packet.FLAG_RESET);
|
||||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||||
reply.setSendStreamId(packet.getReceiveStreamId());
|
reply.setSendStreamId(packet.getReceiveStreamId());
|
||||||
|
Reference in New Issue
Block a user