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

View File

@ -207,7 +207,7 @@ Applications:
FatCow icons: See licenses/LICENSE-FatCowIcons.txt
GeoIP Data:
Copyright (c) 2008 MaxMind, Inc. All Rights Reserved.
This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com/
See licenses/LICENSE-GeoIP.txt
Router Console and I2PSnark themes:

View File

@ -7,7 +7,7 @@ import java.util.Map;
import net.i2p.router.Router;
import net.i2p.router.transport.FIFOBandwidthRefiller;
import net.i2p.router.transport.TransportImpl;
import net.i2p.router.transport.TransportUtil;
import net.i2p.router.transport.TransportManager;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.router.web.ConfigServiceHandler;
@ -370,7 +370,8 @@ public class ConfigNetHandler extends FormHandler {
addFormError(_("Invalid address") + ": " + addr);
return false;
}
boolean rv = TransportImpl.isPubliclyRoutable(iab);
// TODO set IPv6 arg based on configuration?
boolean rv = TransportUtil.isPubliclyRoutable(iab, true);
if (!rv)
addFormError(_("The hostname or IP {0} is not publicly routable", addr));
return rv;

View File

@ -7,7 +7,6 @@ import net.i2p.data.RouterAddress;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.Router;
import net.i2p.router.transport.TransportManager;
import net.i2p.router.transport.udp.UDPAddress;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.util.Addresses;
@ -32,22 +31,15 @@ public class ConfigNetHelper extends HelperBase {
return _context.getProperty(PROP_I2NP_NTCP_PORT, "");
}
public String getUdpAddress() {
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
if (addr == null)
return _("unknown");
UDPAddress ua = new UDPAddress(addr);
return ua.toString();
}
/** @return host or "unknown" */
public String getUdpIP() {
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
if (addr == null)
return _("unknown");
UDPAddress ua = new UDPAddress(addr);
if (ua.getHost() == null)
String rv = addr.getHost();
if (rv == null)
return _("unknown");
return ua.getHost();
return rv;
}
/**

View File

@ -24,7 +24,7 @@ import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion;
import net.i2p.router.TunnelPoolSettings;
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
import net.i2p.router.transport.ntcp.NTCPAddress;
import net.i2p.router.transport.TransportUtil;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.util.PortMapper;
@ -159,7 +159,8 @@ public class SummaryHelper extends HelperBase {
switch (status) {
case CommSystemFacade.STATUS_OK:
RouterAddress ra = routerInfo.getTargetAddress("NTCP");
if (ra == null || (new NTCPAddress(ra)).isPubliclyRoutable())
// TODO set IPv6 arg based on configuration?
if (ra == null || TransportUtil.isPubliclyRoutable(ra.getIP(), true))
return _("OK");
return _("ERR-Private TCP Address");
case CommSystemFacade.STATUS_DIFFERENT:

View File

@ -1122,6 +1122,8 @@
<copy file="build/addressbook.war" todir="pkg-temp/webapps/" />
<!-- decapitalized the file in 0.7.8 -->
<copy file="installer/resources/countries.txt" todir="pkg-temp/geoip/" />
<!-- small enough to include for now -->
<copy file="installer/resources/geoipv6.dat.gz" todir="pkg-temp/geoip/" />
</target>
<target name="prepupdateRouter" depends="buildrouter, deletepkg-temp">
@ -1137,6 +1139,7 @@
<!-- GeoIP files and flag icons -->
<target name="prepgeoupdate">
<copy file="installer/resources/geoip.txt" todir="pkg-temp/geoip/" />
<copy file="installer/resources/geoipv6.dat.gz" todir="pkg-temp/geoip/" />
<copy file="installer/resources/countries.txt" todir="pkg-temp/geoip/" />
<copy todir="pkg-temp/docs/icons/flags" >
<fileset dir="installer/resources/icons/flags" />
@ -1292,20 +1295,29 @@
<!-- end custom installers -->
<!-- unit tests -->
<target name="updateTest" depends="prepupdate">
<target name="buildTest">
<ant dir="core/java/" target="jarTest" />
<copy file="core/java/build/i2ptest.jar" todir="pkg-temp/lib" />
<zip destfile="i2pupdate.zip" basedir="pkg-temp" />
<ant dir="router/java/" target="jarTest" />
<copy file="core/java/build/i2ptest.jar" todir="build" />
<copy file="router/java/build/routertest.jar" todir="build" />
</target>
<target name="prepTest" depends="prepupdate, buildTest">
<!-- overwrite i2p.jar and router.jar with the test versions -->
<copy file="build/i2ptest.jar" tofile="pkg-temp/lib/i2p.jar" overwrite="true" />
<copy file="build/routertest.jar" tofile="pkg-temp/lib/router.jar" overwrite="true" />
</target>
<target name="updateTest" depends="prepTest, zipit" />
<target name="junit.test" depends="buildProperties, jbigi" >
<ant dir="core/java/" target="junit.test" />
<ant dir="router/java/" target="junit.test" />
</target>
<target name="scalatest.test" depends="buildProperties, jbigi" >
<ant dir="core/java/" target="scalatest.test" />
<!-- note there are no router scala tests yet -->
<ant dir="router/java/" target="scalatest.test" />
</target>
<target name="test" depends="buildProperties, jbigi" >
<!-- both junit and scala -->
<ant dir="core/java/" target="test" />
<ant dir="router/java/" target="test" />
</target>

View File

@ -38,7 +38,7 @@ import net.i2p.util.OrderedProperties;
* @author jrandom
*/
public class RouterAddress extends DataStructureImpl {
private int _cost;
private short _cost;
//private Date _expiration;
private String _transportStyle;
private final Properties _options;
@ -50,16 +50,30 @@ public class RouterAddress extends DataStructureImpl {
public static final String PROP_PORT = "port";
public RouterAddress() {
_cost = -1;
_options = new OrderedProperties();
}
/**
* For efficiency when created by a Transport.
* @param options not copied; do not reuse or modify
* @param cost 0-255
* @since IPv6
*/
public RouterAddress(String style, OrderedProperties options, int cost) {
_transportStyle = style;
_options = options;
if (cost < 0 || cost > 255)
throw new IllegalArgumentException();
_cost = (short) cost;
}
/**
* Retrieve the weighted cost of this address, relative to other methods of
* contacting this router. The value 0 means free and 255 means really expensive.
* No value above 255 is allowed.
*
* Unused before 0.7.12
* @return 0-255
*/
public int getCost() {
return _cost;
@ -67,12 +81,18 @@ public class RouterAddress extends DataStructureImpl {
/**
* Configure the weighted cost of using the address.
* No value above 255 is allowed.
* No value negative or above 255 is allowed.
*
* WARNING - do not change cost on a published address or it will break the RI sig.
* There is no check here.
* Rarely used, use 3-arg constructor.
*
* NTCP is set to 10 and SSU to 5 by default, unused before 0.7.12
*/
public void setCost(int cost) {
_cost = cost;
if (cost < 0 || cost > 255)
throw new IllegalArgumentException();
_cost = (short) cost;
}
/**
@ -113,6 +133,7 @@ public class RouterAddress extends DataStructureImpl {
* Configure the type of transport that must be used to communicate on this address
*
* @throws IllegalStateException if was already set
* @deprecated unused, use 3-arg constructor
*/
public void setTransportStyle(String transportStyle) {
if (_transportStyle != null)
@ -152,6 +173,7 @@ public class RouterAddress extends DataStructureImpl {
* Makes a copy.
* @param options non-null
* @throws IllegalStateException if was already set
* @deprecated unused, use 3-arg constructor
*/
public void setOptions(Properties options) {
if (!_options.isEmpty())
@ -171,7 +193,7 @@ public class RouterAddress extends DataStructureImpl {
if (_ip != null)
return _ip;
byte[] rv = null;
String host = _options.getProperty(PROP_HOST);
String host = getHost();
if (host != null) {
rv = Addresses.getIP(host);
if (rv != null &&
@ -183,6 +205,17 @@ public class RouterAddress extends DataStructureImpl {
return rv;
}
/**
* Convenience, same as getOption("host").
* Does no parsing, so faster than getIP().
*
* @return host string or null
* @since IPv6
*/
public String getHost() {
return _options.getProperty(PROP_HOST);
}
/**
* Caching version of Integer.parseInt(getOption("port"))
* Caches valid ports 1-65535 only.
@ -212,7 +245,7 @@ public class RouterAddress extends DataStructureImpl {
public void readBytes(InputStream in) throws DataFormatException, IOException {
if (_transportStyle != null)
throw new IllegalStateException();
_cost = (int) DataHelper.readLong(in, 1);
_cost = (short) DataHelper.readLong(in, 1);
//_expiration = DataHelper.readDate(in);
DataHelper.readDate(in);
_transportStyle = DataHelper.readString(in);
@ -229,8 +262,8 @@ public class RouterAddress extends DataStructureImpl {
* readin and the signature will fail.
*/
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if ((_cost < 0) || (_transportStyle == null))
throw new DataFormatException("Not enough data to write a router address");
if (_transportStyle == null)
throw new DataFormatException("uninitialized");
DataHelper.writeLong(out, 1, _cost);
//DataHelper.writeDate(out, _expiration);
DataHelper.writeDate(out, null);
@ -238,28 +271,44 @@ public class RouterAddress extends DataStructureImpl {
DataHelper.writeProperties(out, _options);
}
/**
* Transport, host, and port only.
* Never look at cost or other properties.
*/
@Override
public boolean equals(Object object) {
if (object == this) return true;
if ((object == null) || !(object instanceof RouterAddress)) return false;
RouterAddress addr = (RouterAddress) object;
// let's keep this fast as we are putting an address into the RouterInfo set frequently
return
_cost == addr._cost &&
getPort() == addr.getPort() &&
DataHelper.eq(getHost(), addr.getHost()) &&
DataHelper.eq(_transportStyle, addr._transportStyle);
//DataHelper.eq(_options, addr._options) &&
//DataHelper.eq(_expiration, addr._expiration);
}
/**
* Everything, including Transport, host, port, options, and cost
* @param addr may be null
* @since IPv6
*/
public boolean deepEquals(RouterAddress addr) {
return
equals(addr) &&
_cost == addr._cost &&
_options.equals(addr._options);
}
/**
* Just use a few items for speed (expiration is always null).
* Never look at cost or other properties.
*/
@Override
public int hashCode() {
return DataHelper.hashCode(_transportStyle) ^
DataHelper.hashCode(getIP()) ^
getPort() ^
_cost;
getPort();
}
/**
@ -271,10 +320,10 @@ public class RouterAddress extends DataStructureImpl {
public String toString() {
StringBuilder buf = new StringBuilder(128);
buf.append("[RouterAddress: ");
buf.append("\n\tTransportStyle: ").append(_transportStyle);
buf.append("\n\tType: ").append(_transportStyle);
buf.append("\n\tCost: ").append(_cost);
//buf.append("\n\tExpiration: ").append(_expiration);
buf.append("\n\tOptions: #: ").append(_options.size());
buf.append("\n\tOptions (").append(_options.size()).append("):");
for (Map.Entry e : _options.entrySet()) {
String key = (String) e.getKey();
String val = (String) e.getValue();

View File

@ -61,7 +61,7 @@ public class RouterInfo extends DatabaseEntry {
private final Properties _options;
private volatile boolean _validated;
private volatile boolean _isValid;
private volatile String _stringified;
//private volatile String _stringified;
private volatile byte _byteified[];
private volatile int _hashCode;
private volatile boolean _hashCodeInitialized;
@ -613,30 +613,34 @@ public class RouterInfo extends DatabaseEntry {
@Override
public String toString() {
if (_stringified != null) return _stringified;
StringBuilder buf = new StringBuilder(5*1024);
//if (_stringified != null) return _stringified;
StringBuilder buf = new StringBuilder(1024);
buf.append("[RouterInfo: ");
buf.append("\n\tIdentity: ").append(_identity);
buf.append("\n\tSignature: ").append(_signature);
buf.append("\n\tPublished on: ").append(new Date(_published));
buf.append("\n\tAddresses: #: ").append(_addresses.size());
for (RouterAddress addr : _addresses) {
buf.append("\n\t\tAddress: ").append(addr);
}
Set<Hash> peers = getPeers();
buf.append("\n\tPeers: #: ").append(peers.size());
for (Hash hash : peers) {
buf.append("\n\tPublished: ").append(new Date(_published));
if (_peers != null) {
buf.append("\n\tPeers (").append(_peers.size()).append("):");
for (Hash hash : _peers) {
buf.append("\n\t\tPeer hash: ").append(hash);
}
buf.append("\n\tOptions: #: ").append(_options.size());
}
buf.append("\n\tOptions (").append(_options.size()).append("):");
for (Map.Entry e : _options.entrySet()) {
String key = (String) e.getKey();
String val = (String) e.getValue();
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
}
if (!_addresses.isEmpty()) {
buf.append("\n\tAddresses (").append(_addresses.size()).append("):");
for (RouterAddress addr : _addresses) {
buf.append("\n\t").append(addr);
}
}
buf.append("]");
_stringified = buf.toString();
return _stringified;
String rv = buf.toString();
//_stringified = rv;
return rv;
}
/**

View File

@ -34,7 +34,7 @@ public abstract class Addresses {
return !getAddresses(true, false, false).isEmpty();
}
/** @return the first non-local address it finds, or null */
/** @return the first non-local address IPv4 address it finds, or null */
public static String getAnyAddress() {
SortedSet<String> a = getAddresses();
if (!a.isEmpty())
@ -95,7 +95,7 @@ public abstract class Addresses {
haveIPv6 = true;
if (shouldInclude(allMyIps[i], includeSiteLocal,
includeLoopbackAndWildcard, includeIPv6))
rv.add(allMyIps[i].getHostAddress());
rv.add(stripScope(allMyIps[i].getHostAddress()));
}
}
} catch (UnknownHostException e) {}
@ -113,7 +113,7 @@ public abstract class Addresses {
haveIPv6 = true;
if (shouldInclude(addr, includeSiteLocal,
includeLoopbackAndWildcard, includeIPv6))
rv.add(addr.getHostAddress());
rv.add(stripScope(addr.getHostAddress()));
}
}
}
@ -128,6 +128,17 @@ public abstract class Addresses {
return rv;
}
/**
* Strip the trailing "%nn" from Inet6Address.getHostAddress()
* @since IPv6
*/
private static String stripScope(String ip) {
int pct = ip.indexOf("%");
if (pct > 0)
ip = ip.substring(0, pct);
return ip;
}
private static boolean shouldInclude(InetAddress ia, boolean includeSiteLocal,
boolean includeLoopbackAndWildcard, boolean includeIPv6) {
return

View File

@ -46,6 +46,7 @@ Friend of the Chinese Floodfill Flooder:159.226.40.3
<a href="http://www.team-cymru.org/Services/Bogons/http.html">The Team Cymru Bogon List v6.8 03 FEB 2011</a>:172.16.0.0/12
<a href="http://www.team-cymru.org/Services/Bogons/http.html">The Team Cymru Bogon List v6.8 03 FEB 2011</a>:192.0.0.0/24
<a href="http://www.team-cymru.org/Services/Bogons/http.html">The Team Cymru Bogon List v6.8 03 FEB 2011</a>:192.0.2.0/24
<a href="http://tools.ietf.org/html/rfc3068">6to4 Anycast</a>:192.88.99.0/24
<a href="http://www.team-cymru.org/Services/Bogons/http.html">The Team Cymru Bogon List v6.8 03 FEB 2011</a>:192.168.0.0/16
<a href="http://www.team-cymru.org/Services/Bogons/http.html">The Team Cymru Bogon List v6.8 03 FEB 2011</a>:198.18.0.0/15
<a href="http://www.team-cymru.org/Services/Bogons/http.html">The Team Cymru Bogon List v6.8 03 FEB 2011</a>:198.51.100.0/24

View File

@ -0,0 +1,9 @@
# Local geoIPv6 additions
# Format: from IP,to IP,,,country code[,"country name"]
####
# common tunnel brokers
2001:5c0:1000:a::,2001:5c0:1000:a::,,,X1,"Freenet6 anonymous"
2001:5c0:1000:b::,2001:5c0:1000:b::,,,X2,"Freenet6 authenticated"
2001:5c0:1100::,2001:5c0:11ff:ffff:ffff:ffff:ffff:ffff,,,X3,"Freenet6 delegated"
# other interesting addresses
2002::,2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff,,,X4,"IPv4 compatibility"
Can't render this file because it contains an unexpected character in line 2 and column 54.

Binary file not shown.

View File

@ -0,0 +1,19 @@
#!/bin/sh
#
# Fetch the latest file from Maxmind, merge with
# our additions, and compress.
#
FILE1=GeoIPv6.csv.gz
FILE2=geoipv6-extras.csv
FILEOUT=geoipv6.dat.gz
rm -f $FILE1 $FILEOUT
wget http://geolite.maxmind.com/download/geoip/database/$FILE1
if [ "$?" -ne "0" ]
then
echo 'Cannot fetch'
exit 1
fi
java -cp ../../build/i2p.jar:../../build/router.jar net.i2p.router.transport.GeoIPv6 $FILE1 $FILE2 $FILEOUT
exit $?

View File

@ -1,32 +1,8 @@
OPEN DATA LICENSE (GeoLite Country and GeoLite City databases)
The GeoLite databases are distributed under the
Creative Commons Attribution-ShareAlike 3.0 Unported License
http://creativecommons.org/licenses/by-sa/3.0/ .
The attribution requirement may be met by including the following in
all advertising and documentation mentioning features of or use of this database:
Copyright (c) 2008 MaxMind, Inc. All Rights Reserved.
All advertising materials and documentation mentioning features or use of
this database must display the following acknowledgment:
"This product includes GeoLite data created by MaxMind, available from
http://maxmind.com/"
Redistribution and use with or without modification, are permitted provided
that the following conditions are met:
1. Redistributions must retain the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
2. All advertising materials and documentation mentioning features or use of
this database must display the following acknowledgement:
"This product includes GeoLite data created by MaxMind, available from
http://maxmind.com/"
3. "MaxMind" may not be used to endorse or promote products derived from this
database without specific prior written permission.
THIS DATABASE IS PROVIDED BY MAXMIND, INC ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MAXMIND BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
DATABASE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
http://www.maxmind.com/"

View File

@ -258,7 +258,10 @@
<fileset dir="../../reports/router/junit/" />
</replaceregexp>
</target>
<!-- both junit and scala, but we have no scala tests yet -->
<target name="test" depends="junit.test"/>
<!-- test reports -->
<target name="scalatest.report">
<junitreport todir="../../reports/router/scalatest">

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,7 +77,19 @@ 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;
@ -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))
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;
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.

View File

@ -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; }

View File

@ -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 {

View File

@ -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; };
}

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);

View File

@ -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;
}
/**

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}

View File

@ -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());

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -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;
@ -46,6 +42,12 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
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;
_log = _context.logManager().getLog(CommSystemFacadeImpl.class);
@ -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);
List<RouterAddress> addresses = new ArrayList(_manager.getAddresses());
if (_log.shouldLog(Log.INFO))
_log.info("NTCP address: " + addr);
if (addr != null) {
addresses.put(NTCPTransport.STYLE, addr);
newCreated = true;
}
}
if (_log.shouldLog(Log.INFO))
_log.info("Creating addresses: " + addresses + " isNew? " + newCreated, new Exception("creator"));
return new HashSet(addresses.values());
}
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;
_log.info("Creating addresses: " + addresses, new Exception("creator"));
return addresses;
}
/**
* 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 (_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;
port = udp.getRequestedPort();
}
} 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)
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;
return ra.getOption("host");
}
/** 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

View File

@ -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,19 +139,30 @@ 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);
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)))
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,24 +365,31 @@ 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;
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;
}
}
/**
* Get the country for a country code

View File

@ -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));
}
}

View File

@ -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());
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
Set<String> ipset = Addresses.getAddresses(false, true); // non-local, include IPv6
for (String ips : ipset) {
try {
ia = InetAddress.getByName(ips);
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);
return;
}
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)
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,12 +558,16 @@ 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("</pre>\n");
out.write(buf.toString());
if (_upnpManager != null)

View File

@ -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;
}
}

View File

@ -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);
try {
forwardCallback.portForwardStatus(map);
} catch (Exception e) {
_log.error("UPnP RPT error", e);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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,7 +780,7 @@ class EventPumper implements Runnable {
key.attach(con);
con.setKey(key);
try {
NTCPAddress naddr = con.getRemoteAddress();
RouterAddress naddr = con.getRemoteAddress();
if (naddr.getPort() <= 0)
throw new IOException("Invalid NTCP address: " + naddr);
InetSocketAddress saddr = new InetSocketAddress(naddr.getHost(), naddr.getPort());

View File

@ -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))) );
}
}

View File

@ -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();
@ -217,14 +220,41 @@ class NTCPConnection {
_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; }

View File

@ -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;
private synchronized void restartListening(RouterAddress addr) {
if (addr != null) {
RouterAddress myAddress = bindAddress(addr.getPort());
if (myAddress != null)
replaceAddress(myAddress);
else
_myAddress = new NTCPAddress(addr);
return bindAddress();
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;
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");
}
return myAddress;
}
if (_myAddress != null) {
RouterAddress rv = _myAddress.toRouterAddress();
if (rv != null)
replaceAddress(rv);
return rv;
} else {
/**
* @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()));

View File

@ -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++) {

View File

@ -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));
}

View File

@ -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[]) {

View File

@ -66,6 +66,7 @@ class InboundMessageFragments /*implements UDPTransport.PartialACKSource */{
_ackSender.shutdown();
_messageReceiver.shutdown();
}
public boolean isAlive() { return _alive; }
/**

View File

@ -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));
}
}

View File

@ -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,12 +79,30 @@ 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[0]);
System.out.println("MTU is " + getMTU(test));
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 {
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");
}
}
****/
}

View File

@ -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 {

View File

@ -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.

View File

@ -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...

View File

@ -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();
}
}

View File

@ -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;
if (remoteIP.length == 4) {
_mtu = DEFAULT_MTU;
_mtuReceive = DEFAULT_MTU;
_largeMTU = transport.getMTU();
_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++;
int minMTU;
if (_remoteIP.length == 4) {
size += OVERHEAD_SIZE;
if (size <= MIN_MTU) {
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());

View File

@ -24,6 +24,13 @@ import net.i2p.util.SimpleTimer;
* Entry points are runTest() to start a new test as Alice,
* and receiveTest() for all received test packets.
*
* IPv6 info: All Alice-Bob and Alice-Charlie communication is via IPv4.
* The Bob-Charlie communication may be via IPv6, however Charlie must
* be IPv4-capable.
* The IP address (of Alice) in the message must be IPv4 if present,
* as we only support testing of IPv4.
* Testing of IPv6 could be added in the future.
*
* From udp.html on the website:
<p>The automation of collaborative reachability testing for peers is
@ -166,6 +173,8 @@ class PeerTestManager {
/**
* The next few methods are for when we are Alice
*
* @param bobIP IPv4 only
*/
public synchronized void runTest(InetAddress bobIP, int bobPort, SessionKey bobCipherKey, SessionKey bobMACKey) {
if (_currentTest != null) {
@ -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()));
}

View File

@ -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();
}
}
}

View File

@ -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,23 +38,28 @@ 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();
if (_transport != null) {
_receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver " + count);
_receiver.startup();
}
}
public synchronized void shutdown() {
if (_sender != null) {
@ -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; }
@ -146,12 +167,21 @@ class UDPEndpoint {
/**
* 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;
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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 =

View File

@ -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,6 +26,8 @@ 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;
@ -30,6 +35,7 @@ class UDPSender {
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) {

View File

@ -42,10 +42,14 @@ class TunnelGatewayPumper implements Runnable {
_context = ctx;
_wantsPumping = new LinkedHashSet(16);
_backlogged = new HashSet(16);
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();
}

View File

@ -27,14 +27,16 @@ public class SSUDemo {
RouterContext _us;
public static void main(String args[]) {
boolean testNTCP = args.length > 0 && args[0].equals("ntcp");
SSUDemo demo = new SSUDemo();
demo.run();
demo.run(testNTCP);
}
public SSUDemo() {}
public void run() {
public void run(boolean testNTCP) {
String cfgFile = "router.config";
Properties envProps = getEnv();
Properties envProps = getEnv(testNTCP);
Router r = new Router(cfgFile, envProps);
r.runRouter();
_us = r.getContext();
@ -50,21 +52,36 @@ public class SSUDemo {
loadPeers();
}
private Properties getEnv() {
Properties envProps = System.getProperties();
// disable the TCP transport, as its deprecated
envProps.setProperty("i2np.tcp.disable", "true");
private static Properties getEnv(boolean testNTCP) {
Properties envProps = new Properties();
// disable one of the transports and UPnP
if (testNTCP)
envProps.setProperty("i2np.udp.enable", "false");
else
envProps.setProperty("i2np.ntcp.enable", "false");
envProps.setProperty("i2np.upnp.enable", "false");
// we want SNTP synchronization for replay prevention
envProps.setProperty("time.disabled", "false");
// allow 127.0.0.1/10.0.0.1/etc (useful for testing). If this is false,
// peers who say they're on an invalid IP are banlisted
envProps.setProperty("i2np.udp.allowLocal", "true");
envProps.setProperty("i2np.ntcp.allowLocal", "true");
// IPv6
envProps.setProperty("i2np.udp.ipv6", "enable");
envProps.setProperty("i2np.ntcp.ipv6", "enable");
// explicit IP+port. at least one router on the net has to have their IP+port
// set, since there has to be someone to detect one's IP off. most don't need
// to set these though
envProps.setProperty("i2np.udp.host", "127.0.0.1");
envProps.setProperty("i2np.udp.internalPort", "12000");
envProps.setProperty("i2np.udp.port", "12000");
//envProps.setProperty("i2np.udp.host", "127.0.0.1");
envProps.setProperty("i2np.udp.host", "::1");
envProps.setProperty("i2np.ntcp.autoip", "false");
envProps.setProperty("i2np.ntcp.hostname", "::1");
// we don't have a context yet to use its random
String port = Integer.toString(44000 + (((int) System.currentTimeMillis()) & (16384 - 1)));
envProps.setProperty("i2np.udp.internalPort", port);
envProps.setProperty("i2np.udp.port", port);
envProps.setProperty("i2np.ntcp.autoport", "false");
envProps.setProperty("i2np.ntcp.port", port);
// disable I2CP, the netDb, peer testing/profile persistence, and tunnel
// creation/management
envProps.setProperty("i2p.dummyClientFacade", "true");
@ -73,18 +90,28 @@ public class SSUDemo {
envProps.setProperty("i2p.dummyTunnelManager", "true");
// set to false if you want to use HMAC-SHA256-128 instead of HMAC-MD5-128 as
// the SSU MAC
envProps.setProperty("i2p.HMACMD5", "true");
//envProps.setProperty("i2p.HMACMD5", "true");
// if you're using the HMAC MD5, by default it will use a 32 byte MAC field,
// which is a bug, as it doesn't generate the same values as a 16 byte MAC field.
// set this to false if you don't want the bug
envProps.setProperty("i2p.HMACBrokenSize", "false");
//envProps.setProperty("i2p.HMACBrokenSize", "false");
// no need to include any stats in the routerInfo we send to people on SSU
// session establishment
envProps.setProperty("router.publishPeerRankings", "false");
// write the logs to ./logs/log-router-*.txt (logger configured with the file
// ./logger.config, or another config file specified as
// -Dlogger.configLocation=blah)
envProps.setProperty("loggerFilenameOverride", "logs/log-router-@.txt");
// avoid conflicts over log
envProps.setProperty("loggerFilenameOverride", "logs/log-router-" + port + "-@.txt");
System.setProperty("wrapper.logfile", "wrapper-" + port + ".log");
// avoid conflicts over key backup etc. so we don't all use the same keys
envProps.setProperty("router.keyBackupDir", "keyBackup/router-" + port);
envProps.setProperty("router.info.location", "router-" + port + ".info");
envProps.setProperty("router.keys.location", "router-" + port + ".keys");
envProps.setProperty("router.configLocation", "router-" + port + ".config");
envProps.setProperty("router.pingFile", "router-" + port + ".ping");
// avoid conflicts over blockfile
envProps.setProperty("i2p.naming.impl", "net.i2p.client.naming.HostsTxtNamingService");
return envProps;
}
@ -98,14 +125,15 @@ public class SSUDemo {
}
/** random place for storing router info files - written as $dir/base64(SHA256(info.getIdentity)) */
private File getInfoDir() { return new File("/tmp/ssuDemoInfo/"); }
private static File getInfoDir() { return new File("/tmp/ssuDemoInfo/"); }
private void storeMyInfo(RouterInfo info) {
private static void storeMyInfo(RouterInfo info) {
File infoDir = getInfoDir();
if (!infoDir.exists())
infoDir.mkdirs();
FileOutputStream fos = null;
File infoFile = new File(infoDir, info.getIdentity().calculateHash().toBase64());
infoFile.deleteOnExit();
try {
fos = new FileOutputStream(infoFile);
info.writeBytes(fos);
@ -165,7 +193,8 @@ public class SSUDemo {
out.setPriority(100);
out.setTarget(ri);
FooMessage data = new FooMessage(_us, new byte[] { 0x0, 0x1, 0x2, 0x3 });
System.out.println("SEND: " + Base64.encode(data.getData()));
System.out.println("SEND: " + Base64.encode(data.getData()) + " to " +
ri.getIdentity().calculateHash());
out.setMessage(data);
// job fired if we can't contact them, or if it takes too long to get an ACK
out.setOnFailedSendJob(null);
@ -198,45 +227,57 @@ public class SSUDemo {
public FooJobBuilder() {
I2NPMessageImpl.registerBuilder(new FooBuilder(), FooMessage.MESSAGE_TYPE);
}
public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) {
return new FooHandleJob(_us, receivedMessage, from, fromHash);
}
}
private class FooHandleJob extends JobImpl {
private I2NPMessage _msg;
private static class FooHandleJob extends JobImpl {
private final I2NPMessage _msg;
private final Hash _from;
public FooHandleJob(RouterContext ctx, I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) {
super(ctx);
_msg = receivedMessage;
_from = fromHash;
}
public void runJob() {
// we know its a FooMessage, since thats the type of message that the handler
// is registered as
FooMessage m = (FooMessage)_msg;
System.out.println("RECV: " + Base64.encode(m.getData()));
System.out.println("RECV FooMessage: " + Base64.encode(m.getData()) + " from " + _from);
}
public String getName() { return "Handle Foo message"; }
}
private class FooBuilder implements I2NPMessageImpl.Builder {
private static class FooBuilder implements I2NPMessageImpl.Builder {
public I2NPMessage build(I2PAppContext ctx) { return new FooMessage(ctx, null); }
}
/**
* Just carry some data...
*/
class FooMessage extends I2NPMessageImpl {
private static class FooMessage extends I2NPMessageImpl {
private byte[] _data;
public static final int MESSAGE_TYPE = 17;
public FooMessage(I2PAppContext ctx, byte data[]) {
super(ctx);
_data = data;
}
/** pull the read data off */
public byte[] getData() { return _data; }
/** specify the payload to be sent */
public void setData(byte data[]) { _data = data; }
public int getType() { return MESSAGE_TYPE; }
protected int calculateWrittenLength() { return _data.length; }
public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException {
_data = new byte[dataSize];
System.arraycopy(data, offset, _data, 0, dataSize);
@ -260,22 +301,27 @@ public class SSUDemo {
return new HandleJob(_us, receivedMessage, from, fromHash);
}
}
private class HandleJob extends JobImpl {
private I2NPMessage _msg;
private final I2NPMessage _msg;
public HandleJob(RouterContext ctx, I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) {
super(ctx);
_msg = receivedMessage;
}
public void runJob() {
// we know its a DatabaseStoreMessage, since thats the type of message that the handler
// is registered as
DatabaseStoreMessage m = (DatabaseStoreMessage)_msg;
System.out.println("RECV: " + m);
try {
_us.netDb().store(m.getKey(), (RouterInfo) m.getEntry());
} catch (IllegalArgumentException iae) {
iae.printStackTrace();
}
}
public String getName() { return "Handle netDb store"; }
}
}

View File

@ -1,6 +1,8 @@
package net.i2p.router.transport.udp;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -18,11 +20,11 @@ import net.i2p.util.Log;
* --zab
*/
public class UDPEndpointTestStandalone {
private RouterContext _context;
private Log _log;
private final RouterContext _context;
private final Log _log;
private UDPEndpoint _endpoints[];
private boolean _beginTest;
private List _sentNotReceived;
private volatile boolean _beginTest;
private final List _sentNotReceived;
public UDPEndpointTestStandalone(RouterContext ctx) {
_context = ctx;
@ -33,12 +35,17 @@ public class UDPEndpointTestStandalone {
public void runTest(int numPeers) {
_log.debug("Run test("+numPeers+")");
_endpoints = new UDPEndpoint[numPeers];
int base = 2000 + _context.random().nextInt(10000);
int base = 44000 + _context.random().nextInt(10000);
for (int i = 0; i < numPeers; i++) {
_log.debug("Building " + i);
UDPEndpoint endpoint = new UDPEndpoint(_context, null, base + i, null);
_endpoints[i] = endpoint;
try {
endpoint.startup();
} catch (SocketException se) {
_log.error("die", se);
throw new RuntimeException(se);
}
I2PThread read = new I2PThread(new TestRead(endpoint), "Test read " + i);
I2PThread write = new I2PThread(new TestWrite(endpoint), "Test write " + i);
//read.setDaemon(true);
@ -51,7 +58,7 @@ public class UDPEndpointTestStandalone {
}
private class TestRead implements Runnable {
private UDPEndpoint _endpoint;
private final UDPEndpoint _endpoint;
public TestRead(UDPEndpoint peer) {
_endpoint = peer;
}
@ -83,7 +90,7 @@ public class UDPEndpointTestStandalone {
}
private class TestWrite implements Runnable {
private UDPEndpoint _endpoint;
private final UDPEndpoint _endpoint;
public TestWrite(UDPEndpoint peer) {
_endpoint = peer;
}
@ -92,8 +99,16 @@ public class UDPEndpointTestStandalone {
try { Thread.sleep(2000); } catch (InterruptedException ie) {}
}
try { Thread.sleep(2000); } catch (InterruptedException ie) {}
PacketBuilder builder = new PacketBuilder(_context, null);
InetAddress localhost = null;
try {
localhost = InetAddress.getLocalHost();
} catch (UnknownHostException uhe) {
_log.error("die", uhe);
System.exit(0);
}
_log.debug("Beginning to write");
for (int curPacket = 0; curPacket < 10000; curPacket++) {
for (int curPacket = 0; curPacket < 2000; curPacket++) {
byte data[] = new byte[1024];
_context.random().nextBytes(data);
int curPeer = (curPacket % _endpoints.length);
@ -101,27 +116,21 @@ public class UDPEndpointTestStandalone {
curPeer++;
if (curPeer >= _endpoints.length)
curPeer = 0;
short priority = 1;
long expiration = -1;
UDPPacket packet = UDPPacket.acquire(_context, true);
//try {
if (true) throw new RuntimeException("fixme");
//packet.initialize(priority, expiration, InetAddress.getLocalHost(), _endpoints[curPeer].getListenPort());
// Following method is commented out in UDPPacket
//packet.writeData(data, 0, 1024);
packet.getPacket().setLength(1024);
UDPPacket packet = builder.buildPacket(data, localhost, _endpoints[curPeer].getListenPort());
int outstanding = _sentNotReceived.size() + 1;
_sentNotReceived.add(new ByteArray(data, 0, 1024));
_log.debug("Sending packet " + curPacket + " with outstanding " + outstanding);
try {
_endpoint.send(packet);
//try { Thread.sleep(10); } catch (InterruptedException ie) {}
//} catch (UnknownHostException uhe) {
// _log.error("foo!", uhe);
//}
//if (_log.shouldLog(Log.DEBUG)) {
// _log.debug("Sent to " + _endpoints[curPeer].getListenPort() + " from " + _endpoint.getListenPort());
//}
} catch (Exception e) {
_log.error("die", e);
break;
}
try { Thread.sleep(3); } catch (InterruptedException ie) {}
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Sent to " + _endpoints[curPeer].getListenPort() + " from " + _endpoint.getListenPort());
}
_log.debug("Done sending packets");
try { Thread.sleep(10*1000); } catch (InterruptedException e) {}
System.exit(0);
}
@ -133,7 +142,16 @@ public class UDPEndpointTestStandalone {
Properties props = new Properties();
props.setProperty("stat.logFile", "udpEndpointTest.stats");
props.setProperty("stat.logFilters", "*");
UDPEndpointTestStandalone test = new UDPEndpointTestStandalone(new RouterContext(null, props));
props.setProperty("i2p.dummyClientFacade", "true");
props.setProperty("i2p.dummyNetDb", "true");
props.setProperty("i2p.dummyPeerManager", "true");
props.setProperty("i2p.dummyTunnelManager", "true");
props.setProperty("i2p.vmCommSystem", "true");
props.setProperty("i2np.bandwidth.inboundKBytesPerSecond", "9999");
props.setProperty("i2np.bandwidth.outboundKBytesPerSecond", "9999");
RouterContext ctx = new RouterContext(null, props);
ctx.initAll();
UDPEndpointTestStandalone test = new UDPEndpointTestStandalone(ctx);
test.runTest(2);
}
}