- Fix multiple-detection code in externalAddressReceived()

- Synchronize tracking of last IP/port
- Don't accept IPv6 address changes from peers
- Remove unused getLocalAddress()
- Pkg private getLocalPort()

Peer tests:
 - Use only IPv4 peer for Alice and Bob in peer tests; Charlie may be an IPv6 peer.
 - Enforce IPv4 (Alice's) address inside PeerTest packet
This commit is contained in:
zzz
2013-05-05 12:08:28 +00:00
parent 757df8c726
commit 368c2073b2
3 changed files with 97 additions and 33 deletions

View File

@ -940,7 +940,7 @@ class EstablishmentManager {
}
/**
* Are IP and port valid?
* Are IP and port valid? This is only for relay response.
* Refuse anybody in the same /16
* @since 0.9.3
*/

View File

@ -24,6 +24,13 @@ import net.i2p.util.SimpleTimer;
* Entry points are runTest() to start a new test as Alice,
* and receiveTest() for all received test packets.
*
* IPv6 info: All Alice-Bob and Alice-Charlie communication is via IPv4.
* The Bob-Charlie communication may be via IPv6, however Charlie must
* be IPv4-capable.
* The IP address (of Alice) in the message must be IPv4 if present,
* as we only support testing of IPv4.
* Testing of IPv6 could be added in the future.
*
* From udp.html on the website:
<p>The automation of collaborative reachability testing for peers is
@ -166,6 +173,8 @@ class PeerTestManager {
/**
* The next few methods are for when we are Alice
*
* @param bobIP IPv4 only
*/
public synchronized void runTest(InetAddress bobIP, int bobPort, SessionKey bobCipherKey, SessionKey bobMACKey) {
if (_currentTest != null) {
@ -304,7 +313,7 @@ class PeerTestManager {
// The reply is from Bob
int ipSize = testInfo.readIPSize();
if (ipSize != 4 && ipSize != 16) {
if (ipSize != 4) {
// There appears to be a bug where Bob is sending us a zero-length IP.
// We could proceed without setting the IP, but then when Charlie
// sends us his message, we will think we are behind a symmetric NAT
@ -366,6 +375,8 @@ class PeerTestManager {
throw new UnknownHostException("port 0");
test.setAlicePortFromCharlie(testPort);
byte ip[] = new byte[testInfo.readIPSize()];
if (ip.length != 4)
throw new UnknownHostException("not IPv4");
testInfo.readIP(ip, 0);
InetAddress addr = InetAddress.getByAddress(ip);
test.setAliceIPFromCharlie(addr);
@ -505,6 +516,7 @@ class PeerTestManager {
if ((testPort > 0 && (testPort < 1024 || testPort > 65535)) ||
(testIP != null &&
((!_transport.isValid(testIP)) ||
testIP.length != 4 ||
_context.blocklist().isBlocklisted(testIP)))) {
// spoof check, and don't respond to privileged ports
if (_log.shouldLog(Log.WARN))
@ -641,6 +653,8 @@ class PeerTestManager {
byte aliceIPData[] = new byte[sz];
try {
testInfo.readIP(aliceIPData, 0);
if (sz != 4)
throw new UnknownHostException("not IPv4");
int alicePort = testInfo.readPort();
if (alicePort == 0)
throw new UnknownHostException("port 0");
@ -706,7 +720,12 @@ class PeerTestManager {
PeerState charlie;
RouterInfo charlieInfo = null;
if (state == null) { // pick a new charlie
charlie = _transport.pickTestPeer(from);
if (from.getIP().length != 4) {
if (_log.shouldLog(Log.WARN))
_log.warn("PeerTest over IPv6 from Alice as Bob? " + from);
return;
}
charlie = _transport.pickTestPeer(CHARLIE, from);
} else {
charlie = _transport.getPeerState(new RemoteHostId(state.getCharlieIP().getAddress(), state.getCharliePort()));
}

View File

@ -38,6 +38,7 @@ import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.transport.Transport;
import net.i2p.router.transport.TransportBid;
import net.i2p.router.transport.TransportImpl;
import static net.i2p.router.transport.udp.PeerTestState.Role.*;
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
import net.i2p.router.util.RandomIterator;
import net.i2p.util.Addresses;
@ -446,8 +447,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
*/
SessionKey getIntroKey() { return _introKey; }
public InetAddress getLocalAddress() { return _externalListenHost; }
public int getExternalPort() { return _externalListenPort; }
int getExternalPort() { return _externalListenPort; }
/**
* @return IP or null
@ -517,8 +517,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
/**
* From config, UPnP, local i/f, ...
* Not for info received from peers - see externalAddressReceived(Hash, ip, port)
*
* @param source used for logging only
* @param source as defined in Transport.SOURCE_xxx
* @param ip publicly routable IPv4 or IPv6
* @param port 0 if unknown
*/
@ -535,12 +536,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
return;
if (source.equals(Transport.SOURCE_INTERFACE)) {
// temp prevent multiples
if (ip.length == 4 && !gotIPv4Addr) {
gotIPv4Addr = true;
return;
} else if (ip.length == 16 && !gotIPv6Addr) {
gotIPv6Addr = true;
return;
if (ip.length == 4) {
if (gotIPv4Addr)
return;
else
gotIPv4Addr = true;
} else if (ip.length == 16) {
if (gotIPv6Addr)
return;
else
gotIPv6Addr = true;
}
}
boolean changed = changeAddress(ip, port);
@ -585,6 +590,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
* @param ourPort >= 1024
*/
void externalAddressReceived(Hash from, byte ourIP[], int ourPort) {
if (ourIP.length != 4)
return;
boolean isValid = isValid(ourIP) &&
((ourPort >= MIN_EXTERNAL_PORT && ourPort <= MAX_EXTERNAL_PORT) ||
ourPort == _externalListenPort || _externalListenPort <= 0);
@ -616,27 +623,36 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
// leaving us thinking the second IP is still good.
if (_log.shouldLog(Log.INFO))
_log.info("Ignoring IP address suggestion, since we have received an inbound con recently");
} else if (from.equals(_lastFrom) || !eq(_lastOurIP, _lastOurPort, ourIP, ourPort)) {
_lastFrom = from;
_lastOurIP = ourIP;
_lastOurPort = ourPort;
if (_log.shouldLog(Log.INFO))
_log.info("The router " + from + " told us we have a new IP - "
+ Addresses.toString(ourIP, ourPort) + ". Wait until somebody else tells us the same thing.");
} else {
if (_log.shouldLog(Log.INFO))
_log.info(from + " and " + _lastFrom + " agree we have a new IP - "
+ Addresses.toString(ourIP, ourPort) + ". Changing address.");
_lastFrom = from;
_lastOurIP = ourIP;
_lastOurPort = ourPort;
changeAddress(ourIP, ourPort);
// New IP
boolean changeIt = false;
synchronized(this) {
if (from.equals(_lastFrom) || !eq(_lastOurIP, _lastOurPort, ourIP, ourPort)) {
_lastFrom = from;
_lastOurIP = ourIP;
_lastOurPort = ourPort;
if (_log.shouldLog(Log.INFO))
_log.info("The router " + from + " told us we have a new IP - "
+ Addresses.toString(ourIP, ourPort) + ". Wait until somebody else tells us the same thing.");
} else {
_lastFrom = from;
_lastOurIP = ourIP;
_lastOurPort = ourPort;
changeIt = true;
}
}
if (changeIt) {
if (_log.shouldLog(Log.INFO))
_log.info(from + " and " + _lastFrom + " agree we have a new IP - "
+ Addresses.toString(ourIP, ourPort) + ". Changing address.");
changeAddress(ourIP, ourPort);
}
}
}
/**
* @param ourIP MUST have been previously validated with isValid()
* IPv4 or IPv6 OK
* @param ourPort >= 1024 or 0 for no change
*/
private boolean changeAddress(byte ourIP[], int ourPort) {
@ -2702,24 +2718,50 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
return _reachabilityStatus;
}
@Override
public void recheckReachability() {
_testEvent.runTest();
}
PeerState pickTestPeer(RemoteHostId dontInclude) {
/**
* Pick a Bob (if we are Alice) or a Charlie (if we are Bob).
*
* For Bob (as called from PeerTestEvent below), returns an established IPv4 peer.
* While the protocol allows Alice to select an unestablished Bob, we don't support that.
*
* For Charlie (as called from PeerTestManager), returns an established IPv4 or IPv6 peer.
* (doesn't matter how Bob and Charlie communicate)
*
* Any returned peer must advertise an IPv4 address to prove it is IPv4-capable.
*
* @param peerRole BOB or CHARLIE only
* @param dontInclude may be null
* @return IPv4 peer or null
*/
PeerState pickTestPeer(PeerTestState.Role peerRole, RemoteHostId dontInclude) {
if (peerRole == ALICE)
throw new IllegalArgumentException();
List<PeerState> peers = new ArrayList(_peersByIdent.values());
for (Iterator<PeerState> iter = new RandomIterator(peers); iter.hasNext(); ) {
PeerState peer = iter.next();
if ( (dontInclude != null) && (dontInclude.equals(peer.getRemoteHostId())) )
continue;
// enforce IPv4 connection for BOB
byte[] ip = peer.getRemoteIP();
if (peerRole == BOB && ip.length != 4)
continue;
// enforce IPv4 advertised for all
RouterInfo peerInfo = _context.netDb().lookupRouterInfoLocally(peer.getRemotePeer());
if (peerInfo == null)
continue;
RouterAddress addr = peerInfo.getTargetAddress(STYLE);
if (addr == null)
continue;
byte[] ip = addr.getIP();
ip = null;
List<RouterAddress> addrs = peerInfo.getTargetAddresses(STYLE);
for (RouterAddress addr : addrs) {
ip = addr.getIP();
if (ip != null && ip.length == 4)
break;
}
if (ip == null)
continue;
if (DataHelper.eq(ip, 0, getExternalIP(), 0, 2))
@ -2735,6 +2777,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
//return ( (val != null) && ("true".equals(val)) );
}
/**
* Initiate a test (we are Alice)
*/
private class PeerTestEvent extends SimpleTimer2.TimedEvent {
private volatile boolean _alive;
/** when did we last test our reachability */
@ -2761,7 +2806,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
}
private void runTest() {
PeerState bob = pickTestPeer(null);
PeerState bob = pickTestPeer(BOB, null);
if (bob != null) {
if (_log.shouldLog(Log.INFO))
_log.info("Running periodic test with bob = " + bob);