propagate from branch 'i2p.i2p' (head 2a2c708bf9fee43e69469bdf896dfe489c32bdea)

to branch 'i2p.i2p.zzz.ipv6' (head c33552d7026b0a445d1dd7e138bf454144130eb2)
This commit is contained in:
zzz
2013-06-28 16:10:49 +00:00
67 changed files with 3038 additions and 1193 deletions
+104 -19
View File
@@ -11,10 +11,12 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -31,6 +33,7 @@ import net.i2p.data.RouterInfo;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.util.Addresses;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.LHMCache;
import net.i2p.util.Log;
import net.i2p.util.Translate;
@@ -62,6 +65,8 @@ import net.i2p.util.Translate;
* banlist it forever, then go back to the file to get the original
* entry so we can add the reason to the banlist text.
*
* On-disk blocklist supports IPv4 only.
* In-memory supports both IPv4 and IPv6.
*/
public class Blocklist {
private final Log _log;
@@ -72,8 +77,20 @@ public class Blocklist {
private Entry _wrapSave;
private final Set<Hash> _inProcess = new HashSet(4);
private Map<Hash, String> _peerBlocklist = new HashMap(4);
/**
* Limits of transient (in-memory) blocklists.
* Note that it's impossible to prevent clogging up
* the tables by a determined attacker, esp. on IPv6
*/
private static final int MAX_IPV4_SINGLES = 256;
private static final int MAX_IPV6_SINGLES = 512;
private final Set<Integer> _singleIPBlocklist = new ConcurrentHashSet(4);
private final Map<BigInteger, Object> _singleIPv6Blocklist = new LHMCache(MAX_IPV6_SINGLES);
private static final Object DUMMY = Integer.valueOf(0);
public Blocklist(RouterContext context) {
_context = context;
_log = context.logManager().getLog(Blocklist.class);
@@ -437,6 +454,8 @@ public class Blocklist {
* Maintain a simple in-memory single-IP blocklist
* This is used for new additions, NOT for the main list
* of IP ranges read in from the file.
*
* @param ip IPv4 or IPv6
*/
public void add(String ip) {
byte[] pib = Addresses.getIP(ip);
@@ -448,16 +467,24 @@ public class Blocklist {
* Maintain a simple in-memory single-IP blocklist
* This is used for new additions, NOT for the main list
* of IP ranges read in from the file.
*
* @param ip IPv4 or IPv6
*/
public void add(byte ip[]) {
if (ip.length != 4)
return;
if (add(toInt(ip)))
if (_log.shouldLog(Log.WARN))
_log.warn("Adding IP to blocklist: " + Addresses.toString(ip));
boolean rv;
if (ip.length == 4)
rv = add(toInt(ip));
else if (ip.length == 16)
rv = add(new BigInteger(1, ip));
else
rv = false;
if (rv && _log.shouldLog(Log.WARN))
_log.warn("Adding IP to blocklist: " + Addresses.toString(ip));
}
private boolean add(int ip) {
if (_singleIPBlocklist.size() >= MAX_IPV4_SINGLES)
return false;
return _singleIPBlocklist.add(Integer.valueOf(ip));
}
@@ -466,20 +493,41 @@ public class Blocklist {
}
/**
* this tries to not return duplicates
* but I suppose it could.
* @param ip IPv6 non-negative
* @since IPv6
*/
private boolean add(BigInteger ip) {
synchronized(_singleIPv6Blocklist) {
return _singleIPv6Blocklist.put(ip, DUMMY) == null;
}
}
/**
* @param ip IPv6 non-negative
* @since IPv6
*/
private boolean isOnSingleList(BigInteger ip) {
synchronized(_singleIPv6Blocklist) {
return _singleIPv6Blocklist.get(ip) != null;
}
}
/**
* Will not contain duplicates.
*/
private List<byte[]> getAddresses(Hash peer) {
List<byte[]> rv = new ArrayList(1);
RouterInfo pinfo = _context.netDb().lookupRouterInfoLocally(peer);
if (pinfo == null) return rv;
byte[] oldpib = null;
if (pinfo == null)
return Collections.EMPTY_LIST;
List<byte[]> rv = new ArrayList(4);
// for each peer address
for (RouterAddress pa : pinfo.getAddresses()) {
byte[] pib = pa.getIP();
if (pib == null) continue;
if (DataHelper.eq(oldpib, pib)) continue;
oldpib = pib;
// O(n**2)
for (int i = 0; i < rv.size(); i++) {
if (DataHelper.eq(rv.get(i), pib)) continue;
}
rv.add(pib);
}
return rv;
@@ -491,8 +539,9 @@ public class Blocklist {
*/
public boolean isBlocklisted(Hash peer) {
List<byte[]> ips = getAddresses(peer);
for (Iterator<byte[]> iter = ips.iterator(); iter.hasNext(); ) {
byte ip[] = iter.next();
if (ips.isEmpty())
return false;
for (byte[] ip : ips) {
if (isBlocklisted(ip)) {
if (! _context.banlist().isBanlisted(peer))
// nice knowing you...
@@ -505,6 +554,8 @@ public class Blocklist {
/**
* calling this externally won't banlist the peer, this is just an IP check
*
* @param ip IPv4 or IPv6
*/
public boolean isBlocklisted(String ip) {
byte[] pib = Addresses.getIP(ip);
@@ -514,11 +565,15 @@ public class Blocklist {
/**
* calling this externally won't banlist the peer, this is just an IP check
*
* @param ip IPv4 or IPv6
*/
public boolean isBlocklisted(byte ip[]) {
if (ip.length != 4)
return false;
return isBlocklisted(toInt(ip));
if (ip.length == 4)
return isBlocklisted(toInt(ip));
if (ip.length == 16)
return isOnSingleList(new BigInteger(1, ip));
return false;
}
/**
@@ -760,7 +815,7 @@ public class Blocklist {
//out.write("<h2>Banned IPs</h2>");
Set<Integer> singles = new TreeSet();
singles.addAll(_singleIPBlocklist);
if (!singles.isEmpty()) {
if (!(singles.isEmpty() && _singleIPv6Blocklist.isEmpty())) {
out.write("<table><tr><th align=\"center\" colspan=\"2\"><b>");
out.write(_("IPs Banned Until Restart"));
out.write("</b></td></tr>");
@@ -782,6 +837,19 @@ public class Blocklist {
out.write(toStr(ip));
out.write("</td><td width=\"50%\">&nbsp;</td></tr>\n");
}
// then IPv6
if (!_singleIPv6Blocklist.isEmpty()) {
List<BigInteger> s6;
synchronized(_singleIPv6Blocklist) {
s6 = new ArrayList(_singleIPv6Blocklist.keySet());
}
Collections.sort(s6);
for (BigInteger bi : s6) {
out.write("<tr><td align=\"center\" width=\"50%\">");
out.write(Addresses.toString(toIPBytes(bi)));
out.write("</td><td width=\"50%\">&nbsp;</td></tr>\n");
}
}
out.write("</table>");
}
if (_blocklistSize > 0) {
@@ -832,6 +900,23 @@ public class Blocklist {
out.flush();
}
/**
* Convert a (non-negative) two's complement IP to exactly 16 bytes
* @since IPv6
*/
private static byte[] toIPBytes(BigInteger bi) {
byte[] ba = bi.toByteArray();
int len = ba.length;
if (len == 16)
return ba;
byte[] rv = new byte[16];
if (len < 16)
System.arraycopy(ba, 0, rv, 16 - len, len);
else
System.arraycopy(ba, len - 16, rv, 0, 16);
return rv;
}
/**
* Mark a string for extraction by xgettext and translation.
* Use this only in static initializers.
@@ -28,8 +28,8 @@ public abstract class CommSystemFacade implements Service {
public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException { }
public void renderStatusHTML(Writer out) throws IOException { renderStatusHTML(out, null, 0); }
/** Create the set of RouterAddress structures based on the router's config */
public Set<RouterAddress> createAddresses() { return Collections.EMPTY_SET; }
/** Create the list of RouterAddress structures based on the router's config */
public List<RouterAddress> createAddresses() { return Collections.EMPTY_LIST; }
public int countActivePeers() { return 0; }
public int countActiveSendPeers() { return 0; }
@@ -91,8 +91,13 @@ public class MessageHistory {
*/
public synchronized void initialize(boolean forceReinitialize) {
if (!forceReinitialize) return;
Router router = _context.router();
if (router == null) {
// unit testing, presumably
return;
}
if (_context.router().getRouterInfo() == null) {
if (router.getRouterInfo() == null) {
_reinitializeJob.getTiming().setStartAfter(_context.clock().now() + 15*1000);
_context.jobQueue().addJob(_reinitializeJob);
} else {
@@ -77,4 +77,13 @@ public abstract class NetworkDatabaseFacade implements Service {
/** @since 0.9 */
public ReseedChecker reseedChecker() { return null; };
/**
* For convenience, so users don't have to cast to FNDF, and unit tests using
* Dummy NDF will work.
*
* @return false; FNDF overrides to return actual setting
* @since IPv6
*/
public boolean floodfillEnabled() { return false; };
}
+1 -1
View File
@@ -601,7 +601,7 @@ public class Router implements RouterClock.ClockShiftListener {
}
// if prop set to true, don't tell people we are ff even if we are
if (FloodfillNetworkDatabaseFacade.floodfillEnabled(_context) &&
if (_context.netDb().floodfillEnabled() &&
!_context.getBooleanProperty("router.hideFloodfillParticipant"))
ri.addCapability(FloodfillNetworkDatabaseFacade.CAPABILITY_FLOODFILL);
@@ -38,7 +38,8 @@ import net.i2p.util.I2PProperties.I2PPropertyCallback;
*/
public class RouterContext extends I2PAppContext {
private final Router _router;
private ClientManagerFacadeImpl _clientManagerFacade;
private ClientManagerFacade _clientManagerFacade;
private InternalClientManager _internalClientManager;
private ClientMessagePool _clientMessagePool;
private JobQueue _jobQueue;
private InNetMessagePool _inNetMessagePool;
@@ -152,15 +153,29 @@ public class RouterContext extends I2PAppContext {
}
/**
* The following properties may be used to replace various parts
* of the context with dummy implementations for testing, by setting
* the property to "true":
*<pre>
* i2p.dummyClientFacade
* i2p.dummyNetDb
* i2p.dummyPeerManager
* i2p.dummyTunnelManager
* i2p.vmCommSystem (transport)
*</pre>
*/
public synchronized void initAll() {
if (_initialized)
throw new IllegalStateException();
if (getBooleanProperty("i2p.dummyClientFacade"))
System.err.println("i2p.dummyClientFacade currently unsupported");
_clientManagerFacade = new ClientManagerFacadeImpl(this);
// removed since it doesn't implement InternalClientManager for now
//else
// _clientManagerFacade = new DummyClientManagerFacade(this);
if (!getBooleanProperty("i2p.dummyClientFacade")) {
ClientManagerFacadeImpl cmfi = new ClientManagerFacadeImpl(this);
_clientManagerFacade = cmfi;
_internalClientManager = cmfi;
} else {
_clientManagerFacade = new DummyClientManagerFacade(this);
// internal client manager is null
}
_clientMessagePool = new ClientMessagePool(this);
_jobQueue = new JobQueue(this);
_inNetMessagePool = new InNetMessagePool(this);
@@ -168,23 +183,23 @@ public class RouterContext extends I2PAppContext {
_messageHistory = new MessageHistory(this);
_messageRegistry = new OutboundMessageRegistry(this);
//_messageStateMonitor = new MessageStateMonitor(this);
if ("false".equals(getProperty("i2p.dummyNetDb", "false")))
if (!getBooleanProperty("i2p.dummyNetDb"))
_netDb = new FloodfillNetworkDatabaseFacade(this); // new KademliaNetworkDatabaseFacade(this);
else
_netDb = new DummyNetworkDatabaseFacade(this);
_keyManager = new KeyManager(this);
if ("false".equals(getProperty("i2p.vmCommSystem", "false")))
if (!getBooleanProperty("i2p.vmCommSystem"))
_commSystem = new CommSystemFacadeImpl(this);
else
_commSystem = new VMCommSystem(this);
_profileOrganizer = new ProfileOrganizer(this);
if ("false".equals(getProperty("i2p.dummyPeerManager", "false")))
if (!getBooleanProperty("i2p.dummyPeerManager"))
_peerManagerFacade = new PeerManagerFacadeImpl(this);
else
_peerManagerFacade = new DummyPeerManagerFacade();
_profileManager = new ProfileManagerImpl(this);
_bandwidthLimiter = new FIFOBandwidthLimiter(this);
if ("false".equals(getProperty("i2p.dummyTunnelManager", "false")))
if (!getBooleanProperty("i2p.dummyTunnelManager"))
_tunnelManager = new TunnelPoolManager(this);
else
_tunnelManager = new DummyTunnelManagerFacade();
@@ -529,7 +544,7 @@ public class RouterContext extends I2PAppContext {
*/
@Override
public InternalClientManager internalClientManager() {
return _clientManagerFacade;
return _internalClientManager;
}
/**
@@ -21,7 +21,7 @@ public class RouterVersion {
public final static long BUILD = 13;
/** for example "-test" */
public final static String EXTRA = "";
public final static String EXTRA = "-ipv6";
public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA;
public static void main(String args[]) {
System.out.println("I2P Router version: " + FULL_VERSION);
@@ -38,7 +38,7 @@ class ExploreKeySelectorJob extends JobImpl {
public String getName() { return "Explore Key Selector Job"; }
public void runJob() {
if (((FloodfillNetworkDatabaseFacade)_facade).floodfillEnabled()) {
if (_facade.floodfillEnabled()) {
requeue(30*RERUN_DELAY_MS);
return;
}
@@ -261,10 +261,8 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
}
}
@Override
public boolean floodfillEnabled() { return _floodfillEnabled; }
public static boolean floodfillEnabled(RouterContext ctx) {
return ((FloodfillNetworkDatabaseFacade)ctx.netDb()).floodfillEnabled();
}
/**
* @param peer may be null, returns false if null
@@ -303,7 +303,7 @@ class FloodfillPeerSelector extends PeerSelector {
* @since 0.9.5 modified from ProfileOrganizer
*/
private Set<Integer> maskedIPSet(Hash peer, RouterInfo pinfo, int mask) {
Set<Integer> rv = new HashSet(2);
Set<Integer> rv = new HashSet(4);
byte[] commIP = _context.commSystem().getIP(peer);
if (commIP != null)
rv.add(maskedIP(commIP, mask));
@@ -322,12 +322,22 @@ class FloodfillPeerSelector extends PeerSelector {
/**
* generate an arbitrary unique value for this ip/mask (mask = 1-4)
* If IPv6, force mask = 8.
* @since 0.9.5 copied from ProfileOrganizer
*/
private static Integer maskedIP(byte[] ip, int mask) {
int rv = 0;
for (int i = 0; i < mask; i++)
rv = (rv << 8) | (ip[i] & 0xff);
int rv = ip[0];
if (ip.length == 16) {
for (int i = 1; i < 8; i++) {
rv <<= i * 4;
rv ^= ip[i];
}
} else {
for (int i = 1; i < mask; i++) {
rv <<= 8;
rv ^= ip[i];
}
}
return Integer.valueOf(rv);
}
@@ -38,7 +38,7 @@ public class HandleFloodfillDatabaseLookupMessageJob extends HandleDatabaseLooku
*/
@Override
protected boolean answerAllQueries() {
if (!FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext())) return false;
if (!getContext().netDb().floodfillEnabled()) return false;
return FloodfillNetworkDatabaseFacade.isFloodfill(getContext().router().getRouterInfo());
}
@@ -52,7 +52,7 @@ public class HandleFloodfillDatabaseLookupMessageJob extends HandleDatabaseLooku
super.sendClosest(key, routerInfoSet, toPeer, replyTunnel);
// go away, you got the wrong guy, send our RI back unsolicited
if (!FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext())) {
if (!getContext().netDb().floodfillEnabled()) {
// We could just call sendData(myhash, myri, toPeer, replyTunnel) but
// that would increment the netDb.lookupsHandled and netDb.lookupsMatched stats
DatabaseStoreMessage msg = new DatabaseStoreMessage(getContext());
@@ -187,7 +187,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
// flood it
if (invalidMessage == null &&
FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext()) &&
getContext().netDb().floodfillEnabled() &&
_message.getReplyToken() > 0) {
if (wasNew) {
// DOS prevention
@@ -763,7 +763,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
// As the net grows this won't be sufficient, and we'll have to implement
// flushing some from memory, while keeping all on disk.
long adjustedExpiration;
if (FloodfillNetworkDatabaseFacade.floodfillEnabled(_context))
if (floodfillEnabled())
adjustedExpiration = ROUTER_INFO_EXPIRATION_FLOODFILL;
else
// _kb.size() includes leasesets but that's ok
@@ -134,7 +134,7 @@ class SearchJob extends JobImpl {
// The other two places this was called (one below and one in FNDF)
// have been commented out.
// Returning false essentially enables kademlia as a backup to floodfill for search responses.
if (FloodfillNetworkDatabaseFacade.floodfillEnabled(ctx))
if (ctx.netDb().floodfillEnabled())
return false;
return ctx.getProperty("netDb.floodfillOnly", DEFAULT_FLOODFILL_ONLY);
}
@@ -51,7 +51,7 @@ class StartExplorersJob extends JobImpl {
public String getName() { return "Start Explorers Job"; }
public void runJob() {
if (! (((FloodfillNetworkDatabaseFacade)_facade).floodfillEnabled() ||
if (! (_facade.floodfillEnabled() ||
getContext().router().gracefulShutdownInProgress())) {
int num = MAX_PER_RUN;
if (_facade.getDataStore().size() < LOW_ROUTERS)
@@ -93,7 +93,7 @@ class StartExplorersJob extends JobImpl {
*/
private long getNextRunDelay() {
// we don't explore if floodfill
if (((FloodfillNetworkDatabaseFacade)_facade).floodfillEnabled())
if (_facade.floodfillEnabled())
return MAX_RERUN_DELAY_MS;
// If we don't know too many peers, or just started, explore aggressively
@@ -1264,7 +1264,7 @@ public class ProfileOrganizer {
* @return an opaque set of masked IPs for this peer
*/
private Set<Integer> maskedIPSet(Hash peer, int mask) {
Set<Integer> rv = new HashSet(2);
Set<Integer> rv = new HashSet(4);
byte[] commIP = _context.commSystem().getIP(peer);
if (commIP != null)
rv.add(maskedIP(commIP, mask));
@@ -1282,11 +1282,23 @@ public class ProfileOrganizer {
return rv;
}
/** generate an arbitrary unique value for this ip/mask (mask = 1-4) */
/**
* generate an arbitrary unique value for this ip/mask (mask = 1-4)
* If IPv6, force mask = 8.
*/
private static Integer maskedIP(byte[] ip, int mask) {
int rv = 0;
for (int i = 0; i < mask; i++)
rv = (rv << 8) | (ip[i] & 0xff);
int rv = ip[0];
if (ip.length == 16) {
for (int i = 1; i < 8; i++) {
rv <<= i * 4;
rv ^= ip[i];
}
} else {
for (int i = 1; i < mask; i++) {
rv <<= 8;
rv ^= ip[i];
}
}
return Integer.valueOf(rv);
}
@@ -10,15 +10,14 @@ package net.i2p.router.transport;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import net.i2p.data.Hash;
@@ -27,9 +26,6 @@ import net.i2p.data.RouterInfo;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.ntcp.NTCPAddress;
import net.i2p.router.transport.ntcp.NTCPTransport;
import net.i2p.router.transport.udp.UDPAddress;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
@@ -45,6 +41,12 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
private final GeoIP _geoIP;
private volatile boolean _netMonitorStatus;
private boolean _wasStarted;
/**
* Disable connections for testing
* @since IPv6
*/
private static final String PROP_DISABLED = "i2np.disable";
public CommSystemFacadeImpl(RouterContext context) {
_context = context;
@@ -125,23 +127,17 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
return sum * 1000 / frameSize;
}
public List<TransportBid> getBids(OutNetMessage msg) {
return _manager.getBids(msg);
}
public TransportBid getBid(OutNetMessage msg) {
return _manager.getBid(msg);
}
public TransportBid getNextBid(OutNetMessage msg) {
return _manager.getNextBid(msg);
}
int getTransportCount() { return _manager.getTransportCount(); }
/** Send the message out */
public void processMessage(OutNetMessage msg) {
if (isDummy()) {
// testing
GetBidsJob.fail(_context, msg);
return;
}
//GetBidsJob j = new GetBidsJob(_context, this, msg);
//j.runJob();
//long before = _context.clock().now();
GetBidsJob.getBids(_context, this, msg);
GetBidsJob.getBids(_context, _manager, msg);
// < 0.4 ms
//_context.statManager().addRateData("transport.getBidsJobTime", _context.clock().now() - before);
}
@@ -167,7 +163,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
}
@Override
public List getMostRecentErrorMessages() {
public List<String> getMostRecentErrorMessages() {
return _manager.getMostRecentErrorMessages();
}
@@ -190,240 +186,33 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
/** @return non-null, possibly empty */
@Override
public Set<RouterAddress> createAddresses() {
public List<RouterAddress> createAddresses() {
// No, don't do this, it makes it almost impossible to build inbound tunnels
//if (_context.router().isHidden())
// return Collections.EMPTY_SET;
Map<String, RouterAddress> addresses = _manager.getAddresses();
boolean newCreated = false;
if (!addresses.containsKey(NTCPTransport.STYLE)) {
RouterAddress addr = createNTCPAddress(_context);
if (_log.shouldLog(Log.INFO))
_log.info("NTCP address: " + addr);
if (addr != null) {
addresses.put(NTCPTransport.STYLE, addr);
newCreated = true;
}
}
List<RouterAddress> addresses = new ArrayList(_manager.getAddresses());
if (_log.shouldLog(Log.INFO))
_log.info("Creating addresses: " + addresses + " isNew? " + newCreated, new Exception("creator"));
return new HashSet(addresses.values());
_log.info("Creating addresses: " + addresses, new Exception("creator"));
return addresses;
}
public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport";
public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoip";
/**
* This only creates an address if the hostname AND port are set in router.config,
* which should be rare.
* Otherwise, notifyReplaceAddress() below takes care of it.
* Note this is called both from above and from NTCPTransport.startListening()
*
* This should really be moved to ntcp/NTCPTransport.java, why is it here?
*/
public static RouterAddress createNTCPAddress(RouterContext ctx) {
if (!TransportManager.isNTCPEnabled(ctx)) return null;
String name = ctx.router().getConfigSetting(PROP_I2NP_NTCP_HOSTNAME);
String port = ctx.router().getConfigSetting(PROP_I2NP_NTCP_PORT);
/*
boolean isNew = false;
if (name == null) {
name = "localhost";
isNew = true;
}
if (port == null) {
port = String.valueOf(ctx.random().nextInt(10240)+1024);
isNew = true;
}
*/
if ( (name == null) || (port == null) || (name.trim().length() <= 0) || ("null".equals(name)) )
return null;
try {
int p = Integer.parseInt(port);
if ( (p <= 0) || (p > 64*1024) )
return null;
} catch (NumberFormatException nfe) {
return null;
}
Properties props = new Properties();
props.setProperty(NTCPAddress.PROP_HOST, name);
props.setProperty(NTCPAddress.PROP_PORT, port);
RouterAddress addr = new RouterAddress();
addr.setCost(NTCPAddress.DEFAULT_COST);
//addr.setExpiration(null);
addr.setOptions(props);
addr.setTransportStyle(NTCPTransport.STYLE);
//if (isNew) {
// why save the same thing?
Map<String, String> changes = new HashMap();
changes.put(PROP_I2NP_NTCP_HOSTNAME, name);
changes.put(PROP_I2NP_NTCP_PORT, port);
ctx.router().saveConfig(changes, null);
//}
return addr;
}
/**
* UDP changed addresses, tell NTCP and restart
* This should really be moved to ntcp/NTCPTransport.java, why is it here?
*
* All the work moved to NTCPTransport.externalAddressReceived()
*
* @param udpAddr may be null; or udpAddr's host/IP may be null
*/
@Override
public synchronized void notifyReplaceAddress(RouterAddress udpAddr) {
if (udpAddr == null)
return;
NTCPTransport t = (NTCPTransport) _manager.getTransport(NTCPTransport.STYLE);
if (t == null)
return;
RouterAddress oldAddr = t.getCurrentAddress();
if (_log.shouldLog(Log.INFO))
_log.info("Changing NTCP Address? was " + oldAddr);
RouterAddress newAddr = new RouterAddress();
newAddr.setTransportStyle(NTCPTransport.STYLE);
Properties newProps = new Properties();
if (oldAddr == null) {
newAddr.setCost(NTCPAddress.DEFAULT_COST);
} else {
newAddr.setCost(oldAddr.getCost());
newProps.putAll(oldAddr.getOptionsMap());
}
boolean changed = false;
// Auto Port Setting
// old behavior (<= 0.7.3): auto-port defaults to false, and true trumps explicit setting
// new behavior (>= 0.7.4): auto-port defaults to true, but explicit setting trumps auto
// TODO rewrite this to operate on ints instead of strings
String oport = newProps.getProperty(NTCPAddress.PROP_PORT);
String nport = null;
String cport = _context.getProperty(PROP_I2NP_NTCP_PORT);
if (cport != null && cport.length() > 0) {
nport = cport;
} else if (_context.getBooleanPropertyDefaultTrue(PROP_I2NP_NTCP_AUTO_PORT)) {
// 0.9.6 change
// This wasn't quite right, as udpAddr is the EXTERNAL port and we really
// want NTCP to bind to the INTERNAL port the first time,
// because if they are different, the NAT is changing them, and
// it probably isn't mapping UDP and TCP the same.
public void notifyReplaceAddress(RouterAddress udpAddr) {
byte[] ip = udpAddr != null ? udpAddr.getIP() : null;
int port = udpAddr != null ? udpAddr.getPort() : 0;
if (port < 0) {
Transport udp = _manager.getTransport(UDPTransport.STYLE);
if (udp != null) {
int udpIntPort = udp.getRequestedPort();
if (udpIntPort > 0)
// should always be true
nport = Integer.toString(udpIntPort);
}
if (nport == null)
// fallback
nport = udpAddr.getOption(UDPAddress.PROP_PORT);
if (udp != null)
port = udp.getRequestedPort();
}
if (_log.shouldLog(Log.INFO))
_log.info("old: " + oport + " config: " + cport + " new: " + nport);
if (nport == null || nport.length() <= 0)
return;
// 0.9.6 change
// Don't have NTCP "chase" SSU's external port,
// as it may change, possibly frequently.
//if (oport == null || ! oport.equals(nport)) {
if (oport == null) {
newProps.setProperty(NTCPAddress.PROP_PORT, nport);
changed = true;
}
// Auto IP Setting
// old behavior (<= 0.7.3): auto-ip defaults to false, and trumps configured hostname,
// and ignores reachability status - leading to
// "firewalled with inbound TCP enabled" warnings.
// new behavior (>= 0.7.4): auto-ip defaults to true, and explicit setting trumps auto,
// and only takes effect if reachability is OK.
// And new "always" setting ignores reachability status, like
// "true" was in 0.7.3
String ohost = newProps.getProperty(NTCPAddress.PROP_HOST);
String enabled = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "true").toLowerCase(Locale.US);
String name = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME);
// hostname config trumps auto config
if (name != null && name.length() > 0)
enabled = "false";
Transport udp = _manager.getTransport(UDPTransport.STYLE);
short status = STATUS_UNKNOWN;
if (udp != null)
status = udp.getReachabilityStatus();
if (_log.shouldLog(Log.INFO))
_log.info("old: " + ohost + " config: " + name + " auto: " + enabled + " status: " + status);
if (enabled.equals("always") ||
(Boolean.parseBoolean(enabled) && status == STATUS_OK)) {
String nhost = udpAddr.getOption(UDPAddress.PROP_HOST);
if (_log.shouldLog(Log.INFO))
_log.info("old: " + ohost + " config: " + name + " new: " + nhost);
if (nhost == null || nhost.length() <= 0)
return;
if (ohost == null || ! ohost.equalsIgnoreCase(nhost)) {
newProps.setProperty(NTCPAddress.PROP_HOST, nhost);
changed = true;
}
} else if (enabled.equals("false") &&
name != null && name.length() > 0 &&
!name.equals(ohost) &&
nport != null) {
// Host name is configured, and we have a port (either auto or configured)
// but we probably only get here if the port is auto,
// otherwise createNTCPAddress() would have done it already
if (_log.shouldLog(Log.INFO))
_log.info("old: " + ohost + " config: " + name + " new: " + name);
newProps.setProperty(NTCPAddress.PROP_HOST, name);
changed = true;
} else if (ohost == null || ohost.length() <= 0) {
return;
} else if (Boolean.parseBoolean(enabled) && status != STATUS_OK) {
// UDP transitioned to not-OK, turn off NTCP address
// This will commonly happen at startup if we were initially OK
// because UPnP was successful, but a subsequent SSU Peer Test determines
// we are still firewalled (SW firewall, bad UPnP indication, etc.)
if (_log.shouldLog(Log.INFO))
_log.info("old: " + ohost + " config: " + name + " new: null");
newAddr = null;
changed = true;
}
if (!changed) {
if (oldAddr != null) {
int oldCost = oldAddr.getCost();
int newCost = NTCPAddress.DEFAULT_COST;
if (TransportImpl.ADJUST_COST && !t.haveCapacity())
newCost++;
if (newCost != oldCost) {
oldAddr.setCost(newCost);
if (_log.shouldLog(Log.WARN))
_log.warn("Changing NTCP cost from " + oldCost + " to " + newCost);
} else {
_log.info("No change to NTCP Address");
}
} else {
_log.info("No change to NTCP Address");
}
return;
}
// stopListening stops the pumper, readers, and writers, so required even if
// oldAddr == null since startListening starts them all again
//
// really need to fix this so that we can change or create an inbound address
// without tearing down everything
// Especially on disabling the address, we shouldn't tear everything down.
//
_log.warn("Halting NTCP to change address");
t.stopListening();
if (newAddr != null)
newAddr.setOptions(newProps);
// Wait for NTCP Pumper to stop so we don't end up with two...
while (t.isAlive()) {
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
}
t.restartListening(newAddr);
_log.warn("Changed NTCP Address and started up, address is now " + newAddr);
return;
_manager.externalAddressReceived(Transport.AddressSource.SOURCE_SSU, ip, port);
}
/*
@@ -451,10 +240,10 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
RouterInfo ri = _context.netDb().lookupRouterInfoLocally(iter.next());
if (ri == null)
continue;
String host = getIPString(ri);
if (host == null)
byte[] ip = getIP(ri);
if (ip == null)
continue;
_geoIP.add(host);
_geoIP.add(ip);
}
_context.simpleScheduler().addPeriodicEvent(new Lookup(), 5000, LOOKUP_TIME);
}
@@ -491,31 +280,35 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
/**
* Uses the transport IP first because that lookup is fast,
* then the SSU IP from the netDb.
* then the IP from the netDb.
*
* @return two-letter lower-case country code or null
*/
@Override
public String getCountry(Hash peer) {
byte[] ip = TransportImpl.getIP(peer);
//if (ip != null && ip.length == 4)
if (ip != null)
return _geoIP.get(ip);
RouterInfo ri = _context.netDb().lookupRouterInfoLocally(peer);
if (ri == null)
return null;
String s = getIPString(ri);
if (s != null)
return _geoIP.get(s);
ip = getIP(ri);
if (ip != null)
return _geoIP.get(ip);
return null;
}
private String getIPString(RouterInfo ri) {
// use SSU only, it is likely to be an IP not a hostname,
// we don't want to generate a lot of DNS queries at startup
RouterAddress ra = ri.getTargetAddress("SSU");
if (ra == null)
return null;
return ra.getOption("host");
private static byte[] getIP(RouterInfo ri) {
// Return first IP (v4 or v6) we find, any transport
// Assume IPv6 doesn't have geoIP for now
for (RouterAddress ra : ri.getAddresses()) {
byte[] rv = ra.getIP();
//if (rv != null && rv.length == 4)
if (rv != null)
return rv;
}
return null;
}
/** full name for a country code, or the code if we don't know the name */
@@ -556,9 +349,14 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
return buf.toString();
}
/** @since 0.8.13 */
/**
* Is everything disabled for testing?
* @since 0.8.13
*/
@Override
public boolean isDummy() { return false; }
public boolean isDummy() {
return _context.getBooleanProperty(PROP_DISABLED);
}
/**
* Translate
@@ -48,8 +48,12 @@ class GeoIP {
private final Map<String, String> _codeToName;
/** code to itself to prevent String proliferation */
private final Map<String, String> _codeCache;
// In the following structures, an IPv4 IP is stored as a non-negative long, 0 to 2**32 - 1,
// and the first 8 bytes of an IPv6 IP are stored as a signed long.
private final Map<Long, String> _IPToCountry;
private final Set<Long> _pendingSearch;
private final Set<Long> _pendingIPv6Search;
private final Set<Long> _notFound;
private final AtomicBoolean _lock;
private int _lookupRunCount;
@@ -58,10 +62,11 @@ class GeoIP {
public GeoIP(RouterContext context) {
_context = context;
_log = context.logManager().getLog(GeoIP.class);
_codeToName = new ConcurrentHashMap(256);
_codeCache = new ConcurrentHashMap(256);
_codeToName = new ConcurrentHashMap(512);
_codeCache = new ConcurrentHashMap(512);
_IPToCountry = new ConcurrentHashMap();
_pendingSearch = new ConcurrentHashSet();
_pendingIPv6Search = new ConcurrentHashSet();
_notFound = new ConcurrentHashSet();
_lock = new AtomicBoolean();
readCountryFile();
@@ -81,6 +86,7 @@ class GeoIP {
_codeCache.clear();
_IPToCountry.clear();
_pendingSearch.clear();
_pendingIPv6Search.clear();
_notFound.clear();
}
@@ -107,6 +113,7 @@ class GeoIP {
public void blockingLookup() {
if (! _context.getBooleanPropertyDefaultTrue(PROP_GEOIP_ENABLED)) {
_pendingSearch.clear();
_pendingIPv6Search.clear();
return;
}
int pri = Thread.currentThread().getPriority();
@@ -132,18 +139,29 @@ class GeoIP {
// clear the negative cache every few runs, to prevent it from getting too big
if (((++_lookupRunCount) % CLEAR) == 0)
_notFound.clear();
// IPv4
Long[] search = _pendingSearch.toArray(new Long[_pendingSearch.size()]);
if (search.length <= 0)
return;
_pendingSearch.clear();
Arrays.sort(search);
String[] countries = readGeoIPFile(search);
for (int i = 0; i < countries.length; i++) {
if (countries[i] != null)
_IPToCountry.put(search[i], countries[i]);
else
_notFound.add(search[i]);
if (search.length > 0) {
String[] countries = readGeoIPFile(search);
for (int i = 0; i < countries.length; i++) {
if (countries[i] != null)
_IPToCountry.put(search[i], countries[i]);
else
_notFound.add(search[i]);
}
}
// IPv6
search = _pendingSearch.toArray(new Long[_pendingIPv6Search.size()]);
_pendingIPv6Search.clear();
if (search.length > 0) {
String[] countries = GeoIPv6.readGeoIPFile(_context, search, _codeCache);
for (int i = 0; i < countries.length; i++) {
if (countries[i] != null)
_IPToCountry.put(search[i], countries[i]);
else
_notFound.add(search[i]);
}
}
} finally {
_lock.set(false);
@@ -169,16 +187,16 @@ class GeoIP {
*
*/
private void readCountryFile() {
File GeoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT);
GeoFile = new File(GeoFile, COUNTRY_FILE_DEFAULT);
if (!GeoFile.exists()) {
File geoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT);
geoFile = new File(geoFile, COUNTRY_FILE_DEFAULT);
if (!geoFile.exists()) {
if (_log.shouldLog(Log.WARN))
_log.warn("Country file not found: " + GeoFile.getAbsolutePath());
_log.warn("Country file not found: " + geoFile.getAbsolutePath());
return;
}
FileInputStream in = null;
try {
in = new FileInputStream(GeoFile);
in = new FileInputStream(geoFile);
BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
String line = null;
while ( (line = br.readLine()) != null) {
@@ -228,11 +246,11 @@ class GeoIP {
*
*/
private String[] readGeoIPFile(Long[] search) {
File GeoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT);
GeoFile = new File(GeoFile, GEOIP_FILE_DEFAULT);
if (!GeoFile.exists()) {
File geoFile = new File(_context.getBaseDir(), GEOIP_DIR_DEFAULT);
geoFile = new File(geoFile, GEOIP_FILE_DEFAULT);
if (!geoFile.exists()) {
if (_log.shouldLog(Log.WARN))
_log.warn("GeoIP file not found: " + GeoFile.getAbsolutePath());
_log.warn("GeoIP file not found: " + geoFile.getAbsolutePath());
return new String[0];
}
String[] rv = new String[search.length];
@@ -240,7 +258,7 @@ class GeoIP {
long start = _context.clock().now();
FileInputStream in = null;
try {
in = new FileInputStream(GeoFile);
in = new FileInputStream(geoFile);
String buf = null;
BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1"));
while ((buf = br.readLine()) != null && idx < search.length) {
@@ -268,7 +286,7 @@ class GeoIP {
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error reading the GeoFile", ioe);
_log.error("Error reading the geoFile", ioe);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
@@ -307,6 +325,7 @@ class GeoIP {
/**
* Add to the list needing lookup
* @param ip IPv4 or IPv6
*/
public void add(String ip) {
byte[] pib = Addresses.getIP(ip);
@@ -314,20 +333,28 @@ class GeoIP {
add(pib);
}
/**
* Add to the list needing lookup
* @param ip IPv4 or IPv6
*/
public void add(byte ip[]) {
if (ip.length != 4)
return;
add(toLong(ip));
}
/** see above for ip-to-long mapping */
private void add(long ip) {
Long li = Long.valueOf(ip);
if (!(_IPToCountry.containsKey(li) || _notFound.contains(li)))
_pendingSearch.add(li);
if (!(_IPToCountry.containsKey(li) || _notFound.contains(li))) {
if (ip >= 0 && ip < (1L << 32))
_pendingSearch.add(li);
else
_pendingIPv6Search.add(li);
}
}
/**
* Get the country for an IP from the cache.
* @param ip IPv4 or IPv6
* @return lower-case code, generally two letters, or null.
*/
public String get(String ip) {
@@ -338,23 +365,30 @@ class GeoIP {
/**
* Get the country for an IP from the cache.
* @param ip IPv4 or IPv6
* @return lower-case code, generally two letters, or null.
*/
public String get(byte ip[]) {
if (ip.length != 4)
return null;
return get(toLong(ip));
}
/** see above for ip-to-long mapping */
private String get(long ip) {
return _IPToCountry.get(Long.valueOf(ip));
}
/** see above for ip-to-long mapping */
private static long toLong(byte ip[]) {
int rv = 0;
for (int i = 0; i < 4; i++)
rv |= (ip[i] & 0xff) << ((3-i)*8);
return rv & 0xffffffffl;
if (ip.length == 16) {
for (int i = 0; i < 8; i++)
rv |= (ip[i] & 0xffL) << ((7-i)*8);
return rv;
} else {
for (int i = 0; i < 4; i++)
rv |= (ip[i] & 0xff) << ((3-i)*8);
return rv & 0xffffffffl;
}
}
/**
@@ -0,0 +1,344 @@
package net.i2p.router.transport;
/*
* free (adj.): unencumbered; not under the control of others
* Use at your own risk.
*/
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* Generate compressed geoipv6.dat.gz file, and
* lookup entries in it.
*
* @since IPv6
*/
class GeoIPv6 {
private static final String GEOIP_DIR_DEFAULT = "geoip";
private static final String GEOIP_FILE_DEFAULT = "geoipv6.dat.gz";
private static final String MAGIC = "I2PGeoIPv6\0\001\0\0\0\0";
private static final String COMMENT = "I2P compressed geoipv6 file. See GeoIPv6.java for format.";
/** includes magic */
private static final int HEADER_LEN = 256;
/**
* Lookup search items in the geoip file.
* See below for format.
*/
public static String[] readGeoIPFile(I2PAppContext context, Long[] search, Map<String, String> codeCache) {
Log log = context.logManager().getLog(GeoIPv6.class);
File geoFile = new File(context.getBaseDir(), GEOIP_DIR_DEFAULT);
geoFile = new File(geoFile, GEOIP_FILE_DEFAULT);
if (!geoFile.exists()) {
if (log.shouldLog(Log.WARN))
log.warn("GeoIP file not found: " + geoFile.getAbsolutePath());
return new String[0];
}
return readGeoIPFile(geoFile, search, codeCache, log);
}
/**
* Lookup search items in the geoip file.
* See below for format.
*/
private static String[] readGeoIPFile(File geoFile, Long[] search, Map<String, String> codeCache, Log log) {
String[] rv = new String[search.length];
int idx = 0;
long start = System.currentTimeMillis();
InputStream in = null;
try {
in = new GZIPInputStream(new BufferedInputStream(new FileInputStream(geoFile)));
byte[] magic = new byte[MAGIC.length()];
DataHelper.read(in, magic);
if (!DataHelper.eq(magic, DataHelper.getASCII(MAGIC)))
throw new IOException("Not a IPv6 geoip data file");
// skip timestamp and comments
in.skip(HEADER_LEN - MAGIC.length());
byte[] buf = new byte[18];
while (DataHelper.read(in, buf) == 18 && idx < search.length) {
long ip1 = readLong(buf, 0);
long ip2 = readLong(buf, 8);
while (idx < search.length && search[idx].longValue() < ip1) {
idx++;
}
while (idx < search.length && search[idx].longValue() >= ip1 && search[idx].longValue() <= ip2) {
// written in lower case
String lc = new String(buf, 16, 2, "ISO-8859-1");
// replace the new string with the identical one from the cache
String cached = codeCache.get(lc);
if (cached == null)
cached = lc;
rv[idx++] = cached;
}
}
} catch (IOException ioe) {
if (log.shouldLog(Log.ERROR))
log.error("Error reading the geoFile", ioe);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
if (log.shouldLog(Log.INFO))
log.info("GeoIPv6 processing finished, time: " + (System.currentTimeMillis() - start));
return rv;
}
/**
* Read in and parse multiple IPv6 geoip CSV files,
* merge them, and write out a gzipped binary IPv6 geoip file.
*
* Acceptable input formats (IPv6 only):
*<pre>
* #comment (# must be in column 1)
* "text IP", "text IP", "bigint IP", "bigint IP", "country code", "country name"
*</pre>
* Quotes and spaces optional. Sorting not required.
* Country code case-insensitive.
* Fields 1, 2, and 5 are used; fields 3, 4, and 6 are ignored.
* This is identical to the format of the MaxMind GeoLite IPv6 file.
*
* Example:
*<pre>
* "2001:200::", "2001:200:ffff:ffff:ffff:ffff:ffff:ffff", "42540528726795050063891204319802818560", "42540528806023212578155541913346768895", "JP", "Japan"
*</pre>
*
*<pre>
* Output format:
* Bytes 0-9: Magic number "I2PGeoIPv6"
* Bytes 10-11: version (0x0001)
* Bytes 12-15 flags (0)
* Bytes 16-23: Date (long)
* Bytes 24-xx: Comment (UTF-8)
* Bytes xx-255: null padding
* Bytes 256-: 18 byte records:
* 8 byte from (/64)
* 8 byte to (/64)
* 2 byte country code LOWER case (ASCII)
* Data must be sorted (SIGNED twos complement), no overlap
*</pre>
*
* SLOW. For preprocessing only!
*
* @return success
*/
private static boolean compressGeoIPv6CSVFiles(List<File> inFiles, File outFile) {
boolean DEBUG = false;
List<V6Entry> entries = new ArrayList(20000);
for (File geoFile : inFiles) {
int count = 0;
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(geoFile));
if (geoFile.getName().endsWith(".gz"))
in = new GZIPInputStream(in);
String buf = null;
BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1"));
while ((buf = br.readLine()) != null) {
try {
if (buf.charAt(0) == '#') {
continue;
}
String[] s = buf.split(",");
String ips1 = s[0].replace("\"", "").trim();
String ips2 = s[1].replace("\"", "").trim();
byte[] ip1 = InetAddress.getByName(ips1).getAddress();
byte[] ip2 = InetAddress.getByName(ips2).getAddress();
String country = s[4].replace("\"", "").trim().toLowerCase(Locale.US);
entries.add(new V6Entry(ip1, ip2, country));
count++;
} catch (UnknownHostException uhe) {
uhe.printStackTrace();
} catch (RuntimeException re) {
re.printStackTrace();
}
}
System.err.println("Read " + count + " entries from " + geoFile);
} catch (IOException ioe) {
ioe.printStackTrace();
//if (_log.shouldLog(Log.ERROR))
// _log.error("Error reading the geoFile", ioe);
return false;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
}
Collections.sort(entries);
// merge
V6Entry old = null;
for (int i = 0; i < entries.size(); i++) {
V6Entry e = entries.get(i);
if (DEBUG)
System.out.println("proc " + e.toString());
if (old != null) {
if (e.from == old.from && e.to == old.to) {
// dup
if (DEBUG)
System.out.println("remove dup " + e);
entries.remove(i);
i--;
continue;
}
if (e.from <= old.to) {
// overlap
// truncate old
if (e.from < old.to) {
V6Entry rewrite = new V6Entry(old.from, e.from - 1, old.cc);
if (DEBUG)
System.out.println("rewrite old to " + rewrite);
entries.set(i - 1, rewrite);
}
if (e.to < old.to) {
// e inside old, add new after e
V6Entry insert = new V6Entry(e.to + 1, old.to, old.cc);
if (DEBUG)
System.out.println("insert " + insert);
int j = i + 1;
while (j < entries.size() && insert.compareTo(entries.get(j)) > 0) {
j++;
}
entries.add(j, insert);
}
}
}
old = e;
}
OutputStream out = null;
try {
out = new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(outFile)));
out.write(DataHelper.getASCII(MAGIC));
writeLong(out, System.currentTimeMillis());
byte[] comment = DataHelper.getUTF8(COMMENT);
out.write(comment);
out.write(new byte[256 - (16 + 8 + comment.length)]);
for (V6Entry e : entries) {
writeLong(out, e.from);
writeLong(out, e.to);
out.write(DataHelper.getASCII(e.cc));
}
System.err.println("Wrote " + entries.size() + " entries to " + outFile);
} catch (IOException ioe) {
ioe.printStackTrace();
//if (_log.shouldLog(Log.ERROR))
// _log.error("Error reading the geoFile", ioe);
return false;
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
return true;
}
/**
* Used to temporarily hold, sort, and merge entries before compressing
*/
private static class V6Entry implements Comparable<V6Entry> {
public final long from, to;
public final String cc;
public V6Entry(byte[] f, byte[] t, String c) {
if (f.length != 16 || t.length != 16 || c.length() != 2)
throw new IllegalArgumentException();
from = toLong(f);
to = toLong(t);
cc = c;
if (to < from)
throw new IllegalArgumentException(toString());
}
public V6Entry(long f, long t, String c) {
from = f;
to = t;
cc = c;
if (t < f)
throw new IllegalArgumentException(toString());
}
/** twos complement */
public int compareTo(V6Entry r) {
if (from < r.from) return -1;
if (r.from < from) return 1;
if (to < r.to) return -1;
if (r.to < to) return 1;
return 0;
}
@Override
public String toString() {
return "0x" + Long.toHexString(from) + " -> 0x" + Long.toHexString(to) + " : " + cc;
}
}
private static long toLong(byte ip[]) {
long rv = 0;
for (int i = 0; i < 8; i++)
rv |= (ip[i] & 0xffL) << ((7-i)*8);
return rv;
}
/** like DataHelper.writeLong(rawStream, 8, value) but allows negative values */
private static void writeLong(OutputStream rawStream, long value) throws IOException {
for (int i = 56; i >= 0; i -= 8) {
byte cur = (byte) (value >> i);
rawStream.write(cur);
}
}
/** like DataHelper.readLong(src, offset, 8) but allows negative values */
private static long readLong(byte[] src, int offset) throws IOException {
long rv = 0;
int limit = offset + 8;
for (int i = offset; i < limit; i++) {
rv <<= 8;
rv |= src[i] & 0xFF;
}
return rv;
}
/**
* Merge and compress CSV files to I2P compressed format
*
* GeoIP infile1.csv[.gz] [infile2.csv[.gz]...] outfile.dat.gz
*
* Used to create the file for distribution, do not comment out
*/
public static void main(String args[]) {
if (args.length < 2) {
System.err.println("Usage: GeoIP infile1.csv [infile2.csv...] outfile.dat.gz");
System.exit(1);
}
List<File> infiles = new ArrayList();
for (int i = 0; i < args.length - 1; i++) {
infiles.add(new File(args[i]));
}
File outfile = new File(args[args.length - 1]);
boolean success = compressGeoIPv6CSVFiles(infiles, outfile);
if (!success) {
System.err.println("Failed");
System.exit(1);
}
// readback for testing
readGeoIPFile(outfile, new Long[] { Long.MAX_VALUE }, Collections.EMPTY_MAP, new Log(GeoIPv6.class));
}
}
@@ -23,22 +23,25 @@ import net.i2p.util.Log;
*/
class GetBidsJob extends JobImpl {
private final Log _log;
private final CommSystemFacadeImpl _facade;
private final TransportManager _tmgr;
private final OutNetMessage _msg;
public GetBidsJob(RouterContext ctx, CommSystemFacadeImpl facade, OutNetMessage msg) {
/**
* @deprecated unused, see static getBids()
*/
public GetBidsJob(RouterContext ctx, TransportManager tmgr, OutNetMessage msg) {
super(ctx);
_log = ctx.logManager().getLog(GetBidsJob.class);
_facade = facade;
_tmgr = tmgr;
_msg = msg;
}
public String getName() { return "Fetch bids for a message to be delivered"; }
public void runJob() {
getBids(getContext(), _facade, _msg);
getBids(getContext(), _tmgr, _msg);
}
static void getBids(RouterContext context, CommSystemFacadeImpl facade, OutNetMessage msg) {
static void getBids(RouterContext context, TransportManager tmgr, OutNetMessage msg) {
Log log = context.logManager().getLog(GetBidsJob.class);
Hash to = msg.getTarget().getIdentity().getHash();
msg.timestamp("bid");
@@ -61,14 +64,14 @@ class GetBidsJob extends JobImpl {
return;
}
TransportBid bid = facade.getNextBid(msg);
TransportBid bid = tmgr.getNextBid(msg);
if (bid == null) {
int failedCount = msg.getFailedTransports().size();
if (failedCount == 0) {
context.statManager().addRateData("transport.bidFailNoTransports", msg.getLifetime());
// This used to be "no common transports" but it is almost always no transports at all
context.banlist().banlistRouter(to, _x("No transports (hidden or starting up?)"));
} else if (failedCount >= facade.getTransportCount()) {
} else if (failedCount >= tmgr.getTransportCount()) {
context.statManager().addRateData("transport.bidFailAllTransports", msg.getLifetime());
// fail after all transports were unsuccessful
context.netDb().fail(to);
@@ -82,7 +85,7 @@ class GetBidsJob extends JobImpl {
}
private static void fail(RouterContext context, OutNetMessage msg) {
static void fail(RouterContext context, OutNetMessage msg) {
if (msg.getOnFailedSendJob() != null) {
context.jobQueue().addJob(msg.getOnFailedSendJob());
}
@@ -32,17 +32,90 @@ public interface Transport {
*
*/
public void send(OutNetMessage msg);
public RouterAddress startListening();
public void startListening();
public void stopListening();
public RouterAddress getCurrentAddress();
public RouterAddress updateAddress();
public static final String SOURCE_UPNP = "upnp";
public static final String SOURCE_INTERFACE = "local";
public static final String SOURCE_CONFIG = "config"; // unused
public void externalAddressReceived(String source, byte[] ip, int port);
public void forwardPortStatus(int port, int externalPort, boolean success, String reason);
/**
* What addresses are we currently listening to?
* Replaces getCurrentAddress()
* @return all addresses, non-null
* @since IPv6
*/
public List<RouterAddress> getCurrentAddresses();
/**
* Do we have any current address?
* @since IPv6
*/
public boolean hasCurrentAddress();
/**
* Ask the transport to update its addresses based on current information and return them
* @return all addresses, non-null
*/
public List<RouterAddress> updateAddress();
/**
* @since IPv6
*/
public enum AddressSource {
SOURCE_UPNP("upnp"),
SOURCE_INTERFACE("local"),
/** unused */
SOURCE_CONFIG("config"),
SOURCE_SSU("ssu");
private final String cfgstr;
AddressSource(String cfgstr) {
this.cfgstr = cfgstr;
}
public String toConfigString() {
return cfgstr;
}
}
/**
* Notify a transport of an external address change.
* This may be from a local interface, UPnP, a config change, etc.
* This should not be called if the ip didn't change
* (from that source's point of view), or is a local address.
* May be called multiple times for IPv4 or IPv6.
* The transport should also do its own checking on whether to accept
* notifications from this source.
*
* This can be called before startListening() to set an initial address,
* or after the transport is running.
*
* @param source defined in Transport.java
* @param ip typ. IPv4 or IPv6 non-local; may be null to indicate IPv4 failure or port info only
* @param port 0 for unknown or unchanged
*/
public void externalAddressReceived(AddressSource source, byte[] ip, int port);
/**
* Notify a transport of the results of trying to forward a port.
*
* @param ip may be null
* @param port the internal port
* @param externalPort the external port, which for now should always be the same as
* the internal port if the forwarding was successful.
*/
public void forwardPortStatus(byte[] ip, int port, int externalPort, boolean success, String reason);
/**
* What INTERNAL port would the transport like to have forwarded by UPnP.
* This can't be passed via getCurrentAddress(), as we have to open the port
* before we can publish the address, and that's the external port anyway.
*
* @return port or -1 for none or 0 for any
*/
public int getRequestedPort();
/** Who to notify on message availability */
public void setListener(TransportEventListener listener);
public String getStyle();
public int countPeers();
@@ -51,12 +124,17 @@ public interface Transport {
public boolean haveCapacity();
public boolean haveCapacity(int pct);
public Vector getClockSkews();
public List getMostRecentErrorMessages();
public List<String> getMostRecentErrorMessages();
public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException;
public short getReachabilityStatus();
public void recheckReachability();
public boolean isBacklogged(Hash dest);
/**
* Was the peer UNreachable (outbound only) the last time we tried it?
* This is NOT reset if the peer contacts us and it is never expired.
*/
public boolean wasUnreachable(Hash dest);
public boolean isUnreachable(Hash peer);
@@ -10,8 +10,11 @@ package net.i2p.router.transport;
import java.io.IOException;
import java.io.Writer;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
@@ -23,6 +26,7 @@ import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
@@ -36,7 +40,6 @@ import net.i2p.router.MessageSelector;
import net.i2p.router.OutNetMessage;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.LHMCache;
import net.i2p.util.Log;
@@ -51,13 +54,14 @@ import net.i2p.util.SystemVersion;
public abstract class TransportImpl implements Transport {
private final Log _log;
private TransportEventListener _listener;
private RouterAddress _currentAddress;
protected final List<RouterAddress> _currentAddresses;
// Only used by NTCP. SSU does not use. See send() below.
private final BlockingQueue<OutNetMessage> _sendPool;
protected final RouterContext _context;
/** map from routerIdentHash to timestamp (Long) that the peer was last unreachable */
private final Map<Hash, Long> _unreachableEntries;
private final Set<Hash> _wasUnreachableEntries;
private final Set<InetAddress> _localAddresses;
/** global router ident -> IP */
private static final Map<Hash, byte[]> _IPMap;
@@ -88,12 +92,15 @@ public abstract class TransportImpl implements Transport {
_context.statManager().createRequiredRateStat("transport.sendProcessingTime", "Time to process and send a message (ms)", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
//_context.statManager().createRateStat("transport.sendProcessingTime." + getStyle(), "Time to process and send a message (ms)", "Transport", new long[] { 60*1000l });
_context.statManager().createRateStat("transport.expiredOnQueueLifetime", "How long a message that expires on our outbound queue is processed", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l } );
_currentAddresses = new CopyOnWriteArrayList();
if (getStyle().equals("NTCP"))
_sendPool = new ArrayBlockingQueue(8);
else
_sendPool = null;
_unreachableEntries = new HashMap(16);
_wasUnreachableEntries = new ConcurrentHashSet(16);
_localAddresses = new ConcurrentHashSet(4);
_context.simpleScheduler().addPeriodicEvent(new CleanupUnreachable(), 2 * UNREACHABLE_PERIOD, UNREACHABLE_PERIOD / 2);
}
@@ -119,6 +126,9 @@ public abstract class TransportImpl implements Transport {
/** Per-transport connection limit */
public int getMaxConnections() {
if (_context.commSystem().isDummy())
// testing
return 0;
String style = getStyle();
// object churn
String maxProp;
@@ -135,7 +145,7 @@ public abstract class TransportImpl implements Transport {
if (bw > Router.CAPABILITY_BW12 && bw <= Router.CAPABILITY_BW256)
def *= (1 + bw - Router.CAPABILITY_BW12);
}
if (((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled()) {
if (_context.netDb().floodfillEnabled()) {
// && !SystemVersion.isWindows()) {
def *= 17; def /= 10; // 425 for Class O ff
}
@@ -167,7 +177,7 @@ public abstract class TransportImpl implements Transport {
*/
public Vector getClockSkews() { return new Vector(); }
public List getMostRecentErrorMessages() { return Collections.EMPTY_LIST; }
public List<String> getMostRecentErrorMessages() { return Collections.EMPTY_LIST; }
/**
* Nonblocking call to pull the next outbound message
@@ -463,63 +473,201 @@ public abstract class TransportImpl implements Transport {
}
/** Do we increase the advertised cost when approaching conn limits? */
public static final boolean ADJUST_COST = true;
protected static final boolean ADJUST_COST = true;
/** TODO change to 2 */
protected static final int CONGESTION_COST_ADJUSTMENT = 1;
/** What addresses are we currently listening to? */
public RouterAddress getCurrentAddress() {
return _currentAddress;
/**
* What addresses are we currently listening to?
* Replaces getCurrentAddress()
* @return all addresses, non-null
* @since IPv6
*/
public List<RouterAddress> getCurrentAddresses() {
return _currentAddresses;
}
/**
* What address are we currently listening to?
* Replaces getCurrentAddress()
* @param ipv6 true for IPv6 only; false for IPv4 only
* @return first matching address or null
* @since IPv6
*/
public RouterAddress getCurrentAddress(boolean ipv6) {
for (RouterAddress ra : _currentAddresses) {
if (ipv6 == TransportUtil.isIPv6(ra))
return ra;
}
return null;
}
/**
* Do we have any current address?
* @since IPv6
*/
public boolean hasCurrentAddress() {
return !_currentAddresses.isEmpty();
}
/**
* Ask the transport to update its address based on current information and return it
* Transports should override.
* @return all addresses, non-null
* @since 0.7.12
*/
public RouterAddress updateAddress() {
return _currentAddress;
public List<RouterAddress> updateAddress() {
return _currentAddresses;
}
/**
* Replace any existing addresses for the current transport with the given
* one.
* Replace any existing addresses for the current transport
* with the same IP length (4 or 16) with the given one.
* TODO: Allow multiple addresses of the same length.
* Calls listener.transportAddressChanged()
*
* @param address null to remove all
*/
protected void replaceAddress(RouterAddress address) {
// _log.error("Replacing address for " + getStyle() + " was " + _currentAddress + " now " + address);
_currentAddress = address;
if (_log.shouldLog(Log.WARN))
_log.warn("Replacing address with " + address, new Exception());
if (address == null) {
_currentAddresses.clear();
} else {
boolean isIPv6 = TransportUtil.isIPv6(address);
for (RouterAddress ra : _currentAddresses) {
if (isIPv6 == TransportUtil.isIPv6(ra))
_currentAddresses.remove(ra);
}
_currentAddresses.add(address);
}
if (_log.shouldLog(Log.WARN))
_log.warn(getStyle() + " now has " + _currentAddresses.size() + " addresses");
if (_listener != null)
_listener.transportAddressChanged();
}
/**
* Save a local address we were notified about before we started.
*
* @since IPv6
*/
protected void saveLocalAddress(InetAddress address) {
_localAddresses.add(address);
}
/**
* Return and then clear all saved local addresses.
*
* @since IPv6
*/
protected Collection<InetAddress> getSavedLocalAddresses() {
List<InetAddress> rv = new ArrayList(_localAddresses);
_localAddresses.clear();
return rv;
}
/**
* Get all available address we can use,
* shuffled and then sorted by cost/preference.
* Lowest cost (most preferred) first.
* @return non-null, possibly empty
* @since IPv6
*/
protected List<RouterAddress> getTargetAddresses(RouterInfo target) {
List<RouterAddress> rv = target.getTargetAddresses(getStyle());
// Shuffle so everybody doesn't use the first one
if (rv.size() > 1) {
Collections.shuffle(rv, _context.random());
TransportUtil.IPv6Config config = getIPv6Config();
int adj;
switch (config) {
case IPV6_DISABLED:
adj = 10; break;
case IPV6_NOT_PREFERRED:
adj = 1; break;
default:
case IPV6_ENABLED:
adj = 0; break;
case IPV6_PREFERRED:
adj = -1; break;
case IPV6_ONLY:
adj = -10; break;
}
Collections.sort(rv, new AddrComparator(adj));
}
return rv;
}
/**
* Compare based on published cost, adjusting for our IPv6 preference.
* Lowest cost (most preferred) first.
* @since IPv6
*/
private static class AddrComparator implements Comparator<RouterAddress> {
private final int adj;
public AddrComparator(int ipv6Adjustment) {
adj = ipv6Adjustment;
}
public int compare(RouterAddress l, RouterAddress r) {
int lc = l.getCost();
int rc = r.getCost();
byte[] lip = l.getIP();
byte[] rip = r.getIP();
if (lip == null)
lc += 20;
else if (lip.length == 16)
lc += adj;
if (rip == null)
rc += 20;
else if (rip.length == 16)
rc += adj;
if (lc > rc)
return 1;
if (lc < rc)
return -1;
return 0;
}
}
/**
* Notify a transport of an external address change.
* This may be from a local interface, UPnP, a config change, etc.
* This should not be called if the ip didn't change
* (from that source's point of view), or is a local address,
* or if the ip is IPv6, but the transport should check anyway.
* (from that source's point of view), or is a local address.
* May be called multiple times for IPv4 or IPv6.
* The transport should also do its own checking on whether to accept
* notifications from this source.
*
* This can be called before startListening() to set an initial address,
* or after the transport is running.
*
* This implementation does nothing. Transports should override if they want notification.
*
* @param source defined in Transport.java
* @param ip typ. IPv4 non-local
* @param ip typ. IPv4 or IPv6 non-local; may be null to indicate IPv4 failure or port info only
* @param port 0 for unknown or unchanged
*/
public void externalAddressReceived(String source, byte[] ip, int port) {}
public void externalAddressReceived(AddressSource source, byte[] ip, int port) {}
/**
* Notify a transport of the results of trying to forward a port.
*
* This implementation does nothing. Transports should override if they want notification.
*
* @param ip may be null
* @param port the internal port
* @param externalPort the external port, which for now should always be the same as
* the internal port if the forwarding was successful.
*/
public void forwardPortStatus(int port, int externalPort, boolean success, String reason) {}
public void forwardPortStatus(byte[] ip, int port, int externalPort, boolean success, String reason) {}
/**
* What port would the transport like to have forwarded by UPnP.
* What INTERNAL port would the transport like to have forwarded by UPnP.
* This can't be passed via getCurrentAddress(), as we have to open the port
* before we can publish the address.
* before we can publish the address, and that's the external port anyway.
*
* @return port or -1 for none or 0 for any
*/
@@ -531,13 +679,13 @@ public abstract class TransportImpl implements Transport {
public void renderStatusHTML(Writer out) throws IOException {}
public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException { renderStatusHTML(out); }
public RouterContext getContext() { return _context; }
public short getReachabilityStatus() { return CommSystemFacade.STATUS_UNKNOWN; }
public void recheckReachability() {}
public boolean isBacklogged(Hash dest) { return false; }
public boolean isEstablished(Hash dest) { return false; }
private static final long UNREACHABLE_PERIOD = 5*60*1000;
public boolean isUnreachable(Hash peer) {
long now = _context.clock().now();
synchronized (_unreachableEntries) {
@@ -551,6 +699,7 @@ public abstract class TransportImpl implements Transport {
}
}
}
/** called when we can't reach a peer */
/** This isn't very useful since it is cleared when they contact us */
public void markUnreachable(Hash peer) {
@@ -560,6 +709,7 @@ public abstract class TransportImpl implements Transport {
}
markWasUnreachable(peer, true);
}
/** called when we establish a peer connection (outbound or inbound) */
public void markReachable(Hash peer, boolean isInbound) {
// if *some* transport can reach them, then we shouldn't banlist 'em
@@ -605,9 +755,15 @@ public abstract class TransportImpl implements Transport {
else
_wasUnreachableEntries.remove(peer);
if (_log.shouldLog(Log.INFO))
_log.info(this.getStyle() + " setting wasUnreachable to " + yes + " for " + peer);
_log.info(this.getStyle() + " setting wasUnreachable to " + yes + " for " + peer,
yes ? new Exception() : null);
}
/**
* IP of the peer from the last connection (in or out, any transport).
*
* @param IPv4 or IPv6, non-null
*/
public void setIP(Hash peer, byte[] ip) {
byte[] old;
synchronized (_IPMap) {
@@ -617,6 +773,11 @@ public abstract class TransportImpl implements Transport {
_context.commSystem().queueLookup(ip);
}
/**
* IP of the peer from the last connection (in or out, any transport).
*
* @return IPv4 or IPv6 or null
*/
public static byte[] getIP(Hash peer) {
synchronized (_IPMap) {
return _IPMap.get(peer);
@@ -632,26 +793,20 @@ public abstract class TransportImpl implements Transport {
}
}
/** @param addr non-null */
public static boolean isPubliclyRoutable(byte addr[]) {
if (addr.length == 4) {
int a0 = addr[0] & 0xFF;
if (a0 == 127) return false;
if (a0 == 10) return false;
int a1 = addr[1] & 0xFF;
if (a0 == 172 && a1 >= 16 && a1 <= 31) return false;
if (a0 == 192 && a1 == 168) return false;
if (a0 >= 224) return false; // no multicast
if (a0 == 0) return false;
if (a0 == 169 && a1 == 254) return false;
// 5/8 allocated to RIPE (30 November 2010)
//if ((addr[0]&0xFF) == 5) return false; // Hamachi
return true; // or at least possible to be true
} else if (addr.length == 16) {
return false;
} else {
// ipv?
return false;
}
/**
* @since IPv6
*/
protected TransportUtil.IPv6Config getIPv6Config() {
return TransportUtil.getIPv6Config(_context, getStyle());
}
/**
* Allows IPv6 only if the transport is configured for it.
* Caller must check if we actually have a public IPv6 address.
* @param addr non-null
*/
protected boolean isPubliclyRoutable(byte addr[]) {
return TransportUtil.isPubliclyRoutable(addr,
getIPv6Config() != TransportUtil.IPv6Config.IPV6_DISABLED);
}
}
@@ -14,6 +14,7 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -29,6 +30,7 @@ import net.i2p.data.i2np.I2NPMessage;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import static net.i2p.router.transport.Transport.AddressSource.*;
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
import net.i2p.router.transport.ntcp.NTCPTransport;
import net.i2p.router.transport.udp.UDPTransport;
@@ -85,13 +87,24 @@ public class TransportManager implements TransportEventListener {
private void configTransports() {
boolean enableUDP = _context.getBooleanPropertyDefaultTrue(PROP_ENABLE_UDP);
Transport udp = null;
if (enableUDP) {
UDPTransport udp = new UDPTransport(_context, _dhThread);
udp = new UDPTransport(_context, _dhThread);
addTransport(udp);
initializeAddress(udp);
}
if (isNTCPEnabled(_context))
addTransport(new NTCPTransport(_context, _dhThread));
if (isNTCPEnabled(_context)) {
Transport ntcp = new NTCPTransport(_context, _dhThread);
addTransport(ntcp);
initializeAddress(ntcp);
if (udp != null) {
// pass along the port SSU is probably going to use
// so that NTCP may bind early
int port = udp.getRequestedPort();
if (port > 0)
ntcp.externalAddressReceived(SOURCE_CONFIG, null, port);
}
}
if (_transports.isEmpty())
_log.log(Log.CRIT, "No transports are enabled");
}
@@ -100,40 +113,44 @@ public class TransportManager implements TransportEventListener {
return ctx.getBooleanPropertyDefaultTrue(PROP_ENABLE_NTCP);
}
/**
* Notify transport of ALL routable interface addresses, including IPv6.
* It's the transport's job to ignore what it can't handle.
*/
private void initializeAddress(Transport t) {
String ips = Addresses.getAnyAddress();
if (ips == null)
return;
InetAddress ia;
try {
ia = InetAddress.getByName(ips);
} catch (UnknownHostException e) {
_log.error("UDP failed to bind to local address", e);
return;
Set<String> ipset = Addresses.getAddresses(false, true); // non-local, include IPv6
for (String ips : ipset) {
try {
InetAddress ia = InetAddress.getByName(ips);
byte[] ip = ia.getAddress();
t.externalAddressReceived(SOURCE_INTERFACE, ip, 0);
} catch (UnknownHostException e) {
_log.error("UDP failed to bind to local address", e);
}
}
byte[] ip = ia.getAddress();
t.externalAddressReceived(Transport.SOURCE_INTERFACE, ip, 0);
}
/**
* callback from UPnP
* Only tell SSU, it will tell NTCP
* Initialize from interfaces, and callback from UPnP or SSU.
* Tell all transports... but don't loop
*
*/
public void externalAddressReceived(String source, byte[] ip, int port) {
Transport t = getTransport(UDPTransport.STYLE);
if (t != null)
t.externalAddressReceived(source, ip, port);
public void externalAddressReceived(Transport.AddressSource source, byte[] ip, int port) {
for (Transport t : _transports.values()) {
// don't loop
if (!(source == SOURCE_SSU && t.getStyle().equals(UDPTransport.STYLE)))
t.externalAddressReceived(source, ip, port);
}
}
/**
* callback from UPnP
*
*/
public void forwardPortStatus(String style, int port, int externalPort, boolean success, String reason) {
public void forwardPortStatus(String style, byte[] ip, int port, int externalPort, boolean success, String reason) {
Transport t = getTransport(style);
if (t != null)
t.forwardPortStatus(port, externalPort, success, reason);
t.forwardPortStatus(ip, port, externalPort, success, reason);
}
public synchronized void startListening() {
@@ -148,7 +165,17 @@ public class TransportManager implements TransportEventListener {
_upnpManager.start();
configTransports();
_log.debug("Starting up the transport manager");
for (Transport t : _transports.values()) {
// Let's do this in a predictable order to make testing easier
// Start NTCP first so it can get notified from SSU
List<Transport> tps = new ArrayList();
Transport tp = getTransport(NTCPTransport.STYLE);
if (tp != null)
tps.add(tp);
tp = getTransport(UDPTransport.STYLE);
if (tp != null)
tps.add(tp);
//for (Transport t : _transports.values()) {
for (Transport t : tps) {
t.startListening();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Transport " + t.getStyle() + " started");
@@ -248,7 +275,7 @@ public class TransportManager implements TransportEventListener {
*/
public boolean haveInboundCapacity(int pct) {
for (Transport t : _transports.values()) {
if (t.getCurrentAddress() != null && t.haveCapacity(pct))
if (t.hasCurrentAddress() && t.haveCapacity(pct))
return true;
}
return false;
@@ -266,8 +293,8 @@ public class TransportManager implements TransportEventListener {
if ((tempSkews == null) || (tempSkews.isEmpty())) continue;
skews.addAll(tempSkews);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Transport manager returning " + skews.size() + " peer clock skews.");
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Transport manager returning " + skews.size() + " peer clock skews.");
return skews;
}
@@ -324,6 +351,8 @@ public class TransportManager implements TransportEventListener {
*
* For blocking purposes, etc. it's worth checking both
* the netDb addresses and this address.
*
* @return IPv4 or IPv6 or null
*/
public byte[] getIP(Hash dest) {
return TransportImpl.getIP(dest);
@@ -332,35 +361,62 @@ public class TransportManager implements TransportEventListener {
/**
* This forces a rebuild
*/
public Map<String, RouterAddress> getAddresses() {
Map<String, RouterAddress> rv = new HashMap(_transports.size());
public List<RouterAddress> getAddresses() {
List<RouterAddress> rv = new ArrayList(4);
// do this first since SSU may force a NTCP change
for (Transport t : _transports.values())
t.updateAddress();
for (Transport t : _transports.values()) {
if (t.getCurrentAddress() != null)
rv.put(t.getStyle(), t.getCurrentAddress());
rv.addAll(t.getCurrentAddresses());
}
return rv;
}
/**
* The actual or requested INTERNAL ports, for each transport,
* which we will pass along to UPnP to be forwarded.
* @since IPv6
*/
private Map<String, Integer> getPorts() {
Map<String, Integer> rv = new HashMap(_transports.size());
static class Port {
public final String style;
public final int port;
public Port(String style, int port) {
this.style = style;
this.port = port;
}
@Override
public int hashCode() {
return style.hashCode() ^ port;
}
@Override
public boolean equals(Object o) {
if (o == null)
return false;
if (! (o instanceof Port))
return false;
Port p = (Port) o;
return port == p.port && style.equals(p.style);
}
}
/**
* Include the published port, or the requested port, for each transport
* which we will pass along to UPnP
*/
private Set<Port> getPorts() {
Set<Port> rv = new HashSet(4);
for (Transport t : _transports.values()) {
int port = t.getRequestedPort();
// Use UDP port for NTCP too - see comment in NTCPTransport.getRequestedPort() for why this is here
if (t.getStyle().equals(NTCPTransport.STYLE) && port <= 0 &&
_context.getBooleanProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_AUTO_PORT)) {
_context.getBooleanProperty(NTCPTransport.PROP_I2NP_NTCP_AUTO_PORT)) {
Transport udp = getTransport(UDPTransport.STYLE);
if (udp != null)
port = t.getRequestedPort();
}
if (port > 0)
rv.put(t.getStyle(), Integer.valueOf(port));
rv.add(new Port(t.getStyle(), port));
}
return rv;
}
@@ -461,11 +517,11 @@ public class TransportManager implements TransportEventListener {
*/
public void messageReceived(I2NPMessage message, RouterIdentity fromRouter, Hash fromRouterHash) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("I2NPMessage received: " + message.getClass().getName(), new Exception("Where did I come from again?"));
_log.debug("I2NPMessage received: " + message.getClass().getSimpleName() /*, new Exception("Where did I come from again?") */ );
try {
_context.inNetMessagePool().add(message, fromRouter, fromRouterHash);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Added to in pool");
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Added to in pool");
} catch (IllegalArgumentException iae) {
if (_log.shouldLog(Log.WARN))
_log.warn("Error receiving message", iae);
@@ -477,8 +533,8 @@ public class TransportManager implements TransportEventListener {
_upnpManager.update(getPorts());
}
public List getMostRecentErrorMessages() {
List rv = new ArrayList(16);
public List<String> getMostRecentErrorMessages() {
List<String> rv = new ArrayList(16);
for (Transport t : _transports.values()) {
rv.addAll(t.getMostRecentErrorMessages());
}
@@ -502,11 +558,15 @@ public class TransportManager implements TransportEventListener {
StringBuilder buf = new StringBuilder(4*1024);
buf.append("<h3>").append(_("Router Transport Addresses")).append("</h3><pre>\n");
for (Transport t : _transports.values()) {
if (t.getCurrentAddress() != null)
buf.append(t.getCurrentAddress());
else
if (t.hasCurrentAddress()) {
for (RouterAddress ra : t.getCurrentAddresses()) {
buf.append(ra.toString());
buf.append("\n\n");
}
} else {
buf.append(_("{0} is used for outbound connections only", t.getStyle()));
buf.append("\n\n");
buf.append("\n\n");
}
}
buf.append("</pre>\n");
out.write(buf.toString());
@@ -0,0 +1,131 @@
package net.i2p.router.transport;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind { either expressed or implied.
* It probably won't make your computer catch on fire { or eat
* your children { but it might. Use at your own risk.
*
*/
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import net.i2p.data.RouterAddress;
import net.i2p.router.RouterContext;
/**
* @since IPv6
*/
public abstract class TransportUtil {
public static final String NTCP_IPV6_CONFIG = "i2np.ntcp.ipv6";
public static final String SSU_IPV6_CONFIG = "i2np.udp.ipv6";
public enum IPv6Config {
/** IPv6 disabled */
IPV6_DISABLED("false"),
/** lower priority than IPv4 */
IPV6_NOT_PREFERRED("preferIPv4"),
/** equal priority to IPv4 */
IPV6_ENABLED("enable"),
/** higher priority than IPv4 */
IPV6_PREFERRED("preferIPv6"),
/** IPv4 disabled */
IPV6_ONLY("only");
private final String cfgstr;
IPv6Config(String cfgstr) {
this.cfgstr = cfgstr;
}
public String toConfigString() {
return cfgstr;
}
}
private static final Map<String, IPv6Config> BY_NAME = new HashMap<String, IPv6Config>();
private static final IPv6Config DEFAULT_IPV6_CONFIG = IPv6Config.IPV6_DISABLED;
static {
for (IPv6Config cfg : IPv6Config.values()) {
BY_NAME.put(cfg.toConfigString(), cfg);
}
// alias
BY_NAME.put("true", IPv6Config.IPV6_ENABLED);
}
public static IPv6Config getIPv6Config(RouterContext ctx, String transportStyle) {
String cfg;
if (transportStyle.equals("NTCP"))
cfg = ctx.getProperty(NTCP_IPV6_CONFIG);
else if (transportStyle.equals("SSU"))
cfg = ctx.getProperty(SSU_IPV6_CONFIG);
else
return DEFAULT_IPV6_CONFIG;
return getIPv6Config(cfg);
}
public static IPv6Config getIPv6Config(String cfg) {
if (cfg == null)
return DEFAULT_IPV6_CONFIG;
IPv6Config c = BY_NAME.get(cfg);
if (c != null)
return c;
return DEFAULT_IPV6_CONFIG;
}
/**
* Addresses without a host (i.e. w/introducers)
* are assumed to be IPv4
*/
public static boolean isIPv6(RouterAddress addr) {
// do this the fast way, without calling getIP() to parse the host string
String host = addr.getOption(RouterAddress.PROP_HOST);
return host != null && host.contains(":");
}
/**
* @param addr non-null
* @since IPv6 moved from TransportImpl
*/
public static boolean isPubliclyRoutable(byte addr[], boolean allowIPv6) {
if (addr.length == 4) {
int a0 = addr[0] & 0xFF;
if (a0 == 127) return false;
if (a0 == 10) return false;
int a1 = addr[1] & 0xFF;
if (a0 == 172 && a1 >= 16 && a1 <= 31) return false;
if (a0 == 192 && a1 == 168) return false;
if (a0 >= 224) return false; // no multicast
if (a0 == 0) return false;
if (a0 == 169 && a1 == 254) return false;
// 5/8 allocated to RIPE (30 November 2010)
//if ((addr[0]&0xFF) == 5) return false; // Hamachi
return true; // or at least possible to be true
} else if (addr.length == 16) {
if (allowIPv6) {
// disallow 2002::/16 (6to4 RFC 3056)
if (addr[0] == 0x20 && addr[1] == 0x02)
return false;
try {
InetAddress ia = InetAddress.getByAddress(addr);
return
(!ia.isLinkLocalAddress()) &&
(!ia.isMulticastAddress()) &&
(!ia.isAnyLocalAddress()) &&
(!ia.isLoopbackAddress()) &&
(!ia.isSiteLocalAddress());
} catch (UnknownHostException uhe) {}
}
}
return false;
}
}
@@ -52,6 +52,7 @@ import org.freenetproject.ForwardPortStatus;
*
* @see "http://www.upnp.org/"
* @see "http://en.wikipedia.org/wiki/Universal_Plug_and_Play"
* @since 0.7.4
*/
/*
@@ -147,7 +148,7 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
InetAddress detectedIP = InetAddress.getByName(natAddress);
short status = DetectedIP.NOT_SUPPORTED;
thinksWeAreDoubleNatted = !TransportImpl.isPubliclyRoutable(detectedIP.getAddress());
thinksWeAreDoubleNatted = !TransportUtil.isPubliclyRoutable(detectedIP.getAddress(), false);
// If we have forwarded a port AND we don't have a private address
if (_log.shouldLog(Log.WARN))
_log.warn("NATAddress: \"" + natAddress + "\" detectedIP: " + detectedIP + " double? " + thinksWeAreDoubleNatted);
@@ -843,7 +844,11 @@ class UPnP extends ControlPoint implements DeviceChangeListener, EventListener {
fps = new ForwardPortStatus(ForwardPortStatus.PROBABLE_FAILURE, "UPnP port forwarding apparently failed", port.portNumber);
}
Map map = Collections.singletonMap(port, fps);
forwardCallback.portForwardStatus(map);
try {
forwardCallback.portForwardStatus(map);
} catch (Exception e) {
_log.error("UPnP RPT error", e);
}
}
}
}
@@ -11,6 +11,7 @@ import java.util.Map;
import java.util.Set;
import net.i2p.router.RouterContext;
import static net.i2p.router.transport.Transport.AddressSource.SOURCE_UPNP;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
import net.i2p.util.Translate;
@@ -25,6 +26,7 @@ import org.freenetproject.ForwardPortStatus;
* Bridge from the I2P RouterAddress data structure to
* the freenet data structures
*
* @since 0.7.4
* @author zzz
*/
class UPnPManager {
@@ -106,7 +108,7 @@ class UPnPManager {
* which can have multiple UPnP threads running at once, but
* that should be ok.
*/
public void update(Map<String, Integer> ports) {
public void update(Set<TransportManager.Port> ports) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("UPnP Update with " + ports.size() + " ports");
@@ -121,9 +123,9 @@ class UPnPManager {
//}
Set<ForwardPort> forwards = new HashSet(ports.size());
for (Map.Entry<String, Integer> entry : ports.entrySet()) {
String style = entry.getKey();
int port = entry.getValue().intValue();
for (TransportManager.Port entry : ports) {
String style = entry.style;
int port = entry.port;
int protocol = -1;
if ("SSU".equals(style))
protocol = ForwardPort.PROTOCOL_UDP_IPV4;
@@ -151,17 +153,19 @@ class UPnPManager {
if (_log.shouldLog(Log.DEBUG))
_log.debug("UPnP Callback:");
byte[] ipaddr = null;
DetectedIP[] ips = _upnp.getAddress();
if (ips != null) {
for (DetectedIP ip : ips) {
// store the first public one and tell the transport manager if it changed
if (TransportImpl.isPubliclyRoutable(ip.publicAddress.getAddress())) {
if (TransportUtil.isPubliclyRoutable(ip.publicAddress.getAddress(), false)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("External address: " + ip.publicAddress + " type: " + ip.natType);
if (!ip.publicAddress.equals(_detectedAddress)) {
_detectedAddress = ip.publicAddress;
_manager.externalAddressReceived(Transport.SOURCE_UPNP, _detectedAddress.getAddress(), 0);
_manager.externalAddressReceived(SOURCE_UPNP, _detectedAddress.getAddress(), 0);
}
ipaddr = ip.publicAddress.getAddress();
break;
}
}
@@ -184,7 +188,7 @@ class UPnPManager {
else
continue;
boolean success = fps.status >= ForwardPortStatus.MAYBE_SUCCESS;
_manager.forwardPortStatus(style, fp.portNumber, fps.externalPort, success, fps.reasonString);
_manager.forwardPortStatus(style, ipaddr, fp.portNumber, fps.externalPort, success, fps.reasonString);
}
}
}
@@ -261,8 +261,8 @@ class EstablishState {
// ok, we are onto the encrypted area
while (src.hasRemaining() && !_corrupt) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"Encrypted bytes available (" + src.hasRemaining() + ")");
//if (_log.shouldLog(Log.DEBUG))
// _log.debug(prefix()+"Encrypted bytes available (" + src.hasRemaining() + ")");
while (_curEncryptedOffset < _curEncrypted.length && src.hasRemaining()) {
_curEncrypted[_curEncryptedOffset++] = src.get();
_received++;
@@ -299,8 +299,8 @@ class EstablishState {
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")");
//if (_log.shouldLog(Log.DEBUG))
// _log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")");
if (_sz_aliceIdent_tsA_padding_aliceSig.size() >= _sz_aliceIdent_tsA_padding_aliceSigSize) {
verifyInbound();
@@ -20,6 +20,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import net.i2p.I2PAppContext;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterIdentity;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.RouterContext;
@@ -779,9 +780,9 @@ class EventPumper implements Runnable {
key.attach(con);
con.setKey(key);
try {
NTCPAddress naddr = con.getRemoteAddress();
if (naddr.getPort() <= 0)
throw new IOException("Invalid NTCP address: " + naddr);
RouterAddress naddr = con.getRemoteAddress();
if (naddr.getPort() <= 0)
throw new IOException("Invalid NTCP address: " + naddr);
InetSocketAddress saddr = new InetSocketAddress(naddr.getHost(), naddr.getPort());
boolean connected = con.getChannel().connect(saddr);
if (connected) {
@@ -1,134 +0,0 @@
package net.i2p.router.transport.ntcp;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterAddress;
import net.i2p.router.transport.TransportImpl;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
/**
* Wrap up an address
*/
public class NTCPAddress {
private final int _port;
private final String _host;
//private InetAddress _addr;
/** Port number used in RouterAddress definitions */
public final static String PROP_PORT = RouterAddress.PROP_PORT;
/** Host name used in RouterAddress definitions */
public final static String PROP_HOST = RouterAddress.PROP_HOST;
public static final int DEFAULT_COST = 10;
public NTCPAddress(String host, int port) {
if (host != null)
_host = host.trim();
else
_host = null;
_port = port;
}
/*
public NTCPAddress() {
_host = null;
_port = -1;
// _addr = null;
}
public NTCPAddress(InetAddress addr, int port) {
if (addr != null)
_host = addr.getHostAddress();
_addr = addr;
_port = port;
}
*/
public NTCPAddress(RouterAddress addr) {
if (addr == null) {
_host = null;
_port = -1;
return;
}
_host = addr.getOption(PROP_HOST);
_port = addr.getPort();
}
public RouterAddress toRouterAddress() {
if ( (_host == null) || (_port <= 0) )
return null;
RouterAddress addr = new RouterAddress();
addr.setCost(DEFAULT_COST);
//addr.setExpiration(null);
Properties props = new Properties();
props.setProperty(PROP_HOST, _host);
props.setProperty(PROP_PORT, ""+_port);
addr.setOptions(props);
addr.setTransportStyle(NTCPTransport.STYLE);
return addr;
}
public String getHost() { return _host; }
//public void setHost(String host) { _host = host; }
//public InetAddress getAddress() { return _addr; }
//public void setAddress(InetAddress addr) { _addr = addr; }
public int getPort() { return _port; }
//public void setPort(int port) { _port = port; }
public boolean isPubliclyRoutable() {
return isPubliclyRoutable(_host);
}
public static boolean isPubliclyRoutable(String host) {
if (host == null) return false;
byte quad[] = Addresses.getIP(host);
return TransportImpl.isPubliclyRoutable(quad);
}
@Override
public String toString() { return _host + ":" + _port; }
@Override
public int hashCode() {
int rv = _port;
//if (_addr != null)
// rv += _addr.getHostAddress().hashCode();
//else
if (_host != null) rv ^= _host.hashCode();
return rv;
}
@Override
public boolean equals(Object val) {
if ( (val != null) && (val instanceof NTCPAddress) ) {
NTCPAddress addr = (NTCPAddress)val;
String hostname = null;
if (addr.getHost() != null)
hostname = addr.getHost().trim();
String ourHost = getHost();
if (ourHost != null)
ourHost = ourHost.trim();
return DataHelper.eq(hostname, ourHost) && getPort() == addr.getPort();
}
return false;
}
public boolean equals(RouterAddress addr) {
if (addr == null) return false;
return ( (_host.equals(addr.getOption(PROP_HOST))) &&
(Integer.toString(_port).equals(addr.getOption(PROP_PORT))) );
}
}
@@ -1,6 +1,7 @@
package net.i2p.router.transport.ntcp;
import java.io.IOException;
import java.net.Inet6Address;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
@@ -16,6 +17,7 @@ import java.util.zip.Adler32;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterIdentity;
import net.i2p.data.RouterInfo;
import net.i2p.data.SessionKey;
@@ -86,7 +88,7 @@ class NTCPConnection {
private final NTCPTransport _transport;
private final boolean _isInbound;
private volatile boolean _closed;
private NTCPAddress _remAddr;
private final RouterAddress _remAddr;
private RouterIdentity _remotePeer;
private long _clockSkew; // in seconds
/**
@@ -165,6 +167,7 @@ class NTCPConnection {
_log = ctx.logManager().getLog(getClass());
_created = System.currentTimeMillis();
_transport = transport;
_remAddr = null;
_chan = chan;
_readBufs = new ConcurrentLinkedQueue();
_writeBufs = new ConcurrentLinkedQueue();
@@ -187,7 +190,7 @@ class NTCPConnection {
* Create an outbound unconnected NTCP connection
*
*/
public NTCPConnection(RouterContext ctx, NTCPTransport transport, RouterIdentity remotePeer, NTCPAddress remAddr) {
public NTCPConnection(RouterContext ctx, NTCPTransport transport, RouterIdentity remotePeer, RouterAddress remAddr) {
_context = ctx;
_log = ctx.logManager().getLog(getClass());
_created = System.currentTimeMillis();
@@ -216,15 +219,42 @@ class NTCPConnection {
_prevReadBlock = new byte[BLOCK_SIZE];
_transport.establishing(this);
}
/**
* Valid for inbound; valid for outbound shortly after creation
*/
public SocketChannel getChannel() { return _chan; }
/**
* Valid for inbound; valid for outbound shortly after creation
*/
public SelectionKey getKey() { return _conKey; }
public void setChannel(SocketChannel chan) { _chan = chan; }
public void setKey(SelectionKey key) { _conKey = key; }
public boolean isInbound() { return _isInbound; }
public boolean isEstablished() { return _established; }
/**
* @since IPv6
*/
public boolean isIPv6() {
return _chan != null &&
_chan.socket().getInetAddress() instanceof Inet6Address;
}
/**
* Only valid during establishment; null later
*/
public EstablishState getEstablishState() { return _establishState; }
public NTCPAddress getRemoteAddress() { return _remAddr; }
/**
* Only valid for outbound; null for inbound
*/
public RouterAddress getRemoteAddress() { return _remAddr; }
/**
* Valid for outbound; valid for inbound after handshake
*/
public RouterIdentity getRemotePeer() { return _remotePeer; }
public void setRemotePeer(RouterIdentity ident) { _remotePeer = ident; }
@@ -3,14 +3,17 @@ package net.i2p.router.transport.ntcp;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.InetAddress;
import java.net.Inet6Address;
import java.net.UnknownHostException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -30,12 +33,17 @@ import net.i2p.router.OutNetMessage;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.CommSystemFacadeImpl;
import net.i2p.router.transport.Transport;
import static net.i2p.router.transport.Transport.AddressSource.*;
import net.i2p.router.transport.TransportBid;
import net.i2p.router.transport.TransportImpl;
import net.i2p.router.transport.TransportUtil;
import static net.i2p.router.transport.TransportUtil.IPv6Config.*;
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.util.Addresses;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.Translate;
/**
@@ -52,16 +60,25 @@ public class NTCPTransport extends TransportImpl {
private final SharedBid _transientFail;
private final Object _conLock;
private final Map<Hash, NTCPConnection> _conByIdent;
private NTCPAddress _myAddress;
private final EventPumper _pumper;
private final Reader _reader;
private net.i2p.router.transport.ntcp.Writer _writer;
private int _ssuPort;
/** synch on this */
private final Set<InetSocketAddress> _endpoints;
/**
* list of NTCPConnection of connections not yet established that we
* want to remove on establishment or close on timeout
*/
private final Set<NTCPConnection> _establishing;
public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport";
public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoip";
public static final int DEFAULT_COST = 10;
/** this is rarely if ever used, default is to bind to wildcard address */
public static final String PROP_BIND_INTERFACE = "i2np.ntcp.bindInterface";
@@ -147,6 +164,7 @@ public class NTCPTransport extends TransportImpl {
_context.statManager().createRateStat("ntcp.wantsQueuedWrite", "", "ntcp", RATES);
//_context.statManager().createRateStat("ntcp.write", "", "ntcp", RATES);
_context.statManager().createRateStat("ntcp.writeError", "", "ntcp", RATES);
_endpoints = new HashSet(4);
_establishing = new ConcurrentHashSet(16);
_conLock = new Object();
_conByIdent = new ConcurrentHashMap(64);
@@ -195,10 +213,9 @@ public class NTCPTransport extends TransportImpl {
isNew = true;
RouterAddress addr = getTargetAddress(target);
if (addr != null) {
NTCPAddress naddr = new NTCPAddress(addr);
con = new NTCPConnection(_context, this, ident, naddr);
con = new NTCPConnection(_context, this, ident, addr);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send on a new con: " + con + " at " + addr + " for " + ih.toBase64());
_log.debug("Send on a new con: " + con + " at " + addr + " for " + ih);
_conByIdent.put(ih, con);
} else {
_log.error("we bid on a peer who doesn't have an ntcp address? " + target);
@@ -318,12 +335,12 @@ public class NTCPTransport extends TransportImpl {
if (_log.shouldLog(Log.DEBUG))
_log.debug("slow bid when trying to send to " + peer);
if (haveCapacity()) {
if (addr.getCost() > NTCPAddress.DEFAULT_COST)
if (addr.getCost() > DEFAULT_COST)
return _slowCostBid;
else
return _slowBid;
} else {
if (addr.getCost() > NTCPAddress.DEFAULT_COST)
if (addr.getCost() > DEFAULT_COST)
return _nearCapacityCostBid;
else
return _nearCapacityBid;
@@ -336,7 +353,7 @@ public class NTCPTransport extends TransportImpl {
* @since 0.9.6
*/
private RouterAddress getTargetAddress(RouterInfo target) {
List<RouterAddress> addrs = target.getTargetAddresses(STYLE);
List<RouterAddress> addrs = getTargetAddresses(target);
for (int i = 0; i < addrs.size(); i++) {
RouterAddress addr = addrs.get(i);
byte[] ip = addr.getIP();
@@ -458,38 +475,62 @@ public class NTCPTransport extends TransportImpl {
* verify stopped with isAlive()
* Unfortunately TransportManager doesn't do that, so we
* check here to prevent two pumpers.
* @return appears to be ignored by caller
*/
public synchronized RouterAddress startListening() {
public synchronized void startListening() {
// try once again to prevent two pumpers which is fatal
if (_pumper.isAlive())
return _myAddress != null ? _myAddress.toRouterAddress() : null;
return;
if (_log.shouldLog(Log.WARN)) _log.warn("Starting ntcp transport listening");
startIt();
configureLocalAddress();
return bindAddress();
RouterAddress addr = configureLocalAddress();
int port;
if (addr != null)
// probably not set
port = addr.getPort();
else
// received by externalAddressReceived() from TransportManager
port = _ssuPort;
RouterAddress myAddress = bindAddress(port);
if (myAddress != null) {
// fixed interface, or bound to the specified host
replaceAddress(myAddress);
} else if (addr != null) {
// specified host, bound to wildcard
replaceAddress(addr);
} else if (port > 0) {
// all detected interfaces
for (InetAddress ia : getSavedLocalAddresses()) {
OrderedProperties props = new OrderedProperties();
props.setProperty(RouterAddress.PROP_HOST, ia.getHostAddress());
props.setProperty(RouterAddress.PROP_PORT, Integer.toString(port));
int cost = getDefaultCost(ia instanceof Inet6Address);
myAddress = new RouterAddress(STYLE, props, cost);
replaceAddress(myAddress);
}
}
// TransportManager.startListening() calls router.rebuildRouterInfo()
}
/**
* Only called by CSFI.
* Caller should stop the transport first, then
* verify stopped with isAlive()
* @return appears to be ignored by caller
* Only called by externalAddressReceived().
*
* Doesn't actually restart unless addr is non-null and
* the port is different from the current listen port.
*
* If we had interface addresses before, we lost them.
*
* @param addr may be null
*/
public synchronized RouterAddress restartListening(RouterAddress addr) {
// try once again to prevent two pumpers which is fatal
// we could just return null since the return value is ignored
if (_pumper.isAlive())
return _myAddress != null ? _myAddress.toRouterAddress() : null;
if (_log.shouldLog(Log.WARN)) _log.warn("Restarting ntcp transport listening");
startIt();
if (addr == null)
_myAddress = null;
else
_myAddress = new NTCPAddress(addr);
return bindAddress();
private synchronized void restartListening(RouterAddress addr) {
if (addr != null) {
RouterAddress myAddress = bindAddress(addr.getPort());
if (myAddress != null)
replaceAddress(myAddress);
else
replaceAddress(addr);
// UDPTransport.rebuildExternalAddress() calls router.rebuildRouterInfo()
}
}
/**
@@ -520,9 +561,19 @@ public class NTCPTransport extends TransportImpl {
return _pumper.isAlive();
}
/** call from synchronized method */
private RouterAddress bindAddress() {
if (_myAddress != null) {
/**
* Only does something if myPort > 0 and myPort != current bound port
* (or there's no current port, or the configured interface or hostname changed).
* If we are changing the bound port, this restarts everything, which takes a long time.
*
* call from synchronized method
*
* @param myPort does nothing if <= 0
* @return new address ONLY if bound to specific address, otherwise null
*/
private RouterAddress bindAddress(int port) {
RouterAddress myAddress = null;
if (port > 0) {
InetAddress bindToAddr = null;
String bindTo = _context.getProperty(PROP_BIND_INTERFACE);
@@ -530,70 +581,122 @@ public class NTCPTransport extends TransportImpl {
// If we are configured with a fixed IP address,
// AND it's one of our local interfaces,
// bind only to that.
boolean isFixed = _context.getProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_AUTO_IP, "true")
.toLowerCase(Locale.US).equals("false");
String fixedHost = _context.getProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_HOSTNAME);
if (isFixed && fixedHost != null) {
try {
String testAddr = InetAddress.getByName(fixedHost).getHostAddress();
if (Addresses.getAddresses().contains(testAddr))
bindTo = testAddr;
} catch (UnknownHostException uhe) {}
}
bindTo = getFixedHost();
}
if (bindTo != null) {
try {
bindToAddr = InetAddress.getByName(bindTo);
} catch (UnknownHostException uhe) {
_log.log(Log.CRIT, "Invalid NTCP bind interface specified [" + bindTo + "]", uhe);
_log.error("Invalid NTCP bind interface specified [" + bindTo + "]", uhe);
// this can be implemented later, just updates some stats
// see udp/UDPTransport.java
//setReachabilityStatus(CommSystemFacade.STATUS_HOSED);
return null;
//return null;
// fall thru
}
}
try {
ServerSocketChannel chan = ServerSocketChannel.open();
chan.configureBlocking(false);
int port = _myAddress.getPort();
if (port > 0 && port < 1024)
_log.logAlways(Log.WARN, "Specified NTCP port is " + port + ", ports lower than 1024 not recommended");
InetSocketAddress addr = null;
if(bindToAddr==null) {
InetSocketAddress addr;
if (bindToAddr == null) {
addr = new InetSocketAddress(port);
} else {
addr = new InetSocketAddress(bindToAddr, port);
if (_log.shouldLog(Log.WARN))
_log.warn("Binding only to " + bindToAddr);
OrderedProperties props = new OrderedProperties();
props.setProperty(RouterAddress.PROP_HOST, bindTo);
props.setProperty(RouterAddress.PROP_PORT, Integer.toString(port));
int cost = getDefaultCost(false);
myAddress = new RouterAddress(STYLE, props, cost);
}
if (!_endpoints.isEmpty()) {
// If we are already bound to the new address, OR
// if the host is specified and we are bound to the wildcard on the same port,
// do nothing. Changing config from wildcard to a specified host will
// require a restart.
if (_endpoints.contains(addr) ||
(bindToAddr != null && _endpoints.contains(new InetSocketAddress(port)))) {
if (_log.shouldLog(Log.WARN))
_log.warn("Already listening on " + addr);
return null;
}
// FIXME support multiple binds
// FIXME just close and unregister
stopWaitAndRestart();
}
if (port < 1024)
_log.logAlways(Log.WARN, "Specified NTCP port is " + port + ", ports lower than 1024 not recommended");
ServerSocketChannel chan = ServerSocketChannel.open();
chan.configureBlocking(false);
chan.socket().bind(addr);
_endpoints.add(addr);
if (_log.shouldLog(Log.INFO))
_log.info("Listening on " + addr);
_pumper.register(chan);
} catch (IOException ioe) {
_log.error("Error listening", ioe);
myAddress = null;
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Outbound NTCP connections only - no listener configured");
}
if (_myAddress != null) {
RouterAddress rv = _myAddress.toRouterAddress();
if (rv != null)
replaceAddress(rv);
return rv;
} else {
return null;
}
return myAddress;
}
/**
* @return configured host or null. Must be one of our local interfaces.
* @since IPv6 moved from bindAddress()
*/
private String getFixedHost() {
boolean isFixed = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "true")
.toLowerCase(Locale.US).equals("false");
String fixedHost = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME);
if (isFixed && fixedHost != null) {
try {
String testAddr = InetAddress.getByName(fixedHost).getHostAddress();
// FIXME range of IPv6 addresses
if (Addresses.getAddresses().contains(testAddr))
return testAddr;
} catch (UnknownHostException uhe) {}
}
return null;
}
/**
* Caller must sync
* @since IPv6 moved from externalAddressReceived()
*/
private void stopWaitAndRestart() {
if (_log.shouldLog(Log.WARN))
_log.warn("Halting NTCP to change address");
stopListening();
// Wait for NTCP Pumper to stop so we don't end up with two...
while (isAlive()) {
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
}
if (_log.shouldLog(Log.WARN))
_log.warn("Restarting NTCP transport listening");
startIt();
}
/**
* Hook for NTCPConnection
*/
Reader getReader() { return _reader; }
/**
* Hook for NTCPConnection
*/
net.i2p.router.transport.ntcp.Writer getWriter() { return _writer; }
public String getStyle() { return STYLE; }
/**
* Hook for NTCPConnection
*/
EventPumper getPumper() { return _pumper; }
/**
@@ -638,33 +741,255 @@ public class NTCPTransport extends TransportImpl {
//private boolean bindAllInterfaces() { return true; }
/** caller must synch on this */
private void configureLocalAddress() {
RouterContext ctx = getContext();
if (ctx == null) {
System.err.println("NIO transport has no context?");
} else {
/**
* Generally returns null
* caller must synch on this
*/
private RouterAddress configureLocalAddress() {
// this generally returns null -- see javadoc
RouterAddress ra = CommSystemFacadeImpl.createNTCPAddress(ctx);
if (ra != null) {
NTCPAddress addr = new NTCPAddress(ra);
RouterAddress addr = createNTCPAddress();
if (addr != null) {
if (addr.getPort() <= 0) {
_myAddress = null;
addr = null;
if (_log.shouldLog(Log.ERROR))
_log.error("NTCP address is outbound only, since the NTCP configuration is invalid");
} else {
_myAddress = addr;
replaceAddress(ra);
if (_log.shouldLog(Log.INFO))
_log.info("NTCP address configured: " + _myAddress);
_log.info("NTCP address configured: " + addr);
}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("NTCP address is outbound only");
}
}
return addr;
}
/**
* This only creates an address if the hostname AND port are set in router.config,
* which should be rare.
* Otherwise, notifyReplaceAddress() below takes care of it.
* Note this is called both from above and from NTCPTransport.startListening()
*
* @since IPv6 moved from CSFI
*/
private RouterAddress createNTCPAddress() {
// Fixme doesn't check PROP_BIND_INTERFACE
String name = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME);
if ( (name == null) || (name.trim().length() <= 0) || ("null".equals(name)) )
return null;
int p = _context.getProperty(PROP_I2NP_NTCP_PORT, -1);
if (p <= 0 || p >= 64*1024)
return null;
OrderedProperties props = new OrderedProperties();
props.setProperty(RouterAddress.PROP_HOST, name);
props.setProperty(RouterAddress.PROP_PORT, Integer.toString(p));
int cost = getDefaultCost(false);
RouterAddress addr = new RouterAddress(STYLE, props, cost);
return addr;
}
private int getDefaultCost(boolean isIPv6) {
int rv = DEFAULT_COST;
if (isIPv6) {
TransportUtil.IPv6Config config = getIPv6Config();
if (config == IPV6_PREFERRED)
rv--;
else if (config == IPV6_NOT_PREFERRED)
rv++;
}
return rv;
}
/**
* UDP changed addresses, tell NTCP and (possibly) restart
*
* @since IPv6 moved from CSFI.notifyReplaceAddress()
*/
@Override
public void externalAddressReceived(AddressSource source, byte[] ip, int port) {
if (_log.shouldLog(Log.WARN))
_log.warn("Received address: " + Addresses.toString(ip, port) + " from: " + source);
if (ip != null && !isPubliclyRoutable(ip)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid address: " + Addresses.toString(ip, port) + " from: " + source);
return;
}
if (!isAlive()) {
if (source == SOURCE_INTERFACE || source == SOURCE_UPNP) {
try {
InetAddress ia = InetAddress.getByAddress(ip);
saveLocalAddress(ia);
} catch (UnknownHostException uhe) {}
} else if (source == SOURCE_CONFIG) {
// save for startListening()
_ssuPort = port;
}
return;
}
// ignore UPnP for now, get everything from SSU
if (source != SOURCE_SSU)
return;
externalAddressReceived(ip, port);
}
/**
* UDP changed addresses, tell NTCP and restart.
* Port may be set to indicate requested port even if ip is null.
*
* @param ip previously validated
* @since IPv6 moved from CSFI.notifyReplaceAddress()
*/
private synchronized void externalAddressReceived(byte[] ip, int port) {
// FIXME just take first IPv4 address for now
// FIXME if SSU set to hostname, NTCP will be set to IP
RouterAddress oldAddr = getCurrentAddress(false);
if (_log.shouldLog(Log.INFO))
_log.info("Changing NTCP Address? was " + oldAddr);
OrderedProperties newProps = new OrderedProperties();
int cost;
if (oldAddr == null) {
cost = getDefaultCost(ip != null && ip.length == 16);
} else {
cost = oldAddr.getCost();
newProps.putAll(oldAddr.getOptionsMap());
}
RouterAddress newAddr = new RouterAddress(STYLE, newProps, cost);
boolean changed = false;
// Auto Port Setting
// old behavior (<= 0.7.3): auto-port defaults to false, and true trumps explicit setting
// new behavior (>= 0.7.4): auto-port defaults to true, but explicit setting trumps auto
// TODO rewrite this to operate on ints instead of strings
String oport = newProps.getProperty(RouterAddress.PROP_PORT);
String nport = null;
String cport = _context.getProperty(PROP_I2NP_NTCP_PORT);
if (cport != null && cport.length() > 0) {
nport = cport;
} else if (_context.getBooleanPropertyDefaultTrue(PROP_I2NP_NTCP_AUTO_PORT)) {
// 0.9.6 change
// This wasn't quite right, as udpAddr is the EXTERNAL port and we really
// want NTCP to bind to the INTERNAL port the first time,
// because if they are different, the NAT is changing them, and
// it probably isn't mapping UDP and TCP the same.
if (port > 0)
// should always be true
nport = Integer.toString(port);
}
if (_log.shouldLog(Log.INFO))
_log.info("old: " + oport + " config: " + cport + " new: " + nport);
if (nport == null || nport.length() <= 0)
return;
// 0.9.6 change
// Don't have NTCP "chase" SSU's external port,
// as it may change, possibly frequently.
//if (oport == null || ! oport.equals(nport)) {
if (oport == null) {
newProps.setProperty(RouterAddress.PROP_PORT, nport);
changed = true;
}
// Auto IP Setting
// old behavior (<= 0.7.3): auto-ip defaults to false, and trumps configured hostname,
// and ignores reachability status - leading to
// "firewalled with inbound TCP enabled" warnings.
// new behavior (>= 0.7.4): auto-ip defaults to true, and explicit setting trumps auto,
// and only takes effect if reachability is OK.
// And new "always" setting ignores reachability status, like
// "true" was in 0.7.3
String ohost = newProps.getProperty(RouterAddress.PROP_HOST);
String enabled = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "true").toLowerCase(Locale.US);
String name = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME);
// hostname config trumps auto config
if (name != null && name.length() > 0)
enabled = "false";
// assume SSU is happy if the address is non-null
// TODO is this sufficient?
boolean ssuOK = ip != null;
if (_log.shouldLog(Log.INFO))
_log.info("old: " + ohost + " config: " + name + " auto: " + enabled + " ssuOK? " + ssuOK);
if (enabled.equals("always") ||
(Boolean.parseBoolean(enabled) && ssuOK)) {
// ip non-null
String nhost = Addresses.toString(ip);
if (_log.shouldLog(Log.INFO))
_log.info("old: " + ohost + " config: " + name + " new: " + nhost);
if (nhost == null || nhost.length() <= 0)
return;
if (ohost == null || ! ohost.equalsIgnoreCase(nhost)) {
newProps.setProperty(RouterAddress.PROP_HOST, nhost);
changed = true;
}
} else if (enabled.equals("false") &&
name != null && name.length() > 0 &&
!name.equals(ohost) &&
nport != null) {
// Host name is configured, and we have a port (either auto or configured)
// but we probably only get here if the port is auto,
// otherwise createNTCPAddress() would have done it already
if (_log.shouldLog(Log.INFO))
_log.info("old: " + ohost + " config: " + name + " new: " + name);
newProps.setProperty(RouterAddress.PROP_HOST, name);
changed = true;
} else if (ohost == null || ohost.length() <= 0) {
return;
} else if (Boolean.parseBoolean(enabled) && !ssuOK) {
// UDP transitioned to not-OK, turn off NTCP address
// This will commonly happen at startup if we were initially OK
// because UPnP was successful, but a subsequent SSU Peer Test determines
// we are still firewalled (SW firewall, bad UPnP indication, etc.)
if (_log.shouldLog(Log.INFO))
_log.info("old: " + ohost + " config: " + name + " new: null");
newAddr = null;
changed = true;
}
if (!changed) {
if (oldAddr != null) {
// change cost only?
int oldCost = oldAddr.getCost();
int newCost = getDefaultCost(ohost != null && ohost.contains(":"));
if (ADJUST_COST && !haveCapacity())
newCost += CONGESTION_COST_ADJUSTMENT;
if (newCost != oldCost) {
newAddr.setCost(newCost);
if (_log.shouldLog(Log.WARN))
_log.warn("Changing NTCP cost from " + oldCost + " to " + newCost);
// fall thru and republish
} else {
_log.info("No change to NTCP Address");
return;
}
} else {
_log.info("No change to NTCP Address");
return;
}
}
// stopListening stops the pumper, readers, and writers, so required even if
// oldAddr == null since startListening starts them all again
//
// really need to fix this so that we can change or create an inbound address
// without tearing down everything
// Especially on disabling the address, we shouldn't tear everything down.
//
//if (_log.shouldLog(Log.WARN))
// _log.warn("Halting NTCP to change address");
//stopListening();
// Wait for NTCP Pumper to stop so we don't end up with two...
//while (isAlive()) {
// try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
//}
restartListening(newAddr);
if (_log.shouldLog(Log.WARN))
_log.warn("Updating NTCP Address with " + newAddr);
return;
}
/**
* If we didn't used to be forwarded, and we have an address,
* and we are configured to use UPnP, update our RouterAddress
@@ -676,21 +1001,21 @@ public class NTCPTransport extends TransportImpl {
* NTCP address when it transitions to OK.
*/
@Override
public void forwardPortStatus(int port, int externalPort, boolean success, String reason) {
public void forwardPortStatus(byte[] ip, int port, int externalPort, boolean success, String reason) {
if (_log.shouldLog(Log.WARN)) {
if (success)
_log.warn("UPnP has opened the NTCP port: " + port);
_log.warn("UPnP has opened the NTCP port: " + port + " via " + Addresses.toString(ip, externalPort));
else
_log.warn("UPnP has failed to open the NTCP port: " + port + " reason: " + reason);
}
}
/**
* @return current port, else NTCP configured port, else -1 (but not UDP port if auto)
* @return current IPv4 port, else NTCP configured port, else -1 (but not UDP port if auto)
*/
@Override
public int getRequestedPort() {
NTCPAddress addr = _myAddress;
RouterAddress addr = getCurrentAddress(false);
if (addr != null) {
int port = addr.getPort();
if (port > 0)
@@ -700,7 +1025,7 @@ public class NTCPTransport extends TransportImpl {
// from here, so we do it in TransportManager.
// if (Boolean.valueOf(_context.getProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_AUTO_PORT)).booleanValue())
// return foo;
return _context.getProperty(CommSystemFacadeImpl.PROP_I2NP_NTCP_PORT, -1);
return _context.getProperty(PROP_I2NP_NTCP_PORT, -1);
}
/**
@@ -714,7 +1039,8 @@ public class NTCPTransport extends TransportImpl {
*/
@Override
public short getReachabilityStatus() {
if (isAlive() && _myAddress != null) {
// If we have an IPv4 address
if (isAlive() && getCurrentAddress(false) != null) {
for (NTCPConnection con : _conByIdent.values()) {
if (con.isInbound())
return CommSystemFacade.STATUS_OK;
@@ -733,18 +1059,17 @@ public class NTCPTransport extends TransportImpl {
_writer.stopWriting();
_reader.stopReading();
_finisher.stop();
Map cons = null;
List<NTCPConnection> cons;
synchronized (_conLock) {
cons = new HashMap(_conByIdent);
cons = new ArrayList(_conByIdent.values());
_conByIdent.clear();
}
for (Iterator iter = cons.values().iterator(); iter.hasNext(); ) {
NTCPConnection con = (NTCPConnection)iter.next();
for (NTCPConnection con : cons) {
con.close();
}
NTCPConnection.releaseResources();
// will this work?
replaceAddress(null);
_endpoints.clear();
}
public static final String STYLE = "NTCP";
@@ -753,7 +1078,7 @@ public class NTCPTransport extends TransportImpl {
@Override
public void renderStatusHTML(java.io.Writer out, String urlBase, int sortFlags) throws IOException {
TreeSet peers = new TreeSet(getComparator(sortFlags));
TreeSet<NTCPConnection> peers = new TreeSet(getComparator(sortFlags));
peers.addAll(_conByIdent.values());
long offsetTotal = 0;
@@ -771,6 +1096,7 @@ public class NTCPTransport extends TransportImpl {
"<table>\n" +
"<tr><th><a href=\"#def.peer\">").append(_("Peer")).append("</a></th>" +
"<th>").append(_("Dir")).append("</th>" +
"<th>").append(_("IPv6")).append("</th>" +
"<th align=\"right\"><a href=\"#def.idle\">").append(_("Idle")).append("</a></th>" +
"<th align=\"right\"><a href=\"#def.rate\">").append(_("In/Out")).append("</a></th>" +
"<th align=\"right\"><a href=\"#def.up\">").append(_("Up")).append("</a></th>" +
@@ -783,8 +1109,7 @@ public class NTCPTransport extends TransportImpl {
" </tr>\n");
out.write(buf.toString());
buf.setLength(0);
for (Iterator iter = peers.iterator(); iter.hasNext(); ) {
NTCPConnection con = (NTCPConnection)iter.next();
for (NTCPConnection con : peers) {
buf.append("<tr><td class=\"cells\" align=\"left\" nowrap>");
buf.append(_context.commSystem().renderPeerHTML(con.getRemotePeer().calculateHash()));
//byte[] ip = getIP(con.getRemotePeer().calculateHash());
@@ -795,6 +1120,11 @@ public class NTCPTransport extends TransportImpl {
buf.append("<img src=\"/themes/console/images/inbound.png\" alt=\"Inbound\" title=\"").append(_("Inbound")).append("\"/>");
else
buf.append("<img src=\"/themes/console/images/outbound.png\" alt=\"Outbound\" title=\"").append(_("Outbound")).append("\"/>");
buf.append("</td><td class=\"cells\" align=\"center\">");
if (con.isIPv6())
buf.append("&#x2713;");
else
buf.append("&nbsp;");
buf.append("</td><td class=\"cells\" align=\"right\">");
buf.append(DataHelper.formatDuration2(con.getTimeSinceReceive()));
buf.append(THINSP).append(DataHelper.formatDuration2(con.getTimeSinceSend()));
@@ -843,7 +1173,8 @@ public class NTCPTransport extends TransportImpl {
if (!peers.isEmpty()) {
// buf.append("<tr> <td colspan=\"11\"><hr></td></tr>\n");
buf.append("<tr class=\"tablefooter\"><td align=\"center\"><b>").append(peers.size()).append(' ').append(_("peers")).append("</b></td><td>&nbsp;</td><td>&nbsp;");
buf.append("<tr class=\"tablefooter\"><td colspan=\"4\" align=\"left\"><b>").append(_("SUMMARY"))
.append("</b>");
buf.append("</td><td align=\"center\"><b>").append(formatRate(bpsRecv/1024)).append(THINSP).append(formatRate(bpsSend/1024)).append("</b>");
buf.append("</td><td align=\"center\"><b>").append(DataHelper.formatDuration2(totalUptime/peers.size()));
buf.append("</b></td><td align=\"center\"><b>").append(DataHelper.formatDuration2(offsetTotal*1000/peers.size()));
@@ -62,7 +62,7 @@ class ACKSender implements Runnable {
public synchronized void shutdown() {
_alive = false;
PeerState poison = new PeerState(_context, _transport, null, 0, null, false);
PeerState poison = new PeerState(_context, _transport, new byte[4], 0, null, false);
poison.setTheyRelayToUsAs(POISON_PS);
_peersToACK.offer(poison);
for (int i = 1; i <= 5 && !_peersToACK.isEmpty(); i++) {
@@ -249,7 +249,7 @@ class EstablishmentManager {
maybeTo = new RemoteHostId(remAddr.getAddress(), port);
if ((!_transport.isValid(maybeTo.getIP())) ||
Arrays.equals(maybeTo.getIP(), _transport.getExternalIP())) {
(Arrays.equals(maybeTo.getIP(), _transport.getExternalIP()) && !_transport.allowLocal())) {
_transport.failed(msg, "Remote peer's IP isn't valid");
_transport.markUnreachable(toHash);
//_context.banlist().banlistRouter(msg.getTarget().getIdentity().calculateHash(), "Invalid SSU address", UDPTransport.STYLE);
@@ -447,7 +447,9 @@ class EstablishmentManager {
}
if (!_transport.allowConnection())
return; // drop the packet
state = new InboundEstablishState(_context, from.getIP(), from.getPort(), _transport.getExternalPort(),
byte[] fromIP = from.getIP();
state = new InboundEstablishState(_context, fromIP, from.getPort(),
_transport.getExternalPort(fromIP.length == 16),
_transport.getDHBuilder());
state.receiveSessionRequest(reader.getSessionRequestReader());
InboundEstablishState oldState = _inboundStates.putIfAbsent(from, state);
@@ -459,8 +461,10 @@ class EstablishmentManager {
if (isNew) {
// Don't offer to relay to privileged ports.
// Only offer for an IPv4 session.
// TODO if already we have their RI, only offer if they need it (no 'C' cap)
if (_transport.canIntroduce() && state.getSentPort() >= 1024) {
if (_transport.canIntroduce() && state.getSentPort() >= 1024 &&
state.getSentIP().length == 4) {
// ensure > 0
long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE);
state.setSentRelayTag(tag);
@@ -673,7 +677,8 @@ class EstablishmentManager {
String smtu = addr.getOption(UDPAddress.PROP_MTU);
if (smtu != null) {
try {
int mtu = MTU.rectify(Integer.parseInt(smtu));
boolean isIPv6 = state.getSentIP().length == 16;
int mtu = MTU.rectify(isIPv6, Integer.parseInt(smtu));
peer.setHisMTU(mtu);
} catch (NumberFormatException nfe) {}
}
@@ -833,7 +838,9 @@ class EstablishmentManager {
_inboundStates.remove(state.getRemoteHostId());
return;
}
_transport.send(_builder.buildSessionCreatedPacket(state, _transport.getExternalPort(), _transport.getIntroKey()));
_transport.send(_builder.buildSessionCreatedPacket(state,
_transport.getExternalPort(state.getSentIP().length == 16),
_transport.getIntroKey()));
state.createdPacketSent();
}
@@ -884,6 +891,9 @@ class EstablishmentManager {
state.introSent();
}
/**
* We are Alice, we sent a RelayRequest to Bob and got a response back.
*/
void receiveRelayResponse(RemoteHostId bob, UDPPacketReader reader) {
long nonce = reader.getRelayResponseReader().readNonce();
OutboundEstablishState state = _liveIntroductions.remove(Long.valueOf(nonce));
@@ -893,6 +903,7 @@ class EstablishmentManager {
return; // already established
}
// Note that we ignore the Alice (us) IP/Port in the RelayResponse
int sz = reader.getRelayResponseReader().readCharlieIPSize();
byte ip[] = new byte[sz];
reader.getRelayResponseReader().readCharlieIP(ip, 0);
@@ -940,15 +951,17 @@ class EstablishmentManager {
}
/**
* Are IP and port valid?
* Are IP and port valid? This is only for checking the relay response.
* Reject all IPv6, for now, even if we are configured for it.
* Refuse anybody in the same /16
* @since 0.9.3
*/
private boolean isValid(byte[] ip, int port) {
return port >= 1024 &&
return port >= UDPTransport.MIN_PEER_PORT &&
port <= 65535 &&
ip != null && ip.length == 4 &&
_transport.isValid(ip) &&
(!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) &&
(!_transport.isTooClose(ip)) &&
(!_context.blocklist().isBlocklisted(ip));
}
@@ -3,6 +3,7 @@ package net.i2p.router.transport.udp;
import net.i2p.util.ObjectCounter;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
import net.i2p.util.SipHash;
/**
* Count IPs
@@ -21,12 +22,16 @@ class IPThrottler {
/**
* Increments before checking
* @return true if ip.length != 4
*/
public boolean shouldThrottle(byte[] ip) {
if (ip.length != 4)
return true;
return _counter.increment(toInt(ip)) > _max;
// for IPv4 we simply use the IP;
// for IPv6 we use a secure hash as an attacker could select the lower bytes
Integer key;
if (ip.length == 4)
key = toInt(ip);
else
key = Integer.valueOf(SipHash.hashCode(ip));
return _counter.increment(key) > _max;
}
private static Integer toInt(byte ip[]) {
@@ -66,6 +66,7 @@ class InboundMessageFragments /*implements UDPTransport.PartialACKSource */{
_ackSender.shutdown();
_messageReceiver.shutdown();
}
public boolean isAlive() { return _alive; }
/**
@@ -23,6 +23,52 @@ import net.i2p.util.Log;
/**
* Keep track of inbound and outbound introductions.
*
* IPv6 info: Alice-Bob communication may be via IPv4 or IPv6.
* Bob-Charlie communication must be via established IPv4 session as that's the only way
* that Bob knows Charlie's IPv4 address to give it to Alice.
* Alice-Charlie communication is via IPv4.
* If Alice-Bob is over IPv6, Alice must include her IPv4 address in
* the RelayRequest message.
*
* From udp.html on the website:
<p>Indirect session establishment by means of a third party introduction
is necessary for efficient NAT traversal. Charlie, a router behind a
NAT or firewall which does not allow unsolicited inbound UDP packets,
first contacts a few peers, choosing some to serve as introducers. Each
of these peers (Bob, Bill, Betty, etc) provide Charlie with an introduction
tag - a 4 byte random number - which he then makes available to the public
as methods of contacting him. Alice, a router who has Charlie's published
contact methods, first sends a RelayRequest packet to one or more of the
introducers, asking each to introduce her to Charlie (offering the
introduction tag to identify Charlie). Bob then forwards a RelayIntro
packet to Charlie including Alice's public IP and port number, then sends
Alice back a RelayResponse packet containing Charlie's public IP and port
number. When Charlie receives the RelayIntro packet, he sends off a small
random packet to Alice's IP and port (poking a hole in his NAT/firewall),
and when Alice receives Bob's RelayResponse packet, she begins a new
full direction session establishment with the specified IP and port.</p>
<p>
Alice first connects to introducer Bob, who relays the request to Charlie.
</p>
<pre>
Alice Bob Charlie
RelayRequest ----------------------&gt;
&lt;-------------- RelayResponse RelayIntro -----------&gt;
&lt;-------------------------------------------- HolePunch (data ignored)
SessionRequest --------------------------------------------&gt;
&lt;-------------------------------------------- SessionCreated
SessionConfirmed ------------------------------------------&gt;
&lt;-------------------------------------------- DeliveryStatusMessage
&lt;-------------------------------------------- DatabaseStoreMessage
DatabaseStoreMessage --------------------------------------&gt;
Data &lt;--------------------------------------------------&gt; Data
</pre>
<p>
After the hole punch, the session is established between Alice and Charlie as in a direct establishment.
</p>
*/
class IntroductionManager {
private final RouterContext _context;
@@ -77,6 +123,9 @@ class IntroductionManager {
// let's not use an introducer on a privileged port, sounds like trouble
if (peer.getRemotePort() < 1024)
return;
// Only allow relay as Bob or Charlie if the Bob-Charlie session is IPv4
if (peer.getRemoteIP().length != 4)
return;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Adding peer " + peer.getRemoteHostId() + ", weRelayToThemAs "
+ peer.getWeRelayToThemAs() + ", theyRelayToUsAs " + peer.getTheyRelayToUsAs());
@@ -136,6 +185,8 @@ class IntroductionManager {
_log.info("Picked peer has no local routerInfo: " + cur);
continue;
}
// FIXME we can include all his addresses including IPv6 even if we don't support IPv6 (isValid() is false)
// but requires RelayRequest support, see below
RouterAddress ra = _transport.getTargetAddress(ri);
if (ra == null) {
if (_log.shouldLog(Log.INFO))
@@ -159,6 +210,8 @@ class IntroductionManager {
_log.info("Peer is idle too long: " + cur);
continue;
}
// FIXME we can include all his addresses including IPv6 even if we don't support IPv6 (isValid() is false)
// but requires RelayRequest support, see below
byte[] ip = cur.getRemoteIP();
int port = cur.getRemotePort();
if (!isValid(ip, port))
@@ -331,6 +384,9 @@ class IntroductionManager {
// ip/port inside message should be 0:0, as it's unimplemented on send -
// see PacketBuilder.buildRelayRequest()
// and we don't read it here.
// FIXME implement for getting Alice's IPv4 in RelayRequest sent over IPv6?
// or is that just too easy to spoof?
if (!isValid(alice.getIP(), alice.getPort()) || ipSize != 0 || port != 0) {
if (_log.shouldLog(Log.WARN)) {
byte ip[] = new byte[ipSize];
@@ -368,14 +424,16 @@ class IntroductionManager {
/**
* Are IP and port valid?
* Reject all IPv6, for now, even if we are configured for it.
* Refuse anybody in the same /16
* @since 0.9.3
*/
private boolean isValid(byte[] ip, int port) {
return port >= 1024 &&
return port >= UDPTransport.MIN_PEER_PORT &&
port <= 65535 &&
ip != null && ip.length == 4 &&
_transport.isValid(ip) &&
(!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) &&
(!_transport.isTooClose(ip)) &&
(!_context.blocklist().isBlocklisted(ip));
}
}
@@ -1,6 +1,7 @@
package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.net.Inet6Address;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
@@ -41,7 +42,8 @@ abstract class MTU {
try {
// testing
//return ifc.getMTU();
return rectify(ifc.getMTU());
boolean isIPv6 = addr instanceof Inet6Address;
return rectify(isIPv6, ifc.getMTU());
} catch (SocketException se) {
// ignore
} catch (Throwable t) {
@@ -58,11 +60,16 @@ abstract class MTU {
/**
* @return min of PeerState.MIN_MTU, max of PeerState.LARGE_MTU,
* rectified so rv % 16 == 12
* rectified so rv % 16 == 12 (IPv4)
* or rv % 16 == 0 (IPv6)
*/
public static int rectify(int mtu) {
public static int rectify(boolean isIPv6, int mtu) {
int rv = mtu;
int mod = rv % 16;
if (isIPv6) {
rv -= mod;
return Math.max(PeerState.MIN_IPV6_MTU, Math.min(PeerState.MAX_IPV6_MTU, rv));
}
if (mod > 12)
rv -= mod - 12;
else if (mod < 12)
@@ -72,11 +79,29 @@ abstract class MTU {
/****
public static void main(String args[]) {
System.out.println("Cmd line interfaces:");
for (int i = 0; i < args.length; i++) {
try {
InetAddress test = InetAddress.getByName(args[i]);
System.out.println("MTU of " + args[i] + " is " + getMTU(test));
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("All interfaces:");
try {
InetAddress test = InetAddress.getByName(args[0]);
System.out.println("MTU is " + getMTU(test));
} catch (Exception e) {
e.printStackTrace();
Enumeration<NetworkInterface> ifcs = NetworkInterface.getNetworkInterfaces();
if (ifcs != null) {
while (ifcs.hasMoreElements()) {
NetworkInterface ifc = ifcs.nextElement();
for(Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) {
InetAddress addr = addrs.nextElement();
System.out.println("MTU of " + addr.getHostAddress() + " is " + getMTU(addr));
}
}
}
} catch (SocketException se) {
System.out.println("no interfaces");
}
}
****/
@@ -309,8 +309,8 @@ class OutboundMessageFragments {
// use max of 1 second so finishMessages() and/or PeerState.finishMessages()
// gets called regularly
int toWait = Math.min(Math.max(nextSendDelay, 10), MAX_WAIT);
if (_log.shouldLog(Log.DEBUG))
_log.debug("wait for " + toWait);
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("wait for " + toWait);
// wait.. or somethin'
synchronized (_activePeers) {
try {
@@ -141,6 +141,10 @@ class PacketBuilder {
/** 74 */
public static final int MIN_DATA_PACKET_OVERHEAD = IP_HEADER_SIZE + UDP_HEADER_SIZE + DATA_HEADER_SIZE;
public static final int IPV6_HEADER_SIZE = 40;
/** 94 */
public static final int MIN_IPV6_DATA_PACKET_OVERHEAD = IPV6_HEADER_SIZE + UDP_HEADER_SIZE + DATA_HEADER_SIZE;
/** one byte field */
public static final int ABSOLUTE_MAX_ACKS = 255;
@@ -158,6 +162,9 @@ class PacketBuilder {
private static final String PROP_PADDING = "i2np.udp.padding";
/**
* @param transport may be null for unit testing only
*/
public PacketBuilder(I2PAppContext ctx, UDPTransport transport) {
_context = ctx;
_transport = transport;
@@ -244,7 +251,15 @@ class PacketBuilder {
}
int currentMTU = peer.getMTU();
int availableForAcks = currentMTU - MIN_DATA_PACKET_OVERHEAD - dataSize;
int availableForAcks = currentMTU - dataSize;
int ipHeaderSize;
if (peer.getRemoteIP().length == 4) {
availableForAcks -= MIN_DATA_PACKET_OVERHEAD;
ipHeaderSize = IP_HEADER_SIZE;
} else {
availableForAcks -= MIN_IPV6_DATA_PACKET_OVERHEAD;
ipHeaderSize = IPV6_HEADER_SIZE;
}
int availableForExplicitAcks = availableForAcks;
// ok, now for the body...
@@ -400,16 +415,16 @@ class PacketBuilder {
setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
if (_log.shouldLog(Log.INFO)) {
msg.append(" pkt size ").append(off + (IP_HEADER_SIZE + UDP_HEADER_SIZE));
msg.append(" pkt size ").append(off + (ipHeaderSize + UDP_HEADER_SIZE));
_log.info(msg.toString());
}
// the packet could have been built before the current mtu got lowered, so
// compare to LARGE_MTU
if (off + (IP_HEADER_SIZE + UDP_HEADER_SIZE) > PeerState.LARGE_MTU) {
if (off + (ipHeaderSize + UDP_HEADER_SIZE) > PeerState.LARGE_MTU) {
_log.error("Size is " + off + " for " + packet +
" fragment " + fragment +
" data size " + dataSize +
" pkt size " + (off + (IP_HEADER_SIZE + UDP_HEADER_SIZE)) +
" pkt size " + (off + (ipHeaderSize + UDP_HEADER_SIZE)) +
" MTU " + currentMTU +
' ' + availableForAcks + " for all acks " +
availableForExplicitAcks + " for full acks " +
@@ -561,7 +576,7 @@ class PacketBuilder {
state.prepareSessionCreated();
byte sentIP[] = state.getSentIP();
if ( (sentIP == null) || (sentIP.length <= 0) || ( (_transport != null) && (!_transport.isValid(sentIP)) ) ) {
if ( (sentIP == null) || (sentIP.length <= 0) || (!_transport.isValid(sentIP))) {
if (_log.shouldLog(Log.ERROR))
_log.error("How did our sent IP become invalid? " + state);
state.fail();
@@ -642,7 +657,7 @@ class PacketBuilder {
int off = HEADER_SIZE;
byte toIP[] = state.getSentIP();
if ( (_transport !=null) && (!_transport.isValid(toIP)) ) {
if (!_transport.isValid(toIP)) {
packet.release();
return null;
}
@@ -1051,6 +1066,7 @@ class PacketBuilder {
// specify these if we know what our external receive ip/port is and if its different
// from what bob is going to think
// FIXME IPv4 addr must be specified when sent over IPv6
private byte[] getOurExplicitIP() { return null; }
private int getOurExplicitPort() { return 0; }
@@ -1070,8 +1086,10 @@ class PacketBuilder {
// let's not use an introducer on a privileged port, sounds like trouble
if (ikey == null || iport < 1024 || iport > 65535 ||
iaddr == null || tag <= 0 ||
// must be IPv4 for now as we don't send Alice IP/port, see below
iaddr.getAddress().length != 4 ||
(!_transport.isValid(iaddr.getAddress())) ||
Arrays.equals(iaddr.getAddress(), _transport.getExternalIP())) {
(Arrays.equals(iaddr.getAddress(), _transport.getExternalIP()) && !_transport.allowLocal())) {
if (_log.shouldLog(_log.WARN))
_log.warn("Cannot build a relay request to " + state.getRemoteIdentity().calculateHash()
+ ", as their UDP address is invalid: addr=" + addr + " index=" + i);
@@ -1083,6 +1101,11 @@ class PacketBuilder {
return rv;
}
/**
* TODO Alice IP/port in packet will always be null/0, must be fixed to
* send a RelayRequest over IPv6
*
*/
private UDPPacket buildRelayRequest(InetAddress introHost, int introPort, byte introKey[],
long introTag, SessionKey ourIntroKey, long introNonce, boolean encrypt) {
UDPPacket packet = buildPacketHeader(PEER_RELAY_REQUEST_FLAG_BYTE);
@@ -1090,6 +1113,7 @@ class PacketBuilder {
byte data[] = pkt.getData();
int off = HEADER_SIZE;
// FIXME must specify these if request is going over IPv6
byte ourIP[] = getOurExplicitIP();
int ourPort = getOurExplicitPort();
@@ -1211,6 +1235,7 @@ class PacketBuilder {
DataHelper.toLong(data, off, 2, charlie.getRemotePort());
off += 2;
// Alice IP/Port currently ignored on receive - see UDPPacketReader
byte aliceIP[] = alice.getIP();
DataHelper.toLong(data, off, 1, aliceIP.length);
off++;
@@ -1233,7 +1258,7 @@ class PacketBuilder {
}
/**
* Sends an empty unauthenticated packet for hole punching.
* Creates an empty unauthenticated packet for hole punching.
* Parameters must be validated previously.
*/
public UDPPacket buildHolePunch(InetAddress to, int port) {
@@ -1250,6 +1275,23 @@ class PacketBuilder {
return packet;
}
/**
* TESTING ONLY.
* Creates an arbitrary packet for unit testing.
* Null transport in constructor OK.
*
* @param type 0-15
* @since IPv6
*/
public UDPPacket buildPacket(byte[] data, InetAddress to, int port) {
UDPPacket packet = UDPPacket.acquire(_context, false);
byte d[] = packet.getPacket().getData();
System.arraycopy(data, 0, d, 0, data.length);
packet.getPacket().setLength(data.length);
setTo(packet, to, port);
return packet;
}
/**
* Create a new packet and add the flag byte and the time stamp.
* Caller should add data starting at HEADER_SIZE.
@@ -3,9 +3,11 @@ package net.i2p.router.transport.udp;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.util.CoDelBlockingQueue;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PThread;
import net.i2p.util.LHMCache;
@@ -26,7 +28,6 @@ class PacketHandler {
private final RouterContext _context;
private final Log _log;
private final UDPTransport _transport;
private final UDPEndpoint _endpoint;
private final EstablishmentManager _establisher;
private final InboundMessageFragments _inbound;
private final PeerTestManager _testManager;
@@ -34,19 +35,22 @@ class PacketHandler {
private volatile boolean _keepReading;
private final Handler[] _handlers;
private final Map<RemoteHostId, Object> _failCache;
private final BlockingQueue<UDPPacket> _inboundQueue;
private static final Object DUMMY = new Object();
private static final int TYPE_POISON = -99999;
private static final int MIN_QUEUE_SIZE = 16;
private static final int MAX_QUEUE_SIZE = 192;
private static final int MIN_NUM_HANDLERS = 1; // unless < 32MB
private static final int MAX_NUM_HANDLERS = 1;
/** let packets be up to 30s slow */
private static final long GRACE_PERIOD = Router.CLOCK_FUDGE_FACTOR + 30*1000;
PacketHandler(RouterContext ctx, UDPTransport transport, UDPEndpoint endpoint, EstablishmentManager establisher,
PacketHandler(RouterContext ctx, UDPTransport transport, EstablishmentManager establisher,
InboundMessageFragments inbound, PeerTestManager testManager, IntroductionManager introManager) {
_context = ctx;
_log = ctx.logManager().getLog(PacketHandler.class);
_transport = transport;
_endpoint = endpoint;
_establisher = establisher;
_inbound = inbound;
_testManager = testManager;
@@ -56,6 +60,8 @@ class PacketHandler {
long maxMemory = Runtime.getRuntime().maxMemory();
if (maxMemory == Long.MAX_VALUE)
maxMemory = 96*1024*1024l;
int qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (2*1024*1024)));
_inboundQueue = new CoDelBlockingQueue(ctx, "UDP-Receiver", qsize);
int num_handlers;
if (maxMemory < 32*1024*1024)
num_handlers = 1;
@@ -107,6 +113,7 @@ class PacketHandler {
public synchronized void shutdown() {
_keepReading = false;
stopQueue();
}
String getHandlerStatus() {
@@ -119,9 +126,55 @@ class PacketHandler {
return rv.toString();
}
/** @since 0.8.8 */
int getHandlerCount() {
return _handlers.length;
/**
* Blocking call to retrieve the next inbound packet, or null if we have
* shut down.
*
* @since IPv6 moved from UDPReceiver
*/
public void queueReceived(UDPPacket packet) throws InterruptedException {
_inboundQueue.put(packet);
}
/**
* Blocking for a while
*
* @since IPv6 moved from UDPReceiver
*/
private void stopQueue() {
_inboundQueue.clear();
for (int i = 0; i < _handlers.length; i++) {
UDPPacket poison = UDPPacket.acquire(_context, false);
poison.setMessageType(TYPE_POISON);
_inboundQueue.offer(poison);
}
for (int i = 1; i <= 5 && !_inboundQueue.isEmpty(); i++) {
try {
Thread.sleep(i * 50);
} catch (InterruptedException ie) {}
}
_inboundQueue.clear();
}
/**
* Blocking call to retrieve the next inbound packet, or null if we have
* shut down.
*
* @since IPv6 moved from UDPReceiver
*/
public UDPPacket receiveNext() {
UDPPacket rv = null;
//int remaining = 0;
while (_keepReading && rv == null) {
try {
rv = _inboundQueue.take();
} catch (InterruptedException ie) {}
if (rv != null && rv.getMessageType() == TYPE_POISON)
return null;
}
//_context.statManager().addRateData("udp.receiveRemaining", remaining, 0);
return rv;
}
/** the packet is from a peer we are establishing an outbound con to, but failed validation, so fallback */
@@ -144,7 +197,7 @@ class PacketHandler {
_state = 1;
while (_keepReading) {
_state = 2;
UDPPacket packet = _endpoint.receive();
UDPPacket packet = receiveNext();
_state = 3;
if (packet == null) break; // keepReading is probably false, or bind failed...
@@ -1,26 +1,29 @@
package net.i2p.router.transport.udp;
import java.util.List;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Blocking thread to grab new packets off the outbound fragment
* pool and toss 'em onto the outbound packet queue
* pool and toss 'em onto the outbound packet queues.
*
* Here we select which UDPEndpoint/UDPSender to send it out.
*/
class PacketPusher implements Runnable {
// private RouterContext _context;
private final Log _log;
private final OutboundMessageFragments _fragments;
private final UDPSender _sender;
private final List<UDPEndpoint> _endpoints;
private volatile boolean _alive;
public PacketPusher(RouterContext ctx, OutboundMessageFragments fragments, UDPSender sender) {
public PacketPusher(RouterContext ctx, OutboundMessageFragments fragments, List<UDPEndpoint> endpoints) {
// _context = ctx;
_log = ctx.logManager().getLog(PacketPusher.class);
_fragments = fragments;
_sender = sender;
_endpoints = endpoints;
}
public synchronized void startup() {
@@ -38,8 +41,7 @@ class PacketPusher implements Runnable {
if (packets != null) {
for (int i = 0; i < packets.length; i++) {
if (packets[i] != null) // null for ACKed fragments
// BLOCKING if queue is full
_sender.add(packets[i]);
send(packets[i]);
}
}
} catch (Exception e) {
@@ -47,4 +49,38 @@ class PacketPusher implements Runnable {
}
}
}
/**
* This sends it directly out, bypassing OutboundMessageFragments
* and the PacketPusher. The only queueing is for the bandwidth limiter.
* BLOCKING if OB queue is full.
*
* @param packet non-null
* @since IPv6
*/
public void send(UDPPacket packet) {
boolean isIPv4 = packet.getPacket().getAddress().getAddress().length == 4;
for (int j = 0; j < _endpoints.size(); j++) {
// Find the best endpoint (socket) to send this out.
// TODO if we have multiple IPv4, or multiple IPv6 endpoints,
// we have to track which one we're using in the PeerState and
// somehow set that in the UDPPacket so we're consistent
UDPEndpoint ep;
try {
ep = _endpoints.get(j);
} catch (IndexOutOfBoundsException ioobe) {
// whups, list changed
break;
}
if ((isIPv4 && ep.isIPv4()) ||
((!isIPv4) && ep.isIPv6())) {
// BLOCKING if queue is full
ep.getSender().add(packet);
return;
}
}
// not handled
_log.error("No endpoint to send " + packet);
packet.release();
}
}
@@ -267,6 +267,12 @@ class PeerState {
* and so PacketBuilder.buildPacket() works correctly.
*/
public static final int MIN_MTU = 620;
/**
* IPv6/UDP header is 48 bytes, so we want MTU % 16 == 0.
*/
public static final int MIN_IPV6_MTU = 1280;
public static final int MAX_IPV6_MTU = 1472; // TODO 1488
private static final int DEFAULT_MTU = MIN_MTU;
/*
@@ -315,9 +321,15 @@ class PeerState {
_receivePeriodBegin = now;
_lastCongestionOccurred = -1;
_remotePort = remotePort;
_mtu = DEFAULT_MTU;
_mtuReceive = DEFAULT_MTU;
_largeMTU = transport.getMTU();
if (remoteIP.length == 4) {
_mtu = DEFAULT_MTU;
_mtuReceive = DEFAULT_MTU;
_largeMTU = transport.getMTU(false);
} else {
_mtu = MIN_IPV6_MTU;
_mtuReceive = MIN_IPV6_MTU;
_largeMTU = transport.getMTU(true);
}
//_mtuLastChecked = -1;
_lastACKSend = -1;
_rto = INIT_RTO;
@@ -686,6 +698,12 @@ class PeerState {
public int getConcurrentSendWindow() { return _concurrentMessagesAllowed; }
public int getConsecutiveSendRejections() { return _consecutiveRejections; }
public boolean isInbound() { return _isInbound; }
/** @since IPv6 */
public boolean isIPv6() {
return _remoteIP.length == 16;
}
public long getIntroducerTime() { return _lastIntroducerTime; }
public void setIntroducerTime() { _lastIntroducerTime = _context.clock().now(); }
@@ -1145,12 +1163,12 @@ class PeerState {
_context.statManager().addRateData("udp.mtuIncrease", _mtuIncreases);
}
} else if (!wantLarge && _mtu == _largeMTU) {
_mtu = MIN_MTU;
_mtu = _remoteIP.length == 4 ? MIN_MTU : MIN_IPV6_MTU;
_mtuDecreases++;
_context.statManager().addRateData("udp.mtuDecrease", _mtuDecreases);
}
} else {
_mtu = DEFAULT_MTU;
_mtu = _remoteIP.length == 4 ? DEFAULT_MTU : MIN_IPV6_MTU;
}
}
@@ -1158,7 +1176,8 @@ class PeerState {
* @since 0.9.2
*/
public synchronized void setHisMTU(int mtu) {
if (mtu <= MIN_MTU || mtu >= _largeMTU)
if (mtu <= MIN_MTU || mtu >= _largeMTU ||
(_remoteIP.length == 16 && mtu <= MIN_IPV6_MTU))
return;
_largeMTU = mtu;
if (mtu < _mtu)
@@ -1225,17 +1244,27 @@ class PeerState {
/** 60 */
private static final int OVERHEAD_SIZE = PacketBuilder.IP_HEADER_SIZE + PacketBuilder.UDP_HEADER_SIZE +
UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
/** 80 */
private static final int IPV6_OVERHEAD_SIZE = PacketBuilder.IPV6_HEADER_SIZE + PacketBuilder.UDP_HEADER_SIZE +
UDPPacket.MAC_SIZE + UDPPacket.IV_SIZE;
/**
* @param size not including IP header, UDP header, MAC or IV
*/
public void packetReceived(int size) {
_packetsReceived++;
size += OVERHEAD_SIZE;
if (size <= MIN_MTU) {
int minMTU;
if (_remoteIP.length == 4) {
size += OVERHEAD_SIZE;
minMTU = MIN_MTU;
} else {
size += IPV6_OVERHEAD_SIZE;
minMTU = MIN_IPV6_MTU;
}
if (size <= minMTU) {
_consecutiveSmall++;
if (_consecutiveSmall >= MTU_RCV_DISPLAY_THRESHOLD)
_mtuReceive = MIN_MTU;
_mtuReceive = minMTU;
} else {
_consecutiveSmall = 0;
if (size > _mtuReceive)
@@ -1289,7 +1318,7 @@ class PeerState {
private int countMaxACKData() {
return Math.min(PacketBuilder.ABSOLUTE_MAX_ACKS * 4,
_mtu
- PacketBuilder.IP_HEADER_SIZE
- (_remoteIP.length == 4 ? PacketBuilder.IP_HEADER_SIZE : PacketBuilder.IPV6_HEADER_SIZE)
- PacketBuilder.UDP_HEADER_SIZE
- UDPPacket.IV_SIZE
- UDPPacket.MAC_SIZE
@@ -1630,11 +1659,14 @@ class PeerState {
/**
* how much payload data can we shove in there?
* @return MTU - 87, i.e. 533 or 1397
* @return MTU - 87, i.e. 533 or 1397 (IPv4), MTU - 107 (IPv6)
*/
private static final int fragmentSize(int mtu) {
// 46 + 20 + 8 + 13 = 74 + 13 = 87
return mtu - (PacketBuilder.MIN_DATA_PACKET_OVERHEAD + MIN_ACK_SIZE);
private int fragmentSize() {
// 46 + 20 + 8 + 13 = 74 + 13 = 87 (IPv4)
// 46 + 40 + 8 + 13 = 74 + 13 = 107 (IPv6)
return _mtu -
(_remoteIP.length == 4 ? PacketBuilder.MIN_DATA_PACKET_OVERHEAD : PacketBuilder.MIN_IPV6_DATA_PACKET_OVERHEAD) -
MIN_ACK_SIZE;
}
private enum ShouldSend { YES, NO, NO_BW };
@@ -1647,7 +1679,7 @@ class PeerState {
long now = _context.clock().now();
if (state.getNextSendTime() <= now) {
if (!state.isFragmented()) {
state.fragment(fragmentSize(_mtu));
state.fragment(fragmentSize());
if (state.getMessage() != null)
state.getMessage().timestamp("fragment into " + state.getFragmentCount());
@@ -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) {
@@ -173,7 +182,7 @@ class PeerTestManager {
_log.warn("We are already running a test: " + _currentTest + ", aborting test with bob = " + bobIP);
return;
}
if (DataHelper.eq(bobIP.getAddress(), 0, _transport.getExternalIP(), 0, 2)) {
if (_transport.isTooClose(bobIP.getAddress())) {
if (_log.shouldLog(Log.WARN))
_log.warn("Not running test with Bob too close to us " + bobIP);
return;
@@ -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);
@@ -485,7 +496,7 @@ class PeerTestManager {
int fromPort = from.getPort();
if (fromPort < 1024 || fromPort > 65535 ||
(!_transport.isValid(fromIP)) ||
DataHelper.eq(fromIP, 0, _transport.getExternalIP(), 0, 2) ||
_transport.isTooClose(fromIP) ||
_context.blocklist().isBlocklisted(fromIP)) {
// spoof check, and don't respond to privileged ports
if (_log.shouldLog(Log.WARN))
@@ -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))
@@ -544,7 +556,7 @@ class PeerTestManager {
Long lNonce = Long.valueOf(nonce);
PeerTestState state = _activeTests.get(lNonce);
if (testIP != null && DataHelper.eq(testIP, 0, _transport.getExternalIP(), 0, 2)) {
if (testIP != null && _transport.isTooClose(testIP)) {
// spoof check - have to do this after receiveTestReply(), since
// the field should be us there.
// Let's also eliminate anybody in the same /16
@@ -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()));
}
@@ -2,16 +2,17 @@ package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import net.i2p.data.Base64;
import net.i2p.data.RouterAddress;
import net.i2p.data.SessionKey;
import net.i2p.util.LHMCache;
/**
* basic helper to parse out peer info from a udp address
* FIXME public for ConfigNetHelper
*/
public class UDPAddress {
class UDPAddress {
private final String _host;
private InetAddress _hostAddress;
private final int _port;
@@ -37,6 +38,23 @@ public class UDPAddress {
public static final String PROP_INTRO_KEY_PREFIX = "ikey";
public static final String PROP_INTRO_TAG_PREFIX = "itag";
static final int MAX_INTRODUCERS = 3;
private static final String[] PROP_INTRO_HOST;
private static final String[] PROP_INTRO_PORT;
private static final String[] PROP_INTRO_IKEY;
private static final String[] PROP_INTRO_TAG;
static {
// object churn
PROP_INTRO_HOST = new String[MAX_INTRODUCERS];
PROP_INTRO_PORT = new String[MAX_INTRODUCERS];
PROP_INTRO_IKEY = new String[MAX_INTRODUCERS];
PROP_INTRO_TAG = new String[MAX_INTRODUCERS];
for (int i = 0; i < MAX_INTRODUCERS; i++) {
PROP_INTRO_HOST[i] = PROP_INTRO_HOST_PREFIX + i;
PROP_INTRO_PORT[i] = PROP_INTRO_PORT_PREFIX + i;
PROP_INTRO_IKEY[i] = PROP_INTRO_KEY_PREFIX + i;
PROP_INTRO_TAG[i] = PROP_INTRO_TAG_PREFIX + i;
}
}
public UDPAddress(RouterAddress addr) {
// TODO make everything final
@@ -49,33 +67,38 @@ public class UDPAddress {
_port = addr.getPort();
try {
String mtu = addr.getOption(PROP_MTU);
if (mtu != null)
_mtu = MTU.rectify(Integer.parseInt(mtu));
if (mtu != null) {
boolean isIPv6 = _host != null && _host.contains(":");
_mtu = MTU.rectify(isIPv6, Integer.parseInt(mtu));
}
} catch (NumberFormatException nfe) {}
String key = addr.getOption(PROP_INTRO_KEY);
if (key != null)
_introKey = Base64.decode(key.trim());
if (key != null) {
byte[] ik = Base64.decode(key.trim());
if (ik != null && ik.length == SessionKey.KEYSIZE_BYTES)
_introKey = ik;
}
for (int i = MAX_INTRODUCERS; i >= 0; i--) {
String host = addr.getOption(PROP_INTRO_HOST_PREFIX + i);
for (int i = MAX_INTRODUCERS - 1; i >= 0; i--) {
String host = addr.getOption(PROP_INTRO_HOST[i]);
if (host == null) continue;
String port = addr.getOption(PROP_INTRO_PORT_PREFIX + i);
String port = addr.getOption(PROP_INTRO_PORT[i]);
if (port == null) continue;
String k = addr.getOption(PROP_INTRO_KEY_PREFIX + i);
String k = addr.getOption(PROP_INTRO_IKEY[i]);
if (k == null) continue;
byte ikey[] = Base64.decode(k);
if ( (ikey == null) || (ikey.length != SessionKey.KEYSIZE_BYTES) )
continue;
String t = addr.getOption(PROP_INTRO_TAG_PREFIX + i);
String t = addr.getOption(PROP_INTRO_TAG[i]);
if (t == null) continue;
int p = -1;
int p;
try {
p = Integer.parseInt(port);
if (p <= 0 || p > 65535) continue;
if (p < UDPTransport.MIN_PEER_PORT || p > 65535) continue;
} catch (NumberFormatException nfe) {
continue;
}
long tag = -1;
long tag;
try {
tag = Long.parseLong(t);
if (tag <= 0) continue;
@@ -131,14 +154,10 @@ public class UDPAddress {
}
public String getHost() { return _host; }
InetAddress getHostAddress() {
if (_hostAddress == null) {
try {
_hostAddress = InetAddress.getByName(_host);
} catch (UnknownHostException uhe) {
_hostAddress = null;
}
}
if (_hostAddress == null)
_hostAddress = getByName(_host);
return _hostAddress;
}
@@ -150,18 +169,17 @@ public class UDPAddress {
byte[] getIntroKey() { return _introKey; }
int getIntroducerCount() { return (_introAddresses == null ? 0 : _introAddresses.length); }
InetAddress getIntroducerHost(int i) {
if (_introAddresses[i] == null) {
try {
_introAddresses[i] = InetAddress.getByName(_introHosts[i]);
} catch (UnknownHostException uhe) {
_introAddresses[i] = null;
}
}
if (_introAddresses[i] == null)
_introAddresses[i] = getByName(_introHosts[i]);
return _introAddresses[i];
}
int getIntroducerPort(int i) { return _introPorts[i]; }
byte[] getIntroducerKey(int i) { return _introKeys[i]; }
long getIntroducerTag(int i) { return _introTags[i]; }
/**
@@ -192,4 +210,71 @@ public class UDPAddress {
}
return rv.toString();
}
////////////////
// cache copied from Addresses.java but caching InetAddress instead of byte[]
/**
* Textual IP to InetAddress, because InetAddress.getByName() is slow.
*
* @since IPv6
*/
private static final Map<String, InetAddress> _inetAddressCache;
static {
long maxMemory = Runtime.getRuntime().maxMemory();
if (maxMemory == Long.MAX_VALUE)
maxMemory = 96*1024*1024l;
long min = 128;
long max = 2048;
// 512 nominal for 128 MB
int size = (int) Math.max(min, Math.min(max, 1 + (maxMemory / (256*1024))));
_inetAddressCache = new LHMCache(size);
}
/**
* Caching version of InetAddress.getByName(host), which is slow.
* Caches numeric host names only.
* Will resolve but not cache DNS host names.
*
* Unlike InetAddress.getByName(), we do NOT allow numeric IPs
* of the form d.d.d, d.d, or d, as these are almost certainly mistakes.
*
* @param host DNS or IPv4 or IPv6 host name; if null returns null
* @return InetAddress or null
* @since IPv6
*/
private static InetAddress getByName(String host) {
if (host == null)
return null;
InetAddress rv;
synchronized (_inetAddressCache) {
rv = _inetAddressCache.get(host);
}
if (rv == null) {
try {
boolean isIPv4 = host.replaceAll("[0-9\\.]", "").length() == 0;
if (isIPv4 && host.replaceAll("[0-9]", "").length() != 3)
return null;
rv = InetAddress.getByName(host);
if (isIPv4 ||
host.replaceAll("[0-9a-fA-F:]", "").length() == 0) {
synchronized (_inetAddressCache) {
_inetAddressCache.put(host, rv);
}
}
} catch (UnknownHostException uhe) {}
}
return rv;
}
/**
* @since IPv6
*/
static void clearCache() {
synchronized(_inetAddressCache) {
_inetAddressCache.clear();
}
}
}
@@ -1,15 +1,19 @@
package net.i2p.router.transport.udp;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.SocketException;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
* Coordinate the low level datagram socket, managing the UDPSender and
* UDPReceiver
* Coordinate the low-level datagram socket, creating and managing the UDPSender and
* UDPReceiver.
*/
class UDPEndpoint {
private final RouterContext _context;
@@ -20,8 +24,11 @@ class UDPEndpoint {
private UDPReceiver _receiver;
private DatagramSocket _socket;
private final InetAddress _bindAddress;
private final boolean _isIPv4, _isIPv6;
private static final AtomicInteger _counter = new AtomicInteger();
/**
* @param transport may be null for unit testing ONLY
* @param listenPort -1 or the requested port, may not be honored
* @param bindAddress null ok
*/
@@ -31,22 +38,27 @@ class UDPEndpoint {
_transport = transport;
_bindAddress = bindAddress;
_listenPort = listenPort;
_isIPv4 = bindAddress == null || bindAddress instanceof Inet4Address;
_isIPv6 = bindAddress == null || bindAddress instanceof Inet6Address;
}
/** caller should call getListenPort() after this to get the actual bound port and determine success */
public synchronized void startup() {
public synchronized void startup() throws SocketException {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Starting up the UDP endpoint");
shutdown();
_socket = getSocket();
if (_socket == null) {
_log.log(Log.CRIT, "UDP Unable to open a port");
return;
throw new SocketException("SSU Unable to bind to a port on " + _bindAddress);
}
_sender = new UDPSender(_context, _socket, "UDPSender");
_receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver");
int count = _counter.incrementAndGet();
_sender = new UDPSender(_context, _socket, "UDPSender " + count);
_sender.startup();
_receiver.startup();
if (_transport != null) {
_receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver " + count);
_receiver.startup();
}
}
public synchronized void shutdown() {
@@ -103,9 +115,7 @@ class UDPEndpoint {
if (port <= 0) {
// try random ports rather than just do new DatagramSocket()
// so we stay out of the way of other I2P stuff
int minPort = _context.getProperty(PROP_MIN_PORT, MIN_RANDOM_PORT);
int maxPort = _context.getProperty(PROP_MAX_PORT, MAX_RANDOM_PORT);
port = minPort + _context.random().nextInt(maxPort - minPort);
port = selectRandomPort(_context);
}
try {
if (_bindAddress == null)
@@ -131,6 +141,17 @@ class UDPEndpoint {
return socket;
}
/**
* Pick a random port between the configured boundaries
* @since IPv6
*/
public static int selectRandomPort(RouterContext ctx) {
int minPort = Math.min(65535, Math.max(1, ctx.getProperty(PROP_MIN_PORT, MIN_RANDOM_PORT)));
int maxPort = Math.min(65535, Math.max(minPort, ctx.getProperty(PROP_MAX_PORT, MAX_RANDOM_PORT)));
return minPort + ctx.random().nextInt(1 + maxPort - minPort);
}
/** call after startup() to get actual port or -1 on startup failure */
public int getListenPort() { return _listenPort; }
public UDPSender getSender() { return _sender; }
@@ -143,15 +164,24 @@ class UDPEndpoint {
public void send(UDPPacket packet) {
_sender.add(packet);
}
/**
* Blocking call to receive the next inbound UDP packet from any peer.
* @return null if we have shut down
*
* UNIT TESTING ONLY. Direct from the socket.
* In normal operation, UDPReceiver thread injects to PacketHandler queue.
*
* @return null if we have shut down, or on failure
*/
public UDPPacket receive() {
if (_receiver == null)
UDPPacket packet = UDPPacket.acquire(_context, true);
try {
_socket.receive(packet.getPacket());
return packet;
} catch (IOException ioe) {
packet.release();
return null;
return _receiver.receiveNext();
}
}
/**
@@ -162,4 +192,20 @@ class UDPEndpoint {
if (_sender != null)
_sender.clear();
}
/**
* @return true for wildcard too
* @since IPv6
*/
public boolean isIPv4() {
return _isIPv4;
}
/**
* @return true for wildcard too
* @since IPv6
*/
public boolean isIPv6() {
return _isIPv6;
}
}
@@ -10,6 +10,7 @@ import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.router.util.CDQEntry;
import net.i2p.util.Addresses;
import net.i2p.util.Log;
/**
@@ -300,8 +301,7 @@ class UDPPacket implements CDQEntry {
StringBuilder buf = new StringBuilder(256);
buf.append(_packet.getLength());
buf.append(" byte pkt with ");
buf.append(_packet.getAddress().getHostAddress()).append(":");
buf.append(_packet.getPort());
buf.append(Addresses.toString(_packet.getAddress().getAddress(), _packet.getPort()));
//buf.append(" id=").append(System.identityHashCode(this));
if (_messageType >= 0)
buf.append(" msgType=").append(_messageType);
@@ -730,6 +730,7 @@ class UDPPacketReader {
return (int)DataHelper.fromLong(_message, offset, 2);
}
/** @deprecated unused */
public int readAliceIPSize() {
int offset = readBodyOffset();
offset += DataHelper.fromLong(_message, offset, 1);
@@ -737,6 +738,7 @@ class UDPPacketReader {
offset += 2;
return (int)DataHelper.fromLong(_message, offset, 1);
}
/** @deprecated unused */
public void readAliceIP(byte target[], int targetOffset) {
int offset = readBodyOffset();
offset += DataHelper.fromLong(_message, offset, 1);
@@ -746,6 +748,7 @@ class UDPPacketReader {
offset++;
System.arraycopy(_message, offset, target, targetOffset, sz);
}
/** @deprecated unused */
public int readAlicePort() {
int offset = readBodyOffset();
offset += DataHelper.fromLong(_message, offset, 1);
@@ -3,11 +3,9 @@ package net.i2p.router.transport.udp;
import java.io.IOException;
import java.net.DatagramSocket;
import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.router.util.CoDelBlockingQueue;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
@@ -20,37 +18,31 @@ import net.i2p.util.SystemVersion;
* waiting around too long, they are dropped. Packets should be pulled off
* from the queue ASAP by a {@link PacketHandler}
*
* There is a UDPReceiver for each UDPEndpoint.
* It contains a thread but no queue. Received packets are queued
* in the common PacketHandler queue.
*/
class UDPReceiver {
private final RouterContext _context;
private final Log _log;
private final DatagramSocket _socket;
private String _name;
private final BlockingQueue<UDPPacket> _inboundQueue;
private volatile boolean _keepRunning;
private final Runner _runner;
private final UDPTransport _transport;
private static int __id;
private final int _id;
private final PacketHandler _handler;
private static final boolean _isAndroid = SystemVersion.isAndroid();
private static final int TYPE_POISON = -99999;
private static final int MIN_QUEUE_SIZE = 16;
private static final int MAX_QUEUE_SIZE = 192;
public UDPReceiver(RouterContext ctx, UDPTransport transport, DatagramSocket socket, String name) {
_context = ctx;
_log = ctx.logManager().getLog(UDPReceiver.class);
_id = ++__id;
_name = name;
long maxMemory = Runtime.getRuntime().maxMemory();
if (maxMemory == Long.MAX_VALUE)
maxMemory = 96*1024*1024l;
int qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (2*1024*1024)));
_inboundQueue = new CoDelBlockingQueue(ctx, "UDP-Receiver", qsize);
_socket = socket;
_transport = transport;
_handler = transport.getPacketHandler();
if (_handler == null)
throw new IllegalStateException();
_runner = new Runner();
//_context.statManager().createRateStat("udp.receivePacketSize", "How large packets received are", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.receiveRemaining", "How many packets are left sitting on the receiver's queue", "udp", UDPTransport.RATES);
@@ -62,24 +54,12 @@ class UDPReceiver {
public synchronized void startup() {
//adjustDropProbability();
_keepRunning = true;
I2PThread t = new I2PThread(_runner, _name + '.' + _id, true);
I2PThread t = new I2PThread(_runner, _name, true);
t.start();
}
public synchronized void shutdown() {
_keepRunning = false;
_inboundQueue.clear();
for (int i = 0; i < _transport.getPacketHandlerCount(); i++) {
UDPPacket poison = UDPPacket.acquire(_context, false);
poison.setMessageType(TYPE_POISON);
_inboundQueue.offer(poison);
}
for (int i = 1; i <= 5 && !_inboundQueue.isEmpty(); i++) {
try {
Thread.sleep(i * 50);
} catch (InterruptedException ie) {}
}
_inboundQueue.clear();
}
/*********
@@ -171,7 +151,7 @@ class UDPReceiver {
}
// drop anything apparently from our IP (any port)
if (Arrays.equals(from.getIP(), _transport.getExternalIP())) {
if (Arrays.equals(from.getIP(), _transport.getExternalIP()) && !_transport.allowLocal()) {
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping (spoofed?) packet from ourselves");
packet.release();
@@ -194,7 +174,7 @@ class UDPReceiver {
if (!rejected) {
****/
try {
_inboundQueue.put(packet);
_handler.queueReceived(packet);
} catch (InterruptedException ie) {
packet.release();
_keepRunning = false;
@@ -229,24 +209,6 @@ class UDPReceiver {
}
****/
/**
* Blocking call to retrieve the next inbound packet, or null if we have
* shut down.
*
*/
public UDPPacket receiveNext() {
UDPPacket rv = null;
//int remaining = 0;
while (_keepRunning && rv == null) {
try {
rv = _inboundQueue.take();
} catch (InterruptedException ie) {}
if (rv != null && rv.getMessageType() == TYPE_POISON)
return null;
}
//_context.statManager().addRateData("udp.receiveRemaining", remaining, 0);
return rv;
}
private class Runner implements Runnable {
//private volatile boolean _socketChanged;
@@ -288,7 +250,10 @@ class UDPReceiver {
// DatagramSocket javadocs: If the message is longer than the packet's length, the message is truncated.
throw new IOException("packet too large! truncated and dropped from: " + packet.getRemoteHost());
}
if (size > 0) {
if (_context.commSystem().isDummy()) {
// testing
packet.release();
} else if (size > 0) {
//FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestInbound(size, "UDP receiver");
//_context.bandwidthLimiter().requestInbound(req, size, "UDP receiver");
FIFOBandwidthLimiter.Request req =
@@ -14,6 +14,9 @@ import net.i2p.util.Log;
/**
* Lowest level packet sender, pushes anything on its queue ASAP.
*
* There is a UDPSender for each UDPEndpoint.
* It contains a thread and a queue. Packet to be sent are queued
* by the PacketPusher.
*/
class UDPSender {
private final RouterContext _context;
@@ -23,13 +26,16 @@ class UDPSender {
private final BlockingQueue<UDPPacket> _outboundQueue;
private volatile boolean _keepRunning;
private final Runner _runner;
private final boolean _dummy;
private static final int TYPE_POISON = 99999;
private static final int MIN_QUEUE_SIZE = 64;
private static final int MAX_QUEUE_SIZE = 384;
public UDPSender(RouterContext ctx, DatagramSocket socket, String name) {
_context = ctx;
_dummy = false; // ctx.commSystem().isDummy();
_log = ctx.logManager().getLog(UDPSender.class);
long maxMemory = Runtime.getRuntime().maxMemory();
if (maxMemory == Long.MAX_VALUE)
@@ -176,6 +182,12 @@ class UDPSender {
_log.error("Dropping large UDP packet " + psz + " bytes: " + packet);
return;
}
if (_dummy) {
// testing
// back to the cache
packet.release();
return;
}
try {
_outboundQueue.put(packet);
} catch (InterruptedException ie) {
File diff suppressed because it is too large Load Diff
@@ -42,10 +42,14 @@ class TunnelGatewayPumper implements Runnable {
_context = ctx;
_wantsPumping = new LinkedHashSet(16);
_backlogged = new HashSet(16);
long maxMemory = Runtime.getRuntime().maxMemory();
if (maxMemory == Long.MAX_VALUE)
maxMemory = 96*1024*1024l;
_pumpers = (int) Math.max(MIN_PUMPERS, Math.min(MAX_PUMPERS, 1 + (maxMemory / (32*1024*1024))));
if (ctx.getBooleanProperty("i2p.dummyTunnelManager")) {
_pumpers = 1;
} else {
long maxMemory = Runtime.getRuntime().maxMemory();
if (maxMemory == Long.MAX_VALUE)
maxMemory = 96*1024*1024l;
_pumpers = (int) Math.max(MIN_PUMPERS, Math.min(MAX_PUMPERS, 1 + (maxMemory / (32*1024*1024))));
}
for (int i = 0; i < _pumpers; i++)
new I2PThread(this, "Tunnel GW pumper " + (i+1) + '/' + _pumpers, true).start();
}