forked from I2P_Developers/i2p.i2p
- Much improved peer test defenses
- Minor improvements to relay defenses
This commit is contained in:
@@ -18,7 +18,7 @@ public class RouterVersion {
|
||||
/** deprecated */
|
||||
public final static String ID = "Monotone";
|
||||
public final static String VERSION = CoreVersion.VERSION;
|
||||
public final static long BUILD = 9;
|
||||
public final static long BUILD = 10;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
|
@@ -146,6 +146,7 @@ class EstablishmentManager {
|
||||
_context.statManager().createRateStat("udp.establishDropped", "Dropped an inbound establish message", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.establishRejected", "How many pending outbound connections are there when we refuse to add any more?", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.establishOverflow", "How many messages were queued up on a pending connection when it was too much?", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.establishBadIP", "Received IP or port was bad", "udp", UDPTransport.RATES);
|
||||
// following are for PeerState
|
||||
_context.statManager().createRateStat("udp.congestionOccurred", "How large the cwin was when congestion occurred (duration == sendBps)", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.congestedRTO", "retransmission timeout after congestion (duration == rtt dev)", "udp", UDPTransport.RATES);
|
||||
@@ -251,6 +252,7 @@ class EstablishmentManager {
|
||||
_transport.failed(msg, "Remote peer's IP isn't valid");
|
||||
_transport.markUnreachable(toHash);
|
||||
//_context.shitlist().shitlistRouter(msg.getTarget().getIdentity().calculateHash(), "Invalid SSU address", UDPTransport.STYLE);
|
||||
_context.statManager().addRateData("udp.establishBadIP", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -439,6 +441,7 @@ class EstablishmentManager {
|
||||
if (_context.blocklist().isBlocklisted(from.getIP())) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Receive session request from blocklisted IP: " + from);
|
||||
_context.statManager().addRateData("udp.establishBadIP", 1);
|
||||
return; // drop the packet
|
||||
}
|
||||
if (!_transport.allowConnection())
|
||||
@@ -889,15 +892,14 @@ class EstablishmentManager {
|
||||
byte ip[] = new byte[sz];
|
||||
reader.getRelayResponseReader().readCharlieIP(ip, 0);
|
||||
int port = reader.getRelayResponseReader().readCharliePort();
|
||||
if ((!isValid(ip, port)) || (!isValid(bob.getIP(), bob.getPort()))) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Bad relay resp from " + bob + " for " + Addresses.toString(ip, port));
|
||||
_context.statManager().addRateData("udp.relayBadIP", 1);
|
||||
return;
|
||||
}
|
||||
InetAddress addr = null;
|
||||
try {
|
||||
if (!_transport.isValid(ip))
|
||||
throw new UnknownHostException("non-public IP");
|
||||
// let's not relay to a privileged port, sounds like trouble
|
||||
if (port < 1024 || port > 65535)
|
||||
throw new UnknownHostException("bad port " + port);
|
||||
if (Arrays.equals(ip, _transport.getExternalIP()))
|
||||
throw new UnknownHostException("relay myself");
|
||||
addr = InetAddress.getByAddress(ip);
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -931,7 +933,19 @@ class EstablishmentManager {
|
||||
}
|
||||
notifyActivity();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Are IP and port valid?
|
||||
* @since 0.9.3
|
||||
*/
|
||||
private boolean isValid(byte[] ip, int port) {
|
||||
return port >= 1024 &&
|
||||
port <= 65535 &&
|
||||
_transport.isValid(ip) &&
|
||||
(!Arrays.equals(ip, _transport.getExternalIP())) &&
|
||||
(!_context.blocklist().isBlocklisted(ip));
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that while a SessionConfirmed could in theory be fragmented,
|
||||
* in practice a RouterIdentity is 387 bytes and a single fragment is 512 bytes max,
|
||||
|
@@ -64,6 +64,7 @@ class IntroductionManager {
|
||||
ctx.statManager().createRateStat("udp.receiveRelayIntro", "How often we get a relayed request for us to talk to someone?", "udp", UDPTransport.RATES);
|
||||
ctx.statManager().createRateStat("udp.receiveRelayRequest", "How often we receive a good request to relay to someone else?", "udp", UDPTransport.RATES);
|
||||
ctx.statManager().createRateStat("udp.receiveRelayRequestBadTag", "Received relay requests with bad/expired tag", "udp", UDPTransport.RATES);
|
||||
ctx.statManager().createRateStat("udp.relayBadIP", "Received IP or port was bad", "udp", UDPTransport.RATES);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
@@ -160,7 +161,7 @@ class IntroductionManager {
|
||||
}
|
||||
byte[] ip = cur.getRemoteIP();
|
||||
int port = cur.getRemotePort();
|
||||
if (ip == null || !TransportImpl.isPubliclyRoutable(ip) || port < 1024 || port > 65535)
|
||||
if (!isValid(ip, port))
|
||||
continue;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Picking introducer: " + cur);
|
||||
@@ -227,7 +228,7 @@ class IntroductionManager {
|
||||
void receiveRelayIntro(RemoteHostId bob, UDPPacketReader reader) {
|
||||
if (_context.router().isHidden())
|
||||
return;
|
||||
_context.statManager().addRateData("udp.receiveRelayIntro", 1, 0);
|
||||
_context.statManager().addRateData("udp.receiveRelayIntro", 1);
|
||||
|
||||
if (!_transport.allowConnection()) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -239,23 +240,25 @@ class IntroductionManager {
|
||||
byte ip[] = new byte[ipSize];
|
||||
reader.getRelayIntroReader().readIP(ip, 0);
|
||||
int port = reader.getRelayIntroReader().readPort();
|
||||
|
||||
if ((!isValid(ip, port)) || (!isValid(bob.getIP(), bob.getPort()))) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Bad relay intro from " + bob + " for " + Addresses.toString(ip, port));
|
||||
_context.statManager().addRateData("udp.relayBadIP", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Receive relay intro from " + bob + " for " + Addresses.toString(ip, port));
|
||||
|
||||
InetAddress to = null;
|
||||
try {
|
||||
if (!_transport.isValid(ip))
|
||||
throw new UnknownHostException("non-public IP");
|
||||
// let's not punch to a privileged port, sounds like trouble
|
||||
if (port < 1024 || port > 65535)
|
||||
throw new UnknownHostException("bad port " + port);
|
||||
if (Arrays.equals(ip, _transport.getExternalIP()))
|
||||
throw new UnknownHostException("punch myself");
|
||||
to = InetAddress.getByAddress(ip);
|
||||
} catch (UnknownHostException uhe) {
|
||||
// shitlist Bob?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("IP for alice to hole punch to is invalid", uhe);
|
||||
_context.statManager().addRateData("udp.relayBadIP", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -321,7 +324,20 @@ class IntroductionManager {
|
||||
void receiveRelayRequest(RemoteHostId alice, UDPPacketReader reader) {
|
||||
if (_context.router().isHidden())
|
||||
return;
|
||||
long tag = reader.getRelayRequestReader().readTag();
|
||||
UDPPacketReader.RelayRequestReader rrReader = reader.getRelayRequestReader();
|
||||
long tag = rrReader.readTag();
|
||||
int ipSize = rrReader.readIPSize();
|
||||
byte ip[] = new byte[ipSize];
|
||||
rrReader.readIP(ip, 0);
|
||||
int port = rrReader.readPort();
|
||||
|
||||
if ((!isValid(ip, port)) || (!isValid(alice.getIP(), alice.getPort()))) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Bad relay req from " + alice + " for " + Addresses.toString(ip, port));
|
||||
_context.statManager().addRateData("udp.relayBadIP", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
PeerState charlie = get(tag);
|
||||
if (charlie == null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@@ -346,4 +362,16 @@ class IntroductionManager {
|
||||
// send alice back charlie's info
|
||||
_transport.send(_builder.buildRelayResponse(alice, charlie, reader.getRelayRequestReader().readNonce(), aliceIntroKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Are IP and port valid?
|
||||
* @since 0.9.3
|
||||
*/
|
||||
private boolean isValid(byte[] ip, int port) {
|
||||
return port >= 1024 &&
|
||||
port <= 65535 &&
|
||||
_transport.isValid(ip) &&
|
||||
(!Arrays.equals(ip, _transport.getExternalIP())) &&
|
||||
(!_context.blocklist().isBlocklisted(ip));
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package net.i2p.router.transport.udp;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -123,6 +124,7 @@ class PeerTestManager {
|
||||
_context.statManager().createRateStat("udp.statusKnownCharlie", "How often the bob we pick passes us to a charlie we already have a session with?", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.receiveTestReply", "How often we get a reply to our peer test?", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.receiveTest", "How often we get a packet requesting us to participate in a peer test?", "udp", UDPTransport.RATES);
|
||||
_context.statManager().createRateStat("udp.testBadIP", "Received IP or port was bad", "udp", UDPTransport.RATES);
|
||||
}
|
||||
|
||||
private static final int RESEND_TIMEOUT = 5*1000;
|
||||
@@ -240,7 +242,7 @@ class PeerTestManager {
|
||||
* test. We are Alice.
|
||||
*/
|
||||
private synchronized void receiveTestReply(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo) {
|
||||
_context.statManager().addRateData("udp.receiveTestReply", 1, 0);
|
||||
_context.statManager().addRateData("udp.receiveTestReply", 1);
|
||||
PeerTestState test = _currentTest;
|
||||
if (expired())
|
||||
return;
|
||||
@@ -270,7 +272,10 @@ class PeerTestManager {
|
||||
InetAddress addr = InetAddress.getByAddress(ip);
|
||||
test.setAliceIP(addr);
|
||||
test.setReceiveBobTime(_context.clock().now());
|
||||
test.setAlicePort(testInfo.readPort());
|
||||
int testPort = testInfo.readPort();
|
||||
if (testPort == 0)
|
||||
throw new UnknownHostException("port 0");
|
||||
test.setAlicePort(testPort);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive test reply from bob @ " + from + " via our " + test.getAlicePort() + "/" + test.getAlicePortFromCharlie());
|
||||
@@ -280,6 +285,7 @@ class PeerTestManager {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to get our IP (length " + ipSize +
|
||||
") from bob's reply: " + from + ", " + testInfo, uhe);
|
||||
_context.statManager().addRateData("udp.testBadIP", 1);
|
||||
}
|
||||
} else {
|
||||
// The reply is from Charlie
|
||||
@@ -294,7 +300,7 @@ class PeerTestManager {
|
||||
+ _currentTest + ", charlie: " + from + ")");
|
||||
// why are we doing this instead of calling testComplete() ?
|
||||
_currentTestComplete = true;
|
||||
_context.statManager().addRateData("udp.statusKnownCharlie", 1, 0);
|
||||
_context.statManager().addRateData("udp.statusKnownCharlie", 1);
|
||||
honorStatus(CommSystemFacade.STATUS_UNKNOWN);
|
||||
_currentTest = null;
|
||||
return;
|
||||
@@ -302,10 +308,13 @@ class PeerTestManager {
|
||||
|
||||
if (test.getReceiveCharlieTime() > 0) {
|
||||
// this is our second charlie, yay!
|
||||
test.setAlicePortFromCharlie(testInfo.readPort());
|
||||
byte ip[] = new byte[testInfo.readIPSize()];
|
||||
testInfo.readIP(ip, 0);
|
||||
try {
|
||||
int testPort = testInfo.readPort();
|
||||
if (testPort == 0)
|
||||
throw new UnknownHostException("port 0");
|
||||
test.setAlicePortFromCharlie(testPort);
|
||||
byte ip[] = new byte[testInfo.readIPSize()];
|
||||
testInfo.readIP(ip, 0);
|
||||
InetAddress addr = InetAddress.getByAddress(ip);
|
||||
test.setAliceIPFromCharlie(addr);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -316,6 +325,7 @@ class PeerTestManager {
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Charlie @ " + from + " said we were an invalid IP address: " + uhe.getMessage(), uhe);
|
||||
_context.statManager().addRateData("udp.testBadIP", 1);
|
||||
}
|
||||
} else {
|
||||
if (test.getPacketsRelayed() > MAX_RELAYED_PER_TEST) {
|
||||
@@ -343,6 +353,7 @@ class PeerTestManager {
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Charlie's IP is b0rked: " + from + ": " + testInfo);
|
||||
_context.statManager().addRateData("udp.testBadIP", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,10 +426,42 @@ class PeerTestManager {
|
||||
* We could be Alice, Bob, or Charlie.
|
||||
*/
|
||||
public void receiveTest(RemoteHostId from, UDPPacketReader reader) {
|
||||
_context.statManager().addRateData("udp.receiveTest", 1, 0);
|
||||
_context.statManager().addRateData("udp.receiveTest", 1);
|
||||
byte[] fromIP = from.getIP();
|
||||
int fromPort = from.getPort();
|
||||
if (fromPort < 1024 || fromPort > 65535 ||
|
||||
(!_transport.isValid(fromIP)) ||
|
||||
_context.blocklist().isBlocklisted(fromIP)) {
|
||||
// spoof check, and don't respond to privileged ports
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid PeerTest address: " + Addresses.toString(fromIP, fromPort));
|
||||
_context.statManager().addRateData("udp.testBadIP", 1);
|
||||
return;
|
||||
}
|
||||
UDPPacketReader.PeerTestReader testInfo = reader.getPeerTestReader();
|
||||
byte testIP[] = null;
|
||||
int testPort = testInfo.readPort();
|
||||
|
||||
if (testInfo.readIPSize() > 0) {
|
||||
testIP = new byte[testInfo.readIPSize()];
|
||||
testInfo.readIP(testIP, 0);
|
||||
}
|
||||
|
||||
if ((testPort > 0 && (testPort < 1024 || testPort > 65535)) ||
|
||||
(testIP != null && (Arrays.equals(testIP, _transport.getExternalIP()) ||
|
||||
(!_transport.isValid(testIP)) ||
|
||||
_context.blocklist().isBlocklisted(testIP)))) {
|
||||
// spoof check, and don't respond to privileged ports
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid address in PeerTest: " + Addresses.toString(testIP, testPort));
|
||||
_context.statManager().addRateData("udp.testBadIP", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// The from IP/port and message's IP/port are now validated.
|
||||
// EXCEPT that either the message's IP could be empty or the message's port could be 0.
|
||||
// Both of those cases should be checked in receiveXfromY() as appropriate.
|
||||
|
||||
long nonce = testInfo.readNonce();
|
||||
PeerTestState test = _currentTest;
|
||||
if ( (test != null) && (test.getNonce() == nonce) ) {
|
||||
@@ -429,22 +472,11 @@ class PeerTestManager {
|
||||
|
||||
// we are Bob or Charlie
|
||||
|
||||
if ( (testInfo.readIPSize() > 0) && (testPort > 0) ) {
|
||||
testIP = new byte[testInfo.readIPSize()];
|
||||
testInfo.readIP(testIP, 0);
|
||||
}
|
||||
|
||||
PeerTestState state = _activeTests.get(Long.valueOf(nonce));
|
||||
|
||||
if (state == null) {
|
||||
// NEW TEST
|
||||
if ((testPort > 0 && (testPort < 1024 || testPort > 65535)) ||
|
||||
(testIP != null && !_transport.isValid(testIP))) {
|
||||
// spoof check, and don't respond to privileged ports
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid IP/Port rcvd in PeerTest: " + Addresses.toString(testIP, testPort));
|
||||
return;
|
||||
} else if ( (testIP == null) || (testPort <= 0) ) {
|
||||
if ( (testIP == null) || (testPort <= 0) ) {
|
||||
// we are bob, since we haven't seen this nonce before AND its coming from alice
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("test IP/port are blank coming from " + from + ", assuming we are Bob and they are alice");
|
||||
@@ -464,11 +496,11 @@ class PeerTestManager {
|
||||
} else {
|
||||
// EXISTING TEST
|
||||
if (state.getOurRole() == PeerTestState.BOB) {
|
||||
if (DataHelper.eq(from.getIP(), state.getAliceIP().getAddress()) &&
|
||||
(from.getPort() == state.getAlicePort()) ) {
|
||||
if (DataHelper.eq(fromIP, state.getAliceIP().getAddress()) &&
|
||||
(fromPort == state.getAlicePort()) ) {
|
||||
receiveFromAliceAsBob(from, testInfo, nonce, state);
|
||||
} else if (DataHelper.eq(from.getIP(), state.getCharlieIP().getAddress()) &&
|
||||
(from.getPort() == state.getCharliePort()) ) {
|
||||
} else if (DataHelper.eq(fromIP, state.getCharlieIP().getAddress()) &&
|
||||
(fromPort == state.getCharliePort()) ) {
|
||||
receiveFromCharlieAsBob(from, state);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -509,6 +541,8 @@ class PeerTestManager {
|
||||
try {
|
||||
testInfo.readIP(aliceIPData, 0);
|
||||
int alicePort = testInfo.readPort();
|
||||
if (alicePort == 0)
|
||||
throw new UnknownHostException("port 0");
|
||||
InetAddress aliceIP = InetAddress.getByAddress(aliceIPData);
|
||||
InetAddress bobIP = InetAddress.getByAddress(from.getIP());
|
||||
SessionKey aliceIntroKey = new SessionKey(new byte[SessionKey.KEYSIZE_BYTES]);
|
||||
@@ -558,6 +592,7 @@ class PeerTestManager {
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to build the aliceIP from " + from + ", ip size: " + sz + " ip val: " + Base64.encode(aliceIPData), uhe);
|
||||
_context.statManager().addRateData("udp.testBadIP", 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,6 +684,7 @@ class PeerTestManager {
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to build the aliceIP from " + from, uhe);
|
||||
_context.statManager().addRateData("udp.testBadIP", 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,6 +731,7 @@ class PeerTestManager {
|
||||
} catch (UnknownHostException uhe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to build the aliceIP from " + from, uhe);
|
||||
_context.statManager().addRateData("udp.testBadIP", 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user