forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p' (head 2a2c708bf9fee43e69469bdf896dfe489c32bdea)
to branch 'i2p.i2p.zzz.ipv6' (head c33552d7026b0a445d1dd7e138bf454144130eb2)
This commit is contained in:
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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:
|
||||
|
18
build.xml
18
build.xml
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
9
installer/resources/geoipv6-extras.csv
Normal file
9
installer/resources/geoipv6-extras.csv
Normal 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.
|
BIN
installer/resources/geoipv6.dat.gz
Normal file
BIN
installer/resources/geoipv6.dat.gz
Normal file
Binary file not shown.
19
installer/resources/makegeoipv6.sh
Executable file
19
installer/resources/makegeoipv6.sh
Executable 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 $?
|
@ -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/"
|
||||
|
@ -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">
|
||||
|
@ -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%\"> </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%\"> </td></tr>\n");
|
||||
}
|
||||
}
|
||||
out.write("</table>");
|
||||
}
|
||||
if (_blocklistSize > 0) {
|
||||
@ -832,6 +900,23 @@ public class Blocklist {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a (non-negative) two's complement IP to exactly 16 bytes
|
||||
* @since IPv6
|
||||
*/
|
||||
private static byte[] toIPBytes(BigInteger bi) {
|
||||
byte[] ba = bi.toByteArray();
|
||||
int len = ba.length;
|
||||
if (len == 16)
|
||||
return ba;
|
||||
byte[] rv = new byte[16];
|
||||
if (len < 16)
|
||||
System.arraycopy(ba, 0, rv, 16 - len, len);
|
||||
else
|
||||
System.arraycopy(ba, len - 16, rv, 0, 16);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a string for extraction by xgettext and translation.
|
||||
* Use this only in static initializers.
|
||||
|
@ -28,8 +28,8 @@ public abstract class CommSystemFacade implements Service {
|
||||
public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException { }
|
||||
public void renderStatusHTML(Writer out) throws IOException { renderStatusHTML(out, null, 0); }
|
||||
|
||||
/** Create the set of RouterAddress structures based on the router's config */
|
||||
public Set<RouterAddress> createAddresses() { return Collections.EMPTY_SET; }
|
||||
/** Create the list of RouterAddress structures based on the router's config */
|
||||
public List<RouterAddress> createAddresses() { return Collections.EMPTY_LIST; }
|
||||
|
||||
public int countActivePeers() { return 0; }
|
||||
public int countActiveSendPeers() { return 0; }
|
||||
|
@ -91,8 +91,13 @@ public class MessageHistory {
|
||||
*/
|
||||
public synchronized void initialize(boolean forceReinitialize) {
|
||||
if (!forceReinitialize) return;
|
||||
Router router = _context.router();
|
||||
if (router == null) {
|
||||
// unit testing, presumably
|
||||
return;
|
||||
}
|
||||
|
||||
if (_context.router().getRouterInfo() == null) {
|
||||
if (router.getRouterInfo() == null) {
|
||||
_reinitializeJob.getTiming().setStartAfter(_context.clock().now() + 15*1000);
|
||||
_context.jobQueue().addJob(_reinitializeJob);
|
||||
} else {
|
||||
|
@ -77,4 +77,13 @@ public abstract class NetworkDatabaseFacade implements Service {
|
||||
|
||||
/** @since 0.9 */
|
||||
public ReseedChecker reseedChecker() { return null; };
|
||||
|
||||
/**
|
||||
* For convenience, so users don't have to cast to FNDF, and unit tests using
|
||||
* Dummy NDF will work.
|
||||
*
|
||||
* @return false; FNDF overrides to return actual setting
|
||||
* @since IPv6
|
||||
*/
|
||||
public boolean floodfillEnabled() { return false; };
|
||||
}
|
||||
|
@ -601,7 +601,7 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
}
|
||||
|
||||
// if prop set to true, don't tell people we are ff even if we are
|
||||
if (FloodfillNetworkDatabaseFacade.floodfillEnabled(_context) &&
|
||||
if (_context.netDb().floodfillEnabled() &&
|
||||
!_context.getBooleanProperty("router.hideFloodfillParticipant"))
|
||||
ri.addCapability(FloodfillNetworkDatabaseFacade.CAPABILITY_FLOODFILL);
|
||||
|
||||
|
@ -38,7 +38,8 @@ import net.i2p.util.I2PProperties.I2PPropertyCallback;
|
||||
*/
|
||||
public class RouterContext extends I2PAppContext {
|
||||
private final Router _router;
|
||||
private ClientManagerFacadeImpl _clientManagerFacade;
|
||||
private ClientManagerFacade _clientManagerFacade;
|
||||
private InternalClientManager _internalClientManager;
|
||||
private ClientMessagePool _clientMessagePool;
|
||||
private JobQueue _jobQueue;
|
||||
private InNetMessagePool _inNetMessagePool;
|
||||
@ -152,15 +153,29 @@ public class RouterContext extends I2PAppContext {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The following properties may be used to replace various parts
|
||||
* of the context with dummy implementations for testing, by setting
|
||||
* the property to "true":
|
||||
*<pre>
|
||||
* i2p.dummyClientFacade
|
||||
* i2p.dummyNetDb
|
||||
* i2p.dummyPeerManager
|
||||
* i2p.dummyTunnelManager
|
||||
* i2p.vmCommSystem (transport)
|
||||
*</pre>
|
||||
*/
|
||||
public synchronized void initAll() {
|
||||
if (_initialized)
|
||||
throw new IllegalStateException();
|
||||
if (getBooleanProperty("i2p.dummyClientFacade"))
|
||||
System.err.println("i2p.dummyClientFacade currently unsupported");
|
||||
_clientManagerFacade = new ClientManagerFacadeImpl(this);
|
||||
// removed since it doesn't implement InternalClientManager for now
|
||||
//else
|
||||
// _clientManagerFacade = new DummyClientManagerFacade(this);
|
||||
if (!getBooleanProperty("i2p.dummyClientFacade")) {
|
||||
ClientManagerFacadeImpl cmfi = new ClientManagerFacadeImpl(this);
|
||||
_clientManagerFacade = cmfi;
|
||||
_internalClientManager = cmfi;
|
||||
} else {
|
||||
_clientManagerFacade = new DummyClientManagerFacade(this);
|
||||
// internal client manager is null
|
||||
}
|
||||
_clientMessagePool = new ClientMessagePool(this);
|
||||
_jobQueue = new JobQueue(this);
|
||||
_inNetMessagePool = new InNetMessagePool(this);
|
||||
@ -168,23 +183,23 @@ public class RouterContext extends I2PAppContext {
|
||||
_messageHistory = new MessageHistory(this);
|
||||
_messageRegistry = new OutboundMessageRegistry(this);
|
||||
//_messageStateMonitor = new MessageStateMonitor(this);
|
||||
if ("false".equals(getProperty("i2p.dummyNetDb", "false")))
|
||||
if (!getBooleanProperty("i2p.dummyNetDb"))
|
||||
_netDb = new FloodfillNetworkDatabaseFacade(this); // new KademliaNetworkDatabaseFacade(this);
|
||||
else
|
||||
_netDb = new DummyNetworkDatabaseFacade(this);
|
||||
_keyManager = new KeyManager(this);
|
||||
if ("false".equals(getProperty("i2p.vmCommSystem", "false")))
|
||||
if (!getBooleanProperty("i2p.vmCommSystem"))
|
||||
_commSystem = new CommSystemFacadeImpl(this);
|
||||
else
|
||||
_commSystem = new VMCommSystem(this);
|
||||
_profileOrganizer = new ProfileOrganizer(this);
|
||||
if ("false".equals(getProperty("i2p.dummyPeerManager", "false")))
|
||||
if (!getBooleanProperty("i2p.dummyPeerManager"))
|
||||
_peerManagerFacade = new PeerManagerFacadeImpl(this);
|
||||
else
|
||||
_peerManagerFacade = new DummyPeerManagerFacade();
|
||||
_profileManager = new ProfileManagerImpl(this);
|
||||
_bandwidthLimiter = new FIFOBandwidthLimiter(this);
|
||||
if ("false".equals(getProperty("i2p.dummyTunnelManager", "false")))
|
||||
if (!getBooleanProperty("i2p.dummyTunnelManager"))
|
||||
_tunnelManager = new TunnelPoolManager(this);
|
||||
else
|
||||
_tunnelManager = new DummyTunnelManagerFacade();
|
||||
@ -529,7 +544,7 @@ public class RouterContext extends I2PAppContext {
|
||||
*/
|
||||
@Override
|
||||
public InternalClientManager internalClientManager() {
|
||||
return _clientManagerFacade;
|
||||
return _internalClientManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,7 +21,7 @@ public class RouterVersion {
|
||||
public final static long BUILD = 13;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
public final static String EXTRA = "-ipv6";
|
||||
public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA;
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Router version: " + FULL_VERSION);
|
||||
|
@ -38,7 +38,7 @@ class ExploreKeySelectorJob extends JobImpl {
|
||||
|
||||
public String getName() { return "Explore Key Selector Job"; }
|
||||
public void runJob() {
|
||||
if (((FloodfillNetworkDatabaseFacade)_facade).floodfillEnabled()) {
|
||||
if (_facade.floodfillEnabled()) {
|
||||
requeue(30*RERUN_DELAY_MS);
|
||||
return;
|
||||
}
|
||||
|
@ -261,10 +261,8 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean floodfillEnabled() { return _floodfillEnabled; }
|
||||
public static boolean floodfillEnabled(RouterContext ctx) {
|
||||
return ((FloodfillNetworkDatabaseFacade)ctx.netDb()).floodfillEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param peer may be null, returns false if null
|
||||
|
@ -303,7 +303,7 @@ class FloodfillPeerSelector extends PeerSelector {
|
||||
* @since 0.9.5 modified from ProfileOrganizer
|
||||
*/
|
||||
private Set<Integer> maskedIPSet(Hash peer, RouterInfo pinfo, int mask) {
|
||||
Set<Integer> rv = new HashSet(2);
|
||||
Set<Integer> rv = new HashSet(4);
|
||||
byte[] commIP = _context.commSystem().getIP(peer);
|
||||
if (commIP != null)
|
||||
rv.add(maskedIP(commIP, mask));
|
||||
@ -322,12 +322,22 @@ class FloodfillPeerSelector extends PeerSelector {
|
||||
|
||||
/**
|
||||
* generate an arbitrary unique value for this ip/mask (mask = 1-4)
|
||||
* If IPv6, force mask = 8.
|
||||
* @since 0.9.5 copied from ProfileOrganizer
|
||||
*/
|
||||
private static Integer maskedIP(byte[] ip, int mask) {
|
||||
int rv = 0;
|
||||
for (int i = 0; i < mask; i++)
|
||||
rv = (rv << 8) | (ip[i] & 0xff);
|
||||
int rv = ip[0];
|
||||
if (ip.length == 16) {
|
||||
for (int i = 1; i < 8; i++) {
|
||||
rv <<= i * 4;
|
||||
rv ^= ip[i];
|
||||
}
|
||||
} else {
|
||||
for (int i = 1; i < mask; i++) {
|
||||
rv <<= 8;
|
||||
rv ^= ip[i];
|
||||
}
|
||||
}
|
||||
return Integer.valueOf(rv);
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ public class HandleFloodfillDatabaseLookupMessageJob extends HandleDatabaseLooku
|
||||
*/
|
||||
@Override
|
||||
protected boolean answerAllQueries() {
|
||||
if (!FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext())) return false;
|
||||
if (!getContext().netDb().floodfillEnabled()) return false;
|
||||
return FloodfillNetworkDatabaseFacade.isFloodfill(getContext().router().getRouterInfo());
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ public class HandleFloodfillDatabaseLookupMessageJob extends HandleDatabaseLooku
|
||||
super.sendClosest(key, routerInfoSet, toPeer, replyTunnel);
|
||||
|
||||
// go away, you got the wrong guy, send our RI back unsolicited
|
||||
if (!FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext())) {
|
||||
if (!getContext().netDb().floodfillEnabled()) {
|
||||
// We could just call sendData(myhash, myri, toPeer, replyTunnel) but
|
||||
// that would increment the netDb.lookupsHandled and netDb.lookupsMatched stats
|
||||
DatabaseStoreMessage msg = new DatabaseStoreMessage(getContext());
|
||||
|
@ -187,7 +187,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
|
||||
// flood it
|
||||
if (invalidMessage == null &&
|
||||
FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext()) &&
|
||||
getContext().netDb().floodfillEnabled() &&
|
||||
_message.getReplyToken() > 0) {
|
||||
if (wasNew) {
|
||||
// DOS prevention
|
||||
|
@ -763,7 +763,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
// As the net grows this won't be sufficient, and we'll have to implement
|
||||
// flushing some from memory, while keeping all on disk.
|
||||
long adjustedExpiration;
|
||||
if (FloodfillNetworkDatabaseFacade.floodfillEnabled(_context))
|
||||
if (floodfillEnabled())
|
||||
adjustedExpiration = ROUTER_INFO_EXPIRATION_FLOODFILL;
|
||||
else
|
||||
// _kb.size() includes leasesets but that's ok
|
||||
|
@ -134,7 +134,7 @@ class SearchJob extends JobImpl {
|
||||
// The other two places this was called (one below and one in FNDF)
|
||||
// have been commented out.
|
||||
// Returning false essentially enables kademlia as a backup to floodfill for search responses.
|
||||
if (FloodfillNetworkDatabaseFacade.floodfillEnabled(ctx))
|
||||
if (ctx.netDb().floodfillEnabled())
|
||||
return false;
|
||||
return ctx.getProperty("netDb.floodfillOnly", DEFAULT_FLOODFILL_ONLY);
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class StartExplorersJob extends JobImpl {
|
||||
|
||||
public String getName() { return "Start Explorers Job"; }
|
||||
public void runJob() {
|
||||
if (! (((FloodfillNetworkDatabaseFacade)_facade).floodfillEnabled() ||
|
||||
if (! (_facade.floodfillEnabled() ||
|
||||
getContext().router().gracefulShutdownInProgress())) {
|
||||
int num = MAX_PER_RUN;
|
||||
if (_facade.getDataStore().size() < LOW_ROUTERS)
|
||||
@ -93,7 +93,7 @@ class StartExplorersJob extends JobImpl {
|
||||
*/
|
||||
private long getNextRunDelay() {
|
||||
// we don't explore if floodfill
|
||||
if (((FloodfillNetworkDatabaseFacade)_facade).floodfillEnabled())
|
||||
if (_facade.floodfillEnabled())
|
||||
return MAX_RERUN_DELAY_MS;
|
||||
|
||||
// If we don't know too many peers, or just started, explore aggressively
|
||||
|
@ -1264,7 +1264,7 @@ public class ProfileOrganizer {
|
||||
* @return an opaque set of masked IPs for this peer
|
||||
*/
|
||||
private Set<Integer> maskedIPSet(Hash peer, int mask) {
|
||||
Set<Integer> rv = new HashSet(2);
|
||||
Set<Integer> rv = new HashSet(4);
|
||||
byte[] commIP = _context.commSystem().getIP(peer);
|
||||
if (commIP != null)
|
||||
rv.add(maskedIP(commIP, mask));
|
||||
@ -1282,11 +1282,23 @@ public class ProfileOrganizer {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** generate an arbitrary unique value for this ip/mask (mask = 1-4) */
|
||||
/**
|
||||
* generate an arbitrary unique value for this ip/mask (mask = 1-4)
|
||||
* If IPv6, force mask = 8.
|
||||
*/
|
||||
private static Integer maskedIP(byte[] ip, int mask) {
|
||||
int rv = 0;
|
||||
for (int i = 0; i < mask; i++)
|
||||
rv = (rv << 8) | (ip[i] & 0xff);
|
||||
int rv = ip[0];
|
||||
if (ip.length == 16) {
|
||||
for (int i = 1; i < 8; i++) {
|
||||
rv <<= i * 4;
|
||||
rv ^= ip[i];
|
||||
}
|
||||
} else {
|
||||
for (int i = 1; i < mask; i++) {
|
||||
rv <<= 8;
|
||||
rv ^= ip[i];
|
||||
}
|
||||
}
|
||||
return Integer.valueOf(rv);
|
||||
}
|
||||
|
||||
|
@ -10,15 +10,14 @@ package net.i2p.router.transport;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
@ -27,9 +26,6 @@ import net.i2p.data.RouterInfo;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.ntcp.NTCPAddress;
|
||||
import net.i2p.router.transport.ntcp.NTCPTransport;
|
||||
import net.i2p.router.transport.udp.UDPAddress;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.Log;
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
344
router/java/src/net/i2p/router/transport/GeoIPv6.java
Normal file
344
router/java/src/net/i2p/router/transport/GeoIPv6.java
Normal 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));
|
||||
}
|
||||
}
|
@ -23,22 +23,25 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
class GetBidsJob extends JobImpl {
|
||||
private final Log _log;
|
||||
private final CommSystemFacadeImpl _facade;
|
||||
private final TransportManager _tmgr;
|
||||
private final OutNetMessage _msg;
|
||||
|
||||
public GetBidsJob(RouterContext ctx, CommSystemFacadeImpl facade, OutNetMessage msg) {
|
||||
/**
|
||||
* @deprecated unused, see static getBids()
|
||||
*/
|
||||
public GetBidsJob(RouterContext ctx, TransportManager tmgr, OutNetMessage msg) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(GetBidsJob.class);
|
||||
_facade = facade;
|
||||
_tmgr = tmgr;
|
||||
_msg = msg;
|
||||
}
|
||||
|
||||
public String getName() { return "Fetch bids for a message to be delivered"; }
|
||||
public void runJob() {
|
||||
getBids(getContext(), _facade, _msg);
|
||||
getBids(getContext(), _tmgr, _msg);
|
||||
}
|
||||
|
||||
static void getBids(RouterContext context, CommSystemFacadeImpl facade, OutNetMessage msg) {
|
||||
static void getBids(RouterContext context, TransportManager tmgr, OutNetMessage msg) {
|
||||
Log log = context.logManager().getLog(GetBidsJob.class);
|
||||
Hash to = msg.getTarget().getIdentity().getHash();
|
||||
msg.timestamp("bid");
|
||||
@ -61,14 +64,14 @@ class GetBidsJob extends JobImpl {
|
||||
return;
|
||||
}
|
||||
|
||||
TransportBid bid = facade.getNextBid(msg);
|
||||
TransportBid bid = tmgr.getNextBid(msg);
|
||||
if (bid == null) {
|
||||
int failedCount = msg.getFailedTransports().size();
|
||||
if (failedCount == 0) {
|
||||
context.statManager().addRateData("transport.bidFailNoTransports", msg.getLifetime());
|
||||
// This used to be "no common transports" but it is almost always no transports at all
|
||||
context.banlist().banlistRouter(to, _x("No transports (hidden or starting up?)"));
|
||||
} else if (failedCount >= facade.getTransportCount()) {
|
||||
} else if (failedCount >= tmgr.getTransportCount()) {
|
||||
context.statManager().addRateData("transport.bidFailAllTransports", msg.getLifetime());
|
||||
// fail after all transports were unsuccessful
|
||||
context.netDb().fail(to);
|
||||
@ -82,7 +85,7 @@ class GetBidsJob extends JobImpl {
|
||||
}
|
||||
|
||||
|
||||
private static void fail(RouterContext context, OutNetMessage msg) {
|
||||
static void fail(RouterContext context, OutNetMessage msg) {
|
||||
if (msg.getOnFailedSendJob() != null) {
|
||||
context.jobQueue().addJob(msg.getOnFailedSendJob());
|
||||
}
|
||||
|
@ -32,17 +32,90 @@ public interface Transport {
|
||||
*
|
||||
*/
|
||||
public void send(OutNetMessage msg);
|
||||
public RouterAddress startListening();
|
||||
public void startListening();
|
||||
public void stopListening();
|
||||
public RouterAddress getCurrentAddress();
|
||||
public RouterAddress updateAddress();
|
||||
public static final String SOURCE_UPNP = "upnp";
|
||||
public static final String SOURCE_INTERFACE = "local";
|
||||
public static final String SOURCE_CONFIG = "config"; // unused
|
||||
public void externalAddressReceived(String source, byte[] ip, int port);
|
||||
public void forwardPortStatus(int port, int externalPort, boolean success, String reason);
|
||||
|
||||
/**
|
||||
* What addresses are we currently listening to?
|
||||
* Replaces getCurrentAddress()
|
||||
* @return all addresses, non-null
|
||||
* @since IPv6
|
||||
*/
|
||||
public List<RouterAddress> getCurrentAddresses();
|
||||
|
||||
/**
|
||||
* Do we have any current address?
|
||||
* @since IPv6
|
||||
*/
|
||||
public boolean hasCurrentAddress();
|
||||
|
||||
/**
|
||||
* Ask the transport to update its addresses based on current information and return them
|
||||
* @return all addresses, non-null
|
||||
*/
|
||||
public List<RouterAddress> updateAddress();
|
||||
|
||||
/**
|
||||
* @since IPv6
|
||||
*/
|
||||
public enum AddressSource {
|
||||
SOURCE_UPNP("upnp"),
|
||||
SOURCE_INTERFACE("local"),
|
||||
/** unused */
|
||||
SOURCE_CONFIG("config"),
|
||||
SOURCE_SSU("ssu");
|
||||
|
||||
private final String cfgstr;
|
||||
|
||||
AddressSource(String cfgstr) {
|
||||
this.cfgstr = cfgstr;
|
||||
}
|
||||
|
||||
public String toConfigString() {
|
||||
return cfgstr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify a transport of an external address change.
|
||||
* This may be from a local interface, UPnP, a config change, etc.
|
||||
* This should not be called if the ip didn't change
|
||||
* (from that source's point of view), or is a local address.
|
||||
* May be called multiple times for IPv4 or IPv6.
|
||||
* The transport should also do its own checking on whether to accept
|
||||
* notifications from this source.
|
||||
*
|
||||
* This can be called before startListening() to set an initial address,
|
||||
* or after the transport is running.
|
||||
*
|
||||
* @param source defined in Transport.java
|
||||
* @param ip typ. IPv4 or IPv6 non-local; may be null to indicate IPv4 failure or port info only
|
||||
* @param port 0 for unknown or unchanged
|
||||
*/
|
||||
public void externalAddressReceived(AddressSource source, byte[] ip, int port);
|
||||
|
||||
/**
|
||||
* Notify a transport of the results of trying to forward a port.
|
||||
*
|
||||
* @param ip may be null
|
||||
* @param port the internal port
|
||||
* @param externalPort the external port, which for now should always be the same as
|
||||
* the internal port if the forwarding was successful.
|
||||
*/
|
||||
public void forwardPortStatus(byte[] ip, int port, int externalPort, boolean success, String reason);
|
||||
|
||||
/**
|
||||
* What INTERNAL port would the transport like to have forwarded by UPnP.
|
||||
* This can't be passed via getCurrentAddress(), as we have to open the port
|
||||
* before we can publish the address, and that's the external port anyway.
|
||||
*
|
||||
* @return port or -1 for none or 0 for any
|
||||
*/
|
||||
public int getRequestedPort();
|
||||
|
||||
/** Who to notify on message availability */
|
||||
public void setListener(TransportEventListener listener);
|
||||
|
||||
public String getStyle();
|
||||
|
||||
public int countPeers();
|
||||
@ -51,12 +124,17 @@ public interface Transport {
|
||||
public boolean haveCapacity();
|
||||
public boolean haveCapacity(int pct);
|
||||
public Vector getClockSkews();
|
||||
public List getMostRecentErrorMessages();
|
||||
public List<String> getMostRecentErrorMessages();
|
||||
|
||||
public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException;
|
||||
public short getReachabilityStatus();
|
||||
public void recheckReachability();
|
||||
public boolean isBacklogged(Hash dest);
|
||||
|
||||
/**
|
||||
* Was the peer UNreachable (outbound only) the last time we tried it?
|
||||
* This is NOT reset if the peer contacts us and it is never expired.
|
||||
*/
|
||||
public boolean wasUnreachable(Hash dest);
|
||||
|
||||
public boolean isUnreachable(Hash peer);
|
||||
|
@ -10,8 +10,11 @@ package net.i2p.router.transport;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@ -23,6 +26,7 @@ import java.util.Vector;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
@ -36,7 +40,6 @@ import net.i2p.router.MessageSelector;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.LHMCache;
|
||||
import net.i2p.util.Log;
|
||||
@ -51,13 +54,14 @@ import net.i2p.util.SystemVersion;
|
||||
public abstract class TransportImpl implements Transport {
|
||||
private final Log _log;
|
||||
private TransportEventListener _listener;
|
||||
private RouterAddress _currentAddress;
|
||||
protected final List<RouterAddress> _currentAddresses;
|
||||
// Only used by NTCP. SSU does not use. See send() below.
|
||||
private final BlockingQueue<OutNetMessage> _sendPool;
|
||||
protected final RouterContext _context;
|
||||
/** map from routerIdentHash to timestamp (Long) that the peer was last unreachable */
|
||||
private final Map<Hash, Long> _unreachableEntries;
|
||||
private final Set<Hash> _wasUnreachableEntries;
|
||||
private final Set<InetAddress> _localAddresses;
|
||||
/** global router ident -> IP */
|
||||
private static final Map<Hash, byte[]> _IPMap;
|
||||
|
||||
@ -88,12 +92,15 @@ public abstract class TransportImpl implements Transport {
|
||||
_context.statManager().createRequiredRateStat("transport.sendProcessingTime", "Time to process and send a message (ms)", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
//_context.statManager().createRateStat("transport.sendProcessingTime." + getStyle(), "Time to process and send a message (ms)", "Transport", new long[] { 60*1000l });
|
||||
_context.statManager().createRateStat("transport.expiredOnQueueLifetime", "How long a message that expires on our outbound queue is processed", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l } );
|
||||
|
||||
_currentAddresses = new CopyOnWriteArrayList();
|
||||
if (getStyle().equals("NTCP"))
|
||||
_sendPool = new ArrayBlockingQueue(8);
|
||||
else
|
||||
_sendPool = null;
|
||||
_unreachableEntries = new HashMap(16);
|
||||
_wasUnreachableEntries = new ConcurrentHashSet(16);
|
||||
_localAddresses = new ConcurrentHashSet(4);
|
||||
_context.simpleScheduler().addPeriodicEvent(new CleanupUnreachable(), 2 * UNREACHABLE_PERIOD, UNREACHABLE_PERIOD / 2);
|
||||
}
|
||||
|
||||
@ -119,6 +126,9 @@ public abstract class TransportImpl implements Transport {
|
||||
|
||||
/** Per-transport connection limit */
|
||||
public int getMaxConnections() {
|
||||
if (_context.commSystem().isDummy())
|
||||
// testing
|
||||
return 0;
|
||||
String style = getStyle();
|
||||
// object churn
|
||||
String maxProp;
|
||||
@ -135,7 +145,7 @@ public abstract class TransportImpl implements Transport {
|
||||
if (bw > Router.CAPABILITY_BW12 && bw <= Router.CAPABILITY_BW256)
|
||||
def *= (1 + bw - Router.CAPABILITY_BW12);
|
||||
}
|
||||
if (((FloodfillNetworkDatabaseFacade)_context.netDb()).floodfillEnabled()) {
|
||||
if (_context.netDb().floodfillEnabled()) {
|
||||
// && !SystemVersion.isWindows()) {
|
||||
def *= 17; def /= 10; // 425 for Class O ff
|
||||
}
|
||||
@ -167,7 +177,7 @@ public abstract class TransportImpl implements Transport {
|
||||
*/
|
||||
public Vector getClockSkews() { return new Vector(); }
|
||||
|
||||
public List getMostRecentErrorMessages() { return Collections.EMPTY_LIST; }
|
||||
public List<String> getMostRecentErrorMessages() { return Collections.EMPTY_LIST; }
|
||||
|
||||
/**
|
||||
* Nonblocking call to pull the next outbound message
|
||||
@ -463,63 +473,201 @@ public abstract class TransportImpl implements Transport {
|
||||
}
|
||||
|
||||
/** Do we increase the advertised cost when approaching conn limits? */
|
||||
public static final boolean ADJUST_COST = true;
|
||||
protected static final boolean ADJUST_COST = true;
|
||||
/** TODO change to 2 */
|
||||
protected static final int CONGESTION_COST_ADJUSTMENT = 1;
|
||||
|
||||
/** What addresses are we currently listening to? */
|
||||
public RouterAddress getCurrentAddress() {
|
||||
return _currentAddress;
|
||||
/**
|
||||
* What addresses are we currently listening to?
|
||||
* Replaces getCurrentAddress()
|
||||
* @return all addresses, non-null
|
||||
* @since IPv6
|
||||
*/
|
||||
public List<RouterAddress> getCurrentAddresses() {
|
||||
return _currentAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* What address are we currently listening to?
|
||||
* Replaces getCurrentAddress()
|
||||
* @param ipv6 true for IPv6 only; false for IPv4 only
|
||||
* @return first matching address or null
|
||||
* @since IPv6
|
||||
*/
|
||||
public RouterAddress getCurrentAddress(boolean ipv6) {
|
||||
for (RouterAddress ra : _currentAddresses) {
|
||||
if (ipv6 == TransportUtil.isIPv6(ra))
|
||||
return ra;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do we have any current address?
|
||||
* @since IPv6
|
||||
*/
|
||||
public boolean hasCurrentAddress() {
|
||||
return !_currentAddresses.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the transport to update its address based on current information and return it
|
||||
* Transports should override.
|
||||
* @return all addresses, non-null
|
||||
* @since 0.7.12
|
||||
*/
|
||||
public RouterAddress updateAddress() {
|
||||
return _currentAddress;
|
||||
public List<RouterAddress> updateAddress() {
|
||||
return _currentAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace any existing addresses for the current transport with the given
|
||||
* one.
|
||||
* Replace any existing addresses for the current transport
|
||||
* with the same IP length (4 or 16) with the given one.
|
||||
* TODO: Allow multiple addresses of the same length.
|
||||
* Calls listener.transportAddressChanged()
|
||||
*
|
||||
* @param address null to remove all
|
||||
*/
|
||||
protected void replaceAddress(RouterAddress address) {
|
||||
// _log.error("Replacing address for " + getStyle() + " was " + _currentAddress + " now " + address);
|
||||
_currentAddress = address;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Replacing address with " + address, new Exception());
|
||||
if (address == null) {
|
||||
_currentAddresses.clear();
|
||||
} else {
|
||||
boolean isIPv6 = TransportUtil.isIPv6(address);
|
||||
for (RouterAddress ra : _currentAddresses) {
|
||||
if (isIPv6 == TransportUtil.isIPv6(ra))
|
||||
_currentAddresses.remove(ra);
|
||||
}
|
||||
_currentAddresses.add(address);
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getStyle() + " now has " + _currentAddresses.size() + " addresses");
|
||||
if (_listener != null)
|
||||
_listener.transportAddressChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a local address we were notified about before we started.
|
||||
*
|
||||
* @since IPv6
|
||||
*/
|
||||
protected void saveLocalAddress(InetAddress address) {
|
||||
_localAddresses.add(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return and then clear all saved local addresses.
|
||||
*
|
||||
* @since IPv6
|
||||
*/
|
||||
protected Collection<InetAddress> getSavedLocalAddresses() {
|
||||
List<InetAddress> rv = new ArrayList(_localAddresses);
|
||||
_localAddresses.clear();
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available address we can use,
|
||||
* shuffled and then sorted by cost/preference.
|
||||
* Lowest cost (most preferred) first.
|
||||
* @return non-null, possibly empty
|
||||
* @since IPv6
|
||||
*/
|
||||
protected List<RouterAddress> getTargetAddresses(RouterInfo target) {
|
||||
List<RouterAddress> rv = target.getTargetAddresses(getStyle());
|
||||
// Shuffle so everybody doesn't use the first one
|
||||
if (rv.size() > 1) {
|
||||
Collections.shuffle(rv, _context.random());
|
||||
TransportUtil.IPv6Config config = getIPv6Config();
|
||||
int adj;
|
||||
switch (config) {
|
||||
case IPV6_DISABLED:
|
||||
adj = 10; break;
|
||||
case IPV6_NOT_PREFERRED:
|
||||
adj = 1; break;
|
||||
default:
|
||||
case IPV6_ENABLED:
|
||||
adj = 0; break;
|
||||
case IPV6_PREFERRED:
|
||||
adj = -1; break;
|
||||
case IPV6_ONLY:
|
||||
adj = -10; break;
|
||||
}
|
||||
Collections.sort(rv, new AddrComparator(adj));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare based on published cost, adjusting for our IPv6 preference.
|
||||
* Lowest cost (most preferred) first.
|
||||
* @since IPv6
|
||||
*/
|
||||
private static class AddrComparator implements Comparator<RouterAddress> {
|
||||
private final int adj;
|
||||
|
||||
public AddrComparator(int ipv6Adjustment) {
|
||||
adj = ipv6Adjustment;
|
||||
}
|
||||
|
||||
public int compare(RouterAddress l, RouterAddress r) {
|
||||
int lc = l.getCost();
|
||||
int rc = r.getCost();
|
||||
byte[] lip = l.getIP();
|
||||
byte[] rip = r.getIP();
|
||||
if (lip == null)
|
||||
lc += 20;
|
||||
else if (lip.length == 16)
|
||||
lc += adj;
|
||||
if (rip == null)
|
||||
rc += 20;
|
||||
else if (rip.length == 16)
|
||||
rc += adj;
|
||||
if (lc > rc)
|
||||
return 1;
|
||||
if (lc < rc)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify a transport of an external address change.
|
||||
* This may be from a local interface, UPnP, a config change, etc.
|
||||
* This should not be called if the ip didn't change
|
||||
* (from that source's point of view), or is a local address,
|
||||
* or if the ip is IPv6, but the transport should check anyway.
|
||||
* (from that source's point of view), or is a local address.
|
||||
* May be called multiple times for IPv4 or IPv6.
|
||||
* The transport should also do its own checking on whether to accept
|
||||
* notifications from this source.
|
||||
*
|
||||
* This can be called before startListening() to set an initial address,
|
||||
* or after the transport is running.
|
||||
*
|
||||
* This implementation does nothing. Transports should override if they want notification.
|
||||
*
|
||||
* @param source defined in Transport.java
|
||||
* @param ip typ. IPv4 non-local
|
||||
* @param ip typ. IPv4 or IPv6 non-local; may be null to indicate IPv4 failure or port info only
|
||||
* @param port 0 for unknown or unchanged
|
||||
*/
|
||||
public void externalAddressReceived(String source, byte[] ip, int port) {}
|
||||
public void externalAddressReceived(AddressSource source, byte[] ip, int port) {}
|
||||
|
||||
/**
|
||||
* Notify a transport of the results of trying to forward a port.
|
||||
*
|
||||
* This implementation does nothing. Transports should override if they want notification.
|
||||
*
|
||||
* @param ip may be null
|
||||
* @param port the internal port
|
||||
* @param externalPort the external port, which for now should always be the same as
|
||||
* the internal port if the forwarding was successful.
|
||||
*/
|
||||
public void forwardPortStatus(int port, int externalPort, boolean success, String reason) {}
|
||||
public void forwardPortStatus(byte[] ip, int port, int externalPort, boolean success, String reason) {}
|
||||
|
||||
/**
|
||||
* What port would the transport like to have forwarded by UPnP.
|
||||
* What INTERNAL port would the transport like to have forwarded by UPnP.
|
||||
* This can't be passed via getCurrentAddress(), as we have to open the port
|
||||
* before we can publish the address.
|
||||
* before we can publish the address, and that's the external port anyway.
|
||||
*
|
||||
* @return port or -1 for none or 0 for any
|
||||
*/
|
||||
@ -531,13 +679,13 @@ public abstract class TransportImpl implements Transport {
|
||||
public void renderStatusHTML(Writer out) throws IOException {}
|
||||
public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException { renderStatusHTML(out); }
|
||||
|
||||
public RouterContext getContext() { return _context; }
|
||||
public short getReachabilityStatus() { return CommSystemFacade.STATUS_UNKNOWN; }
|
||||
public void recheckReachability() {}
|
||||
public boolean isBacklogged(Hash dest) { return false; }
|
||||
public boolean isEstablished(Hash dest) { return false; }
|
||||
|
||||
private static final long UNREACHABLE_PERIOD = 5*60*1000;
|
||||
|
||||
public boolean isUnreachable(Hash peer) {
|
||||
long now = _context.clock().now();
|
||||
synchronized (_unreachableEntries) {
|
||||
@ -551,6 +699,7 @@ public abstract class TransportImpl implements Transport {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** called when we can't reach a peer */
|
||||
/** This isn't very useful since it is cleared when they contact us */
|
||||
public void markUnreachable(Hash peer) {
|
||||
@ -560,6 +709,7 @@ public abstract class TransportImpl implements Transport {
|
||||
}
|
||||
markWasUnreachable(peer, true);
|
||||
}
|
||||
|
||||
/** called when we establish a peer connection (outbound or inbound) */
|
||||
public void markReachable(Hash peer, boolean isInbound) {
|
||||
// if *some* transport can reach them, then we shouldn't banlist 'em
|
||||
@ -605,9 +755,15 @@ public abstract class TransportImpl implements Transport {
|
||||
else
|
||||
_wasUnreachableEntries.remove(peer);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(this.getStyle() + " setting wasUnreachable to " + yes + " for " + peer);
|
||||
_log.info(this.getStyle() + " setting wasUnreachable to " + yes + " for " + peer,
|
||||
yes ? new Exception() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* IP of the peer from the last connection (in or out, any transport).
|
||||
*
|
||||
* @param IPv4 or IPv6, non-null
|
||||
*/
|
||||
public void setIP(Hash peer, byte[] ip) {
|
||||
byte[] old;
|
||||
synchronized (_IPMap) {
|
||||
@ -617,6 +773,11 @@ public abstract class TransportImpl implements Transport {
|
||||
_context.commSystem().queueLookup(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* IP of the peer from the last connection (in or out, any transport).
|
||||
*
|
||||
* @return IPv4 or IPv6 or null
|
||||
*/
|
||||
public static byte[] getIP(Hash peer) {
|
||||
synchronized (_IPMap) {
|
||||
return _IPMap.get(peer);
|
||||
@ -632,26 +793,20 @@ public abstract class TransportImpl implements Transport {
|
||||
}
|
||||
}
|
||||
|
||||
/** @param addr non-null */
|
||||
public static boolean isPubliclyRoutable(byte addr[]) {
|
||||
if (addr.length == 4) {
|
||||
int a0 = addr[0] & 0xFF;
|
||||
if (a0 == 127) return false;
|
||||
if (a0 == 10) return false;
|
||||
int a1 = addr[1] & 0xFF;
|
||||
if (a0 == 172 && a1 >= 16 && a1 <= 31) return false;
|
||||
if (a0 == 192 && a1 == 168) return false;
|
||||
if (a0 >= 224) return false; // no multicast
|
||||
if (a0 == 0) return false;
|
||||
if (a0 == 169 && a1 == 254) return false;
|
||||
// 5/8 allocated to RIPE (30 November 2010)
|
||||
//if ((addr[0]&0xFF) == 5) return false; // Hamachi
|
||||
return true; // or at least possible to be true
|
||||
} else if (addr.length == 16) {
|
||||
return false;
|
||||
} else {
|
||||
// ipv?
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @since IPv6
|
||||
*/
|
||||
protected TransportUtil.IPv6Config getIPv6Config() {
|
||||
return TransportUtil.getIPv6Config(_context, getStyle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows IPv6 only if the transport is configured for it.
|
||||
* Caller must check if we actually have a public IPv6 address.
|
||||
* @param addr non-null
|
||||
*/
|
||||
protected boolean isPubliclyRoutable(byte addr[]) {
|
||||
return TransportUtil.isPubliclyRoutable(addr,
|
||||
getIPv6Config() != TransportUtil.IPv6Config.IPV6_DISABLED);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -29,6 +30,7 @@ import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import static net.i2p.router.transport.Transport.AddressSource.*;
|
||||
import net.i2p.router.transport.crypto.DHSessionKeyBuilder;
|
||||
import net.i2p.router.transport.ntcp.NTCPTransport;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
@ -85,13 +87,24 @@ public class TransportManager implements TransportEventListener {
|
||||
|
||||
private void configTransports() {
|
||||
boolean enableUDP = _context.getBooleanPropertyDefaultTrue(PROP_ENABLE_UDP);
|
||||
Transport udp = null;
|
||||
if (enableUDP) {
|
||||
UDPTransport udp = new UDPTransport(_context, _dhThread);
|
||||
udp = new UDPTransport(_context, _dhThread);
|
||||
addTransport(udp);
|
||||
initializeAddress(udp);
|
||||
}
|
||||
if (isNTCPEnabled(_context))
|
||||
addTransport(new NTCPTransport(_context, _dhThread));
|
||||
if (isNTCPEnabled(_context)) {
|
||||
Transport ntcp = new NTCPTransport(_context, _dhThread);
|
||||
addTransport(ntcp);
|
||||
initializeAddress(ntcp);
|
||||
if (udp != null) {
|
||||
// pass along the port SSU is probably going to use
|
||||
// so that NTCP may bind early
|
||||
int port = udp.getRequestedPort();
|
||||
if (port > 0)
|
||||
ntcp.externalAddressReceived(SOURCE_CONFIG, null, port);
|
||||
}
|
||||
}
|
||||
if (_transports.isEmpty())
|
||||
_log.log(Log.CRIT, "No transports are enabled");
|
||||
}
|
||||
@ -100,40 +113,44 @@ public class TransportManager implements TransportEventListener {
|
||||
return ctx.getBooleanPropertyDefaultTrue(PROP_ENABLE_NTCP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify transport of ALL routable interface addresses, including IPv6.
|
||||
* It's the transport's job to ignore what it can't handle.
|
||||
*/
|
||||
private void initializeAddress(Transport t) {
|
||||
String ips = Addresses.getAnyAddress();
|
||||
if (ips == null)
|
||||
return;
|
||||
InetAddress ia;
|
||||
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)
|
||||
|
131
router/java/src/net/i2p/router/transport/TransportUtil.java
Normal file
131
router/java/src/net/i2p/router/transport/TransportUtil.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
import static net.i2p.router.transport.Transport.AddressSource.SOURCE_UPNP;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Translate;
|
||||
@ -25,6 +26,7 @@ import org.freenetproject.ForwardPortStatus;
|
||||
* Bridge from the I2P RouterAddress data structure to
|
||||
* the freenet data structures
|
||||
*
|
||||
* @since 0.7.4
|
||||
* @author zzz
|
||||
*/
|
||||
class UPnPManager {
|
||||
@ -106,7 +108,7 @@ class UPnPManager {
|
||||
* which can have multiple UPnP threads running at once, but
|
||||
* that should be ok.
|
||||
*/
|
||||
public void update(Map<String, Integer> ports) {
|
||||
public void update(Set<TransportManager.Port> ports) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("UPnP Update with " + ports.size() + " ports");
|
||||
|
||||
@ -121,9 +123,9 @@ class UPnPManager {
|
||||
//}
|
||||
|
||||
Set<ForwardPort> forwards = new HashSet(ports.size());
|
||||
for (Map.Entry<String, Integer> entry : ports.entrySet()) {
|
||||
String style = entry.getKey();
|
||||
int port = entry.getValue().intValue();
|
||||
for (TransportManager.Port entry : ports) {
|
||||
String style = entry.style;
|
||||
int port = entry.port;
|
||||
int protocol = -1;
|
||||
if ("SSU".equals(style))
|
||||
protocol = ForwardPort.PROTOCOL_UDP_IPV4;
|
||||
@ -151,17 +153,19 @@ class UPnPManager {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("UPnP Callback:");
|
||||
|
||||
byte[] ipaddr = null;
|
||||
DetectedIP[] ips = _upnp.getAddress();
|
||||
if (ips != null) {
|
||||
for (DetectedIP ip : ips) {
|
||||
// store the first public one and tell the transport manager if it changed
|
||||
if (TransportImpl.isPubliclyRoutable(ip.publicAddress.getAddress())) {
|
||||
if (TransportUtil.isPubliclyRoutable(ip.publicAddress.getAddress(), false)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("External address: " + ip.publicAddress + " type: " + ip.natType);
|
||||
if (!ip.publicAddress.equals(_detectedAddress)) {
|
||||
_detectedAddress = ip.publicAddress;
|
||||
_manager.externalAddressReceived(Transport.SOURCE_UPNP, _detectedAddress.getAddress(), 0);
|
||||
_manager.externalAddressReceived(SOURCE_UPNP, _detectedAddress.getAddress(), 0);
|
||||
}
|
||||
ipaddr = ip.publicAddress.getAddress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -184,7 +188,7 @@ class UPnPManager {
|
||||
else
|
||||
continue;
|
||||
boolean success = fps.status >= ForwardPortStatus.MAYBE_SUCCESS;
|
||||
_manager.forwardPortStatus(style, fp.portNumber, fps.externalPort, success, fps.reasonString);
|
||||
_manager.forwardPortStatus(style, ipaddr, fp.portNumber, fps.externalPort, success, fps.reasonString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -261,8 +261,8 @@ class EstablishState {
|
||||
|
||||
// ok, we are onto the encrypted area
|
||||
while (src.hasRemaining() && !_corrupt) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"Encrypted bytes available (" + src.hasRemaining() + ")");
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix()+"Encrypted bytes available (" + src.hasRemaining() + ")");
|
||||
while (_curEncryptedOffset < _curEncrypted.length && src.hasRemaining()) {
|
||||
_curEncrypted[_curEncryptedOffset++] = src.get();
|
||||
_received++;
|
||||
@ -299,8 +299,8 @@ class EstablishState {
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")");
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")");
|
||||
|
||||
if (_sz_aliceIdent_tsA_padding_aliceSig.size() >= _sz_aliceIdent_tsA_padding_aliceSigSize) {
|
||||
verifyInbound();
|
||||
|
@ -20,6 +20,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.RouterContext;
|
||||
@ -779,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());
|
||||
|
@ -1,134 +0,0 @@
|
||||
package net.i2p.router.transport.ntcp;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.router.transport.TransportImpl;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Wrap up an address
|
||||
*/
|
||||
public class NTCPAddress {
|
||||
private final int _port;
|
||||
private final String _host;
|
||||
//private InetAddress _addr;
|
||||
/** Port number used in RouterAddress definitions */
|
||||
public final static String PROP_PORT = RouterAddress.PROP_PORT;
|
||||
/** Host name used in RouterAddress definitions */
|
||||
public final static String PROP_HOST = RouterAddress.PROP_HOST;
|
||||
public static final int DEFAULT_COST = 10;
|
||||
|
||||
public NTCPAddress(String host, int port) {
|
||||
if (host != null)
|
||||
_host = host.trim();
|
||||
else
|
||||
_host = null;
|
||||
_port = port;
|
||||
}
|
||||
|
||||
/*
|
||||
public NTCPAddress() {
|
||||
_host = null;
|
||||
_port = -1;
|
||||
// _addr = null;
|
||||
}
|
||||
|
||||
public NTCPAddress(InetAddress addr, int port) {
|
||||
if (addr != null)
|
||||
_host = addr.getHostAddress();
|
||||
_addr = addr;
|
||||
_port = port;
|
||||
}
|
||||
*/
|
||||
|
||||
public NTCPAddress(RouterAddress addr) {
|
||||
if (addr == null) {
|
||||
_host = null;
|
||||
_port = -1;
|
||||
return;
|
||||
}
|
||||
_host = addr.getOption(PROP_HOST);
|
||||
_port = addr.getPort();
|
||||
}
|
||||
|
||||
public RouterAddress toRouterAddress() {
|
||||
if ( (_host == null) || (_port <= 0) )
|
||||
return null;
|
||||
|
||||
RouterAddress addr = new RouterAddress();
|
||||
|
||||
addr.setCost(DEFAULT_COST);
|
||||
//addr.setExpiration(null);
|
||||
|
||||
Properties props = new Properties();
|
||||
props.setProperty(PROP_HOST, _host);
|
||||
props.setProperty(PROP_PORT, ""+_port);
|
||||
|
||||
addr.setOptions(props);
|
||||
addr.setTransportStyle(NTCPTransport.STYLE);
|
||||
return addr;
|
||||
}
|
||||
|
||||
public String getHost() { return _host; }
|
||||
//public void setHost(String host) { _host = host; }
|
||||
//public InetAddress getAddress() { return _addr; }
|
||||
//public void setAddress(InetAddress addr) { _addr = addr; }
|
||||
public int getPort() { return _port; }
|
||||
//public void setPort(int port) { _port = port; }
|
||||
|
||||
public boolean isPubliclyRoutable() {
|
||||
return isPubliclyRoutable(_host);
|
||||
}
|
||||
|
||||
public static boolean isPubliclyRoutable(String host) {
|
||||
if (host == null) return false;
|
||||
byte quad[] = Addresses.getIP(host);
|
||||
return TransportImpl.isPubliclyRoutable(quad);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return _host + ":" + _port; }
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int rv = _port;
|
||||
//if (_addr != null)
|
||||
// rv += _addr.getHostAddress().hashCode();
|
||||
//else
|
||||
if (_host != null) rv ^= _host.hashCode();
|
||||
return rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object val) {
|
||||
if ( (val != null) && (val instanceof NTCPAddress) ) {
|
||||
NTCPAddress addr = (NTCPAddress)val;
|
||||
String hostname = null;
|
||||
if (addr.getHost() != null)
|
||||
hostname = addr.getHost().trim();
|
||||
String ourHost = getHost();
|
||||
if (ourHost != null)
|
||||
ourHost = ourHost.trim();
|
||||
return DataHelper.eq(hostname, ourHost) && getPort() == addr.getPort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean equals(RouterAddress addr) {
|
||||
if (addr == null) return false;
|
||||
return ( (_host.equals(addr.getOption(PROP_HOST))) &&
|
||||
(Integer.toString(_port).equals(addr.getOption(PROP_PORT))) );
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package net.i2p.router.transport.ntcp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet6Address;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
@ -16,6 +17,7 @@ import java.util.zip.Adler32;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
@ -86,7 +88,7 @@ class NTCPConnection {
|
||||
private final NTCPTransport _transport;
|
||||
private final boolean _isInbound;
|
||||
private volatile boolean _closed;
|
||||
private NTCPAddress _remAddr;
|
||||
private final RouterAddress _remAddr;
|
||||
private RouterIdentity _remotePeer;
|
||||
private long _clockSkew; // in seconds
|
||||
/**
|
||||
@ -165,6 +167,7 @@ class NTCPConnection {
|
||||
_log = ctx.logManager().getLog(getClass());
|
||||
_created = System.currentTimeMillis();
|
||||
_transport = transport;
|
||||
_remAddr = null;
|
||||
_chan = chan;
|
||||
_readBufs = new ConcurrentLinkedQueue();
|
||||
_writeBufs = new ConcurrentLinkedQueue();
|
||||
@ -187,7 +190,7 @@ class NTCPConnection {
|
||||
* Create an outbound unconnected NTCP connection
|
||||
*
|
||||
*/
|
||||
public NTCPConnection(RouterContext ctx, NTCPTransport transport, RouterIdentity remotePeer, NTCPAddress remAddr) {
|
||||
public NTCPConnection(RouterContext ctx, NTCPTransport transport, RouterIdentity remotePeer, RouterAddress remAddr) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(getClass());
|
||||
_created = System.currentTimeMillis();
|
||||
@ -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; }
|
||||
|
||||
|
@ -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("✓");
|
||||
else
|
||||
buf.append(" ");
|
||||
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> </td><td> ");
|
||||
buf.append("<tr class=\"tablefooter\"><td colspan=\"4\" align=\"left\"><b>").append(_("SUMMARY"))
|
||||
.append("</b>");
|
||||
buf.append("</td><td align=\"center\"><b>").append(formatRate(bpsRecv/1024)).append(THINSP).append(formatRate(bpsSend/1024)).append("</b>");
|
||||
buf.append("</td><td align=\"center\"><b>").append(DataHelper.formatDuration2(totalUptime/peers.size()));
|
||||
buf.append("</b></td><td align=\"center\"><b>").append(DataHelper.formatDuration2(offsetTotal*1000/peers.size()));
|
||||
|
@ -62,7 +62,7 @@ class ACKSender implements Runnable {
|
||||
|
||||
public synchronized void shutdown() {
|
||||
_alive = false;
|
||||
PeerState poison = new PeerState(_context, _transport, null, 0, null, false);
|
||||
PeerState poison = new PeerState(_context, _transport, new byte[4], 0, null, false);
|
||||
poison.setTheyRelayToUsAs(POISON_PS);
|
||||
_peersToACK.offer(poison);
|
||||
for (int i = 1; i <= 5 && !_peersToACK.isEmpty(); i++) {
|
||||
|
@ -249,7 +249,7 @@ class EstablishmentManager {
|
||||
maybeTo = new RemoteHostId(remAddr.getAddress(), port);
|
||||
|
||||
if ((!_transport.isValid(maybeTo.getIP())) ||
|
||||
Arrays.equals(maybeTo.getIP(), _transport.getExternalIP())) {
|
||||
(Arrays.equals(maybeTo.getIP(), _transport.getExternalIP()) && !_transport.allowLocal())) {
|
||||
_transport.failed(msg, "Remote peer's IP isn't valid");
|
||||
_transport.markUnreachable(toHash);
|
||||
//_context.banlist().banlistRouter(msg.getTarget().getIdentity().calculateHash(), "Invalid SSU address", UDPTransport.STYLE);
|
||||
@ -447,7 +447,9 @@ class EstablishmentManager {
|
||||
}
|
||||
if (!_transport.allowConnection())
|
||||
return; // drop the packet
|
||||
state = new InboundEstablishState(_context, from.getIP(), from.getPort(), _transport.getExternalPort(),
|
||||
byte[] fromIP = from.getIP();
|
||||
state = new InboundEstablishState(_context, fromIP, from.getPort(),
|
||||
_transport.getExternalPort(fromIP.length == 16),
|
||||
_transport.getDHBuilder());
|
||||
state.receiveSessionRequest(reader.getSessionRequestReader());
|
||||
InboundEstablishState oldState = _inboundStates.putIfAbsent(from, state);
|
||||
@ -459,8 +461,10 @@ class EstablishmentManager {
|
||||
|
||||
if (isNew) {
|
||||
// Don't offer to relay to privileged ports.
|
||||
// Only offer for an IPv4 session.
|
||||
// TODO if already we have their RI, only offer if they need it (no 'C' cap)
|
||||
if (_transport.canIntroduce() && state.getSentPort() >= 1024) {
|
||||
if (_transport.canIntroduce() && state.getSentPort() >= 1024 &&
|
||||
state.getSentIP().length == 4) {
|
||||
// ensure > 0
|
||||
long tag = 1 + _context.random().nextLong(MAX_TAG_VALUE);
|
||||
state.setSentRelayTag(tag);
|
||||
@ -673,7 +677,8 @@ class EstablishmentManager {
|
||||
String smtu = addr.getOption(UDPAddress.PROP_MTU);
|
||||
if (smtu != null) {
|
||||
try {
|
||||
int mtu = MTU.rectify(Integer.parseInt(smtu));
|
||||
boolean isIPv6 = state.getSentIP().length == 16;
|
||||
int mtu = MTU.rectify(isIPv6, Integer.parseInt(smtu));
|
||||
peer.setHisMTU(mtu);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
@ -833,7 +838,9 @@ class EstablishmentManager {
|
||||
_inboundStates.remove(state.getRemoteHostId());
|
||||
return;
|
||||
}
|
||||
_transport.send(_builder.buildSessionCreatedPacket(state, _transport.getExternalPort(), _transport.getIntroKey()));
|
||||
_transport.send(_builder.buildSessionCreatedPacket(state,
|
||||
_transport.getExternalPort(state.getSentIP().length == 16),
|
||||
_transport.getIntroKey()));
|
||||
state.createdPacketSent();
|
||||
}
|
||||
|
||||
@ -884,6 +891,9 @@ class EstablishmentManager {
|
||||
state.introSent();
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Alice, we sent a RelayRequest to Bob and got a response back.
|
||||
*/
|
||||
void receiveRelayResponse(RemoteHostId bob, UDPPacketReader reader) {
|
||||
long nonce = reader.getRelayResponseReader().readNonce();
|
||||
OutboundEstablishState state = _liveIntroductions.remove(Long.valueOf(nonce));
|
||||
@ -893,6 +903,7 @@ class EstablishmentManager {
|
||||
return; // already established
|
||||
}
|
||||
|
||||
// Note that we ignore the Alice (us) IP/Port in the RelayResponse
|
||||
int sz = reader.getRelayResponseReader().readCharlieIPSize();
|
||||
byte ip[] = new byte[sz];
|
||||
reader.getRelayResponseReader().readCharlieIP(ip, 0);
|
||||
@ -940,15 +951,17 @@ class EstablishmentManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Are IP and port valid?
|
||||
* Are IP and port valid? This is only for checking the relay response.
|
||||
* Reject all IPv6, for now, even if we are configured for it.
|
||||
* Refuse anybody in the same /16
|
||||
* @since 0.9.3
|
||||
*/
|
||||
private boolean isValid(byte[] ip, int port) {
|
||||
return port >= 1024 &&
|
||||
return port >= UDPTransport.MIN_PEER_PORT &&
|
||||
port <= 65535 &&
|
||||
ip != null && ip.length == 4 &&
|
||||
_transport.isValid(ip) &&
|
||||
(!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) &&
|
||||
(!_transport.isTooClose(ip)) &&
|
||||
(!_context.blocklist().isBlocklisted(ip));
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package net.i2p.router.transport.udp;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.SipHash;
|
||||
|
||||
/**
|
||||
* Count IPs
|
||||
@ -21,12 +22,16 @@ class IPThrottler {
|
||||
|
||||
/**
|
||||
* Increments before checking
|
||||
* @return true if ip.length != 4
|
||||
*/
|
||||
public boolean shouldThrottle(byte[] ip) {
|
||||
if (ip.length != 4)
|
||||
return true;
|
||||
return _counter.increment(toInt(ip)) > _max;
|
||||
// for IPv4 we simply use the IP;
|
||||
// for IPv6 we use a secure hash as an attacker could select the lower bytes
|
||||
Integer key;
|
||||
if (ip.length == 4)
|
||||
key = toInt(ip);
|
||||
else
|
||||
key = Integer.valueOf(SipHash.hashCode(ip));
|
||||
return _counter.increment(key) > _max;
|
||||
}
|
||||
|
||||
private static Integer toInt(byte ip[]) {
|
||||
|
@ -66,6 +66,7 @@ class InboundMessageFragments /*implements UDPTransport.PartialACKSource */{
|
||||
_ackSender.shutdown();
|
||||
_messageReceiver.shutdown();
|
||||
}
|
||||
|
||||
public boolean isAlive() { return _alive; }
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,52 @@ import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Keep track of inbound and outbound introductions.
|
||||
*
|
||||
* IPv6 info: Alice-Bob communication may be via IPv4 or IPv6.
|
||||
* Bob-Charlie communication must be via established IPv4 session as that's the only way
|
||||
* that Bob knows Charlie's IPv4 address to give it to Alice.
|
||||
* Alice-Charlie communication is via IPv4.
|
||||
* If Alice-Bob is over IPv6, Alice must include her IPv4 address in
|
||||
* the RelayRequest message.
|
||||
*
|
||||
* From udp.html on the website:
|
||||
|
||||
<p>Indirect session establishment by means of a third party introduction
|
||||
is necessary for efficient NAT traversal. Charlie, a router behind a
|
||||
NAT or firewall which does not allow unsolicited inbound UDP packets,
|
||||
first contacts a few peers, choosing some to serve as introducers. Each
|
||||
of these peers (Bob, Bill, Betty, etc) provide Charlie with an introduction
|
||||
tag - a 4 byte random number - which he then makes available to the public
|
||||
as methods of contacting him. Alice, a router who has Charlie's published
|
||||
contact methods, first sends a RelayRequest packet to one or more of the
|
||||
introducers, asking each to introduce her to Charlie (offering the
|
||||
introduction tag to identify Charlie). Bob then forwards a RelayIntro
|
||||
packet to Charlie including Alice's public IP and port number, then sends
|
||||
Alice back a RelayResponse packet containing Charlie's public IP and port
|
||||
number. When Charlie receives the RelayIntro packet, he sends off a small
|
||||
random packet to Alice's IP and port (poking a hole in his NAT/firewall),
|
||||
and when Alice receives Bob's RelayResponse packet, she begins a new
|
||||
full direction session establishment with the specified IP and port.</p>
|
||||
<p>
|
||||
Alice first connects to introducer Bob, who relays the request to Charlie.
|
||||
</p>
|
||||
<pre>
|
||||
Alice Bob Charlie
|
||||
RelayRequest ---------------------->
|
||||
<-------------- RelayResponse RelayIntro ----------->
|
||||
<-------------------------------------------- HolePunch (data ignored)
|
||||
SessionRequest -------------------------------------------->
|
||||
<-------------------------------------------- SessionCreated
|
||||
SessionConfirmed ------------------------------------------>
|
||||
<-------------------------------------------- DeliveryStatusMessage
|
||||
<-------------------------------------------- DatabaseStoreMessage
|
||||
DatabaseStoreMessage -------------------------------------->
|
||||
Data <--------------------------------------------------> Data
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
After the hole punch, the session is established between Alice and Charlie as in a direct establishment.
|
||||
</p>
|
||||
*/
|
||||
class IntroductionManager {
|
||||
private final RouterContext _context;
|
||||
@ -77,6 +123,9 @@ class IntroductionManager {
|
||||
// let's not use an introducer on a privileged port, sounds like trouble
|
||||
if (peer.getRemotePort() < 1024)
|
||||
return;
|
||||
// Only allow relay as Bob or Charlie if the Bob-Charlie session is IPv4
|
||||
if (peer.getRemoteIP().length != 4)
|
||||
return;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Adding peer " + peer.getRemoteHostId() + ", weRelayToThemAs "
|
||||
+ peer.getWeRelayToThemAs() + ", theyRelayToUsAs " + peer.getTheyRelayToUsAs());
|
||||
@ -136,6 +185,8 @@ class IntroductionManager {
|
||||
_log.info("Picked peer has no local routerInfo: " + cur);
|
||||
continue;
|
||||
}
|
||||
// FIXME we can include all his addresses including IPv6 even if we don't support IPv6 (isValid() is false)
|
||||
// but requires RelayRequest support, see below
|
||||
RouterAddress ra = _transport.getTargetAddress(ri);
|
||||
if (ra == null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -159,6 +210,8 @@ class IntroductionManager {
|
||||
_log.info("Peer is idle too long: " + cur);
|
||||
continue;
|
||||
}
|
||||
// FIXME we can include all his addresses including IPv6 even if we don't support IPv6 (isValid() is false)
|
||||
// but requires RelayRequest support, see below
|
||||
byte[] ip = cur.getRemoteIP();
|
||||
int port = cur.getRemotePort();
|
||||
if (!isValid(ip, port))
|
||||
@ -331,6 +384,9 @@ class IntroductionManager {
|
||||
|
||||
// ip/port inside message should be 0:0, as it's unimplemented on send -
|
||||
// see PacketBuilder.buildRelayRequest()
|
||||
// and we don't read it here.
|
||||
// FIXME implement for getting Alice's IPv4 in RelayRequest sent over IPv6?
|
||||
// or is that just too easy to spoof?
|
||||
if (!isValid(alice.getIP(), alice.getPort()) || ipSize != 0 || port != 0) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
byte ip[] = new byte[ipSize];
|
||||
@ -368,14 +424,16 @@ class IntroductionManager {
|
||||
|
||||
/**
|
||||
* Are IP and port valid?
|
||||
* Reject all IPv6, for now, even if we are configured for it.
|
||||
* Refuse anybody in the same /16
|
||||
* @since 0.9.3
|
||||
*/
|
||||
private boolean isValid(byte[] ip, int port) {
|
||||
return port >= 1024 &&
|
||||
return port >= UDPTransport.MIN_PEER_PORT &&
|
||||
port <= 65535 &&
|
||||
ip != null && ip.length == 4 &&
|
||||
_transport.isValid(ip) &&
|
||||
(!DataHelper.eq(ip, 0, _transport.getExternalIP(), 0, 2)) &&
|
||||
(!_transport.isTooClose(ip)) &&
|
||||
(!_context.blocklist().isBlocklisted(ip));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
@ -41,7 +42,8 @@ abstract class MTU {
|
||||
try {
|
||||
// testing
|
||||
//return ifc.getMTU();
|
||||
return rectify(ifc.getMTU());
|
||||
boolean isIPv6 = addr instanceof Inet6Address;
|
||||
return rectify(isIPv6, ifc.getMTU());
|
||||
} catch (SocketException se) {
|
||||
// ignore
|
||||
} catch (Throwable t) {
|
||||
@ -58,11 +60,16 @@ abstract class MTU {
|
||||
|
||||
/**
|
||||
* @return min of PeerState.MIN_MTU, max of PeerState.LARGE_MTU,
|
||||
* rectified so rv % 16 == 12
|
||||
* rectified so rv % 16 == 12 (IPv4)
|
||||
* or rv % 16 == 0 (IPv6)
|
||||
*/
|
||||
public static int rectify(int mtu) {
|
||||
public static int rectify(boolean isIPv6, int mtu) {
|
||||
int rv = mtu;
|
||||
int mod = rv % 16;
|
||||
if (isIPv6) {
|
||||
rv -= mod;
|
||||
return Math.max(PeerState.MIN_IPV6_MTU, Math.min(PeerState.MAX_IPV6_MTU, rv));
|
||||
}
|
||||
if (mod > 12)
|
||||
rv -= mod - 12;
|
||||
else if (mod < 12)
|
||||
@ -72,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");
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
@ -309,8 +309,8 @@ class OutboundMessageFragments {
|
||||
// use max of 1 second so finishMessages() and/or PeerState.finishMessages()
|
||||
// gets called regularly
|
||||
int toWait = Math.min(Math.max(nextSendDelay, 10), MAX_WAIT);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("wait for " + toWait);
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("wait for " + toWait);
|
||||
// wait.. or somethin'
|
||||
synchronized (_activePeers) {
|
||||
try {
|
||||
|
@ -141,6 +141,10 @@ class PacketBuilder {
|
||||
/** 74 */
|
||||
public static final int MIN_DATA_PACKET_OVERHEAD = IP_HEADER_SIZE + UDP_HEADER_SIZE + DATA_HEADER_SIZE;
|
||||
|
||||
public static final int IPV6_HEADER_SIZE = 40;
|
||||
/** 94 */
|
||||
public static final int MIN_IPV6_DATA_PACKET_OVERHEAD = IPV6_HEADER_SIZE + UDP_HEADER_SIZE + DATA_HEADER_SIZE;
|
||||
|
||||
/** one byte field */
|
||||
public static final int ABSOLUTE_MAX_ACKS = 255;
|
||||
|
||||
@ -158,6 +162,9 @@ class PacketBuilder {
|
||||
|
||||
private static final String PROP_PADDING = "i2np.udp.padding";
|
||||
|
||||
/**
|
||||
* @param transport may be null for unit testing only
|
||||
*/
|
||||
public PacketBuilder(I2PAppContext ctx, UDPTransport transport) {
|
||||
_context = ctx;
|
||||
_transport = transport;
|
||||
@ -244,7 +251,15 @@ class PacketBuilder {
|
||||
}
|
||||
|
||||
int currentMTU = peer.getMTU();
|
||||
int availableForAcks = currentMTU - MIN_DATA_PACKET_OVERHEAD - dataSize;
|
||||
int availableForAcks = currentMTU - dataSize;
|
||||
int ipHeaderSize;
|
||||
if (peer.getRemoteIP().length == 4) {
|
||||
availableForAcks -= MIN_DATA_PACKET_OVERHEAD;
|
||||
ipHeaderSize = IP_HEADER_SIZE;
|
||||
} else {
|
||||
availableForAcks -= MIN_IPV6_DATA_PACKET_OVERHEAD;
|
||||
ipHeaderSize = IPV6_HEADER_SIZE;
|
||||
}
|
||||
int availableForExplicitAcks = availableForAcks;
|
||||
|
||||
// ok, now for the body...
|
||||
@ -400,16 +415,16 @@ class PacketBuilder {
|
||||
setTo(packet, peer.getRemoteIPAddress(), peer.getRemotePort());
|
||||
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
msg.append(" pkt size ").append(off + (IP_HEADER_SIZE + UDP_HEADER_SIZE));
|
||||
msg.append(" pkt size ").append(off + (ipHeaderSize + UDP_HEADER_SIZE));
|
||||
_log.info(msg.toString());
|
||||
}
|
||||
// the packet could have been built before the current mtu got lowered, so
|
||||
// compare to LARGE_MTU
|
||||
if (off + (IP_HEADER_SIZE + UDP_HEADER_SIZE) > PeerState.LARGE_MTU) {
|
||||
if (off + (ipHeaderSize + UDP_HEADER_SIZE) > PeerState.LARGE_MTU) {
|
||||
_log.error("Size is " + off + " for " + packet +
|
||||
" fragment " + fragment +
|
||||
" data size " + dataSize +
|
||||
" pkt size " + (off + (IP_HEADER_SIZE + UDP_HEADER_SIZE)) +
|
||||
" pkt size " + (off + (ipHeaderSize + UDP_HEADER_SIZE)) +
|
||||
" MTU " + currentMTU +
|
||||
' ' + availableForAcks + " for all acks " +
|
||||
availableForExplicitAcks + " for full acks " +
|
||||
@ -561,7 +576,7 @@ class PacketBuilder {
|
||||
state.prepareSessionCreated();
|
||||
|
||||
byte sentIP[] = state.getSentIP();
|
||||
if ( (sentIP == null) || (sentIP.length <= 0) || ( (_transport != null) && (!_transport.isValid(sentIP)) ) ) {
|
||||
if ( (sentIP == null) || (sentIP.length <= 0) || (!_transport.isValid(sentIP))) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("How did our sent IP become invalid? " + state);
|
||||
state.fail();
|
||||
@ -642,7 +657,7 @@ class PacketBuilder {
|
||||
int off = HEADER_SIZE;
|
||||
|
||||
byte toIP[] = state.getSentIP();
|
||||
if ( (_transport !=null) && (!_transport.isValid(toIP)) ) {
|
||||
if (!_transport.isValid(toIP)) {
|
||||
packet.release();
|
||||
return null;
|
||||
}
|
||||
@ -1051,6 +1066,7 @@ class PacketBuilder {
|
||||
|
||||
// specify these if we know what our external receive ip/port is and if its different
|
||||
// from what bob is going to think
|
||||
// FIXME IPv4 addr must be specified when sent over IPv6
|
||||
private byte[] getOurExplicitIP() { return null; }
|
||||
private int getOurExplicitPort() { return 0; }
|
||||
|
||||
@ -1070,8 +1086,10 @@ class PacketBuilder {
|
||||
// let's not use an introducer on a privileged port, sounds like trouble
|
||||
if (ikey == null || iport < 1024 || iport > 65535 ||
|
||||
iaddr == null || tag <= 0 ||
|
||||
// must be IPv4 for now as we don't send Alice IP/port, see below
|
||||
iaddr.getAddress().length != 4 ||
|
||||
(!_transport.isValid(iaddr.getAddress())) ||
|
||||
Arrays.equals(iaddr.getAddress(), _transport.getExternalIP())) {
|
||||
(Arrays.equals(iaddr.getAddress(), _transport.getExternalIP()) && !_transport.allowLocal())) {
|
||||
if (_log.shouldLog(_log.WARN))
|
||||
_log.warn("Cannot build a relay request to " + state.getRemoteIdentity().calculateHash()
|
||||
+ ", as their UDP address is invalid: addr=" + addr + " index=" + i);
|
||||
@ -1083,6 +1101,11 @@ class PacketBuilder {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Alice IP/port in packet will always be null/0, must be fixed to
|
||||
* send a RelayRequest over IPv6
|
||||
*
|
||||
*/
|
||||
private UDPPacket buildRelayRequest(InetAddress introHost, int introPort, byte introKey[],
|
||||
long introTag, SessionKey ourIntroKey, long introNonce, boolean encrypt) {
|
||||
UDPPacket packet = buildPacketHeader(PEER_RELAY_REQUEST_FLAG_BYTE);
|
||||
@ -1090,6 +1113,7 @@ class PacketBuilder {
|
||||
byte data[] = pkt.getData();
|
||||
int off = HEADER_SIZE;
|
||||
|
||||
// FIXME must specify these if request is going over IPv6
|
||||
byte ourIP[] = getOurExplicitIP();
|
||||
int ourPort = getOurExplicitPort();
|
||||
|
||||
@ -1211,6 +1235,7 @@ class PacketBuilder {
|
||||
DataHelper.toLong(data, off, 2, charlie.getRemotePort());
|
||||
off += 2;
|
||||
|
||||
// Alice IP/Port currently ignored on receive - see UDPPacketReader
|
||||
byte aliceIP[] = alice.getIP();
|
||||
DataHelper.toLong(data, off, 1, aliceIP.length);
|
||||
off++;
|
||||
@ -1233,7 +1258,7 @@ class PacketBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an empty unauthenticated packet for hole punching.
|
||||
* Creates an empty unauthenticated packet for hole punching.
|
||||
* Parameters must be validated previously.
|
||||
*/
|
||||
public UDPPacket buildHolePunch(InetAddress to, int port) {
|
||||
@ -1250,6 +1275,23 @@ class PacketBuilder {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* TESTING ONLY.
|
||||
* Creates an arbitrary packet for unit testing.
|
||||
* Null transport in constructor OK.
|
||||
*
|
||||
* @param type 0-15
|
||||
* @since IPv6
|
||||
*/
|
||||
public UDPPacket buildPacket(byte[] data, InetAddress to, int port) {
|
||||
UDPPacket packet = UDPPacket.acquire(_context, false);
|
||||
byte d[] = packet.getPacket().getData();
|
||||
System.arraycopy(data, 0, d, 0, data.length);
|
||||
packet.getPacket().setLength(data.length);
|
||||
setTo(packet, to, port);
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new packet and add the flag byte and the time stamp.
|
||||
* Caller should add data starting at HEADER_SIZE.
|
||||
|
@ -3,9 +3,11 @@ package net.i2p.router.transport.udp;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.util.CoDelBlockingQueue;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.LHMCache;
|
||||
@ -26,7 +28,6 @@ class PacketHandler {
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
private final UDPTransport _transport;
|
||||
private final UDPEndpoint _endpoint;
|
||||
private final EstablishmentManager _establisher;
|
||||
private final InboundMessageFragments _inbound;
|
||||
private final PeerTestManager _testManager;
|
||||
@ -34,19 +35,22 @@ class PacketHandler {
|
||||
private volatile boolean _keepReading;
|
||||
private final Handler[] _handlers;
|
||||
private final Map<RemoteHostId, Object> _failCache;
|
||||
private final BlockingQueue<UDPPacket> _inboundQueue;
|
||||
private static final Object DUMMY = new Object();
|
||||
|
||||
private static final int TYPE_POISON = -99999;
|
||||
private static final int MIN_QUEUE_SIZE = 16;
|
||||
private static final int MAX_QUEUE_SIZE = 192;
|
||||
private static final int MIN_NUM_HANDLERS = 1; // unless < 32MB
|
||||
private static final int MAX_NUM_HANDLERS = 1;
|
||||
/** let packets be up to 30s slow */
|
||||
private static final long GRACE_PERIOD = Router.CLOCK_FUDGE_FACTOR + 30*1000;
|
||||
|
||||
PacketHandler(RouterContext ctx, UDPTransport transport, UDPEndpoint endpoint, EstablishmentManager establisher,
|
||||
PacketHandler(RouterContext ctx, UDPTransport transport, EstablishmentManager establisher,
|
||||
InboundMessageFragments inbound, PeerTestManager testManager, IntroductionManager introManager) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(PacketHandler.class);
|
||||
_transport = transport;
|
||||
_endpoint = endpoint;
|
||||
_establisher = establisher;
|
||||
_inbound = inbound;
|
||||
_testManager = testManager;
|
||||
@ -56,6 +60,8 @@ class PacketHandler {
|
||||
long maxMemory = Runtime.getRuntime().maxMemory();
|
||||
if (maxMemory == Long.MAX_VALUE)
|
||||
maxMemory = 96*1024*1024l;
|
||||
int qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (2*1024*1024)));
|
||||
_inboundQueue = new CoDelBlockingQueue(ctx, "UDP-Receiver", qsize);
|
||||
int num_handlers;
|
||||
if (maxMemory < 32*1024*1024)
|
||||
num_handlers = 1;
|
||||
@ -107,6 +113,7 @@ class PacketHandler {
|
||||
|
||||
public synchronized void shutdown() {
|
||||
_keepReading = false;
|
||||
stopQueue();
|
||||
}
|
||||
|
||||
String getHandlerStatus() {
|
||||
@ -119,9 +126,55 @@ class PacketHandler {
|
||||
return rv.toString();
|
||||
}
|
||||
|
||||
/** @since 0.8.8 */
|
||||
int getHandlerCount() {
|
||||
return _handlers.length;
|
||||
/**
|
||||
* Blocking call to retrieve the next inbound packet, or null if we have
|
||||
* shut down.
|
||||
*
|
||||
* @since IPv6 moved from UDPReceiver
|
||||
*/
|
||||
public void queueReceived(UDPPacket packet) throws InterruptedException {
|
||||
_inboundQueue.put(packet);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Blocking for a while
|
||||
*
|
||||
* @since IPv6 moved from UDPReceiver
|
||||
*/
|
||||
private void stopQueue() {
|
||||
_inboundQueue.clear();
|
||||
for (int i = 0; i < _handlers.length; i++) {
|
||||
UDPPacket poison = UDPPacket.acquire(_context, false);
|
||||
poison.setMessageType(TYPE_POISON);
|
||||
_inboundQueue.offer(poison);
|
||||
}
|
||||
for (int i = 1; i <= 5 && !_inboundQueue.isEmpty(); i++) {
|
||||
try {
|
||||
Thread.sleep(i * 50);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
_inboundQueue.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking call to retrieve the next inbound packet, or null if we have
|
||||
* shut down.
|
||||
*
|
||||
* @since IPv6 moved from UDPReceiver
|
||||
*/
|
||||
public UDPPacket receiveNext() {
|
||||
UDPPacket rv = null;
|
||||
//int remaining = 0;
|
||||
while (_keepReading && rv == null) {
|
||||
try {
|
||||
rv = _inboundQueue.take();
|
||||
} catch (InterruptedException ie) {}
|
||||
if (rv != null && rv.getMessageType() == TYPE_POISON)
|
||||
return null;
|
||||
}
|
||||
//_context.statManager().addRateData("udp.receiveRemaining", remaining, 0);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** the packet is from a peer we are establishing an outbound con to, but failed validation, so fallback */
|
||||
@ -144,7 +197,7 @@ class PacketHandler {
|
||||
_state = 1;
|
||||
while (_keepReading) {
|
||||
_state = 2;
|
||||
UDPPacket packet = _endpoint.receive();
|
||||
UDPPacket packet = receiveNext();
|
||||
_state = 3;
|
||||
if (packet == null) break; // keepReading is probably false, or bind failed...
|
||||
|
||||
|
@ -1,26 +1,29 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Blocking thread to grab new packets off the outbound fragment
|
||||
* pool and toss 'em onto the outbound packet queue
|
||||
* pool and toss 'em onto the outbound packet queues.
|
||||
*
|
||||
* Here we select which UDPEndpoint/UDPSender to send it out.
|
||||
*/
|
||||
class PacketPusher implements Runnable {
|
||||
// private RouterContext _context;
|
||||
private final Log _log;
|
||||
private final OutboundMessageFragments _fragments;
|
||||
private final UDPSender _sender;
|
||||
private final List<UDPEndpoint> _endpoints;
|
||||
private volatile boolean _alive;
|
||||
|
||||
public PacketPusher(RouterContext ctx, OutboundMessageFragments fragments, UDPSender sender) {
|
||||
public PacketPusher(RouterContext ctx, OutboundMessageFragments fragments, List<UDPEndpoint> endpoints) {
|
||||
// _context = ctx;
|
||||
_log = ctx.logManager().getLog(PacketPusher.class);
|
||||
_fragments = fragments;
|
||||
_sender = sender;
|
||||
_endpoints = endpoints;
|
||||
}
|
||||
|
||||
public synchronized void startup() {
|
||||
@ -38,8 +41,7 @@ class PacketPusher implements Runnable {
|
||||
if (packets != null) {
|
||||
for (int i = 0; i < packets.length; i++) {
|
||||
if (packets[i] != null) // null for ACKed fragments
|
||||
// BLOCKING if queue is full
|
||||
_sender.add(packets[i]);
|
||||
send(packets[i]);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -47,4 +49,38 @@ class PacketPusher implements Runnable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This sends it directly out, bypassing OutboundMessageFragments
|
||||
* and the PacketPusher. The only queueing is for the bandwidth limiter.
|
||||
* BLOCKING if OB queue is full.
|
||||
*
|
||||
* @param packet non-null
|
||||
* @since IPv6
|
||||
*/
|
||||
public void send(UDPPacket packet) {
|
||||
boolean isIPv4 = packet.getPacket().getAddress().getAddress().length == 4;
|
||||
for (int j = 0; j < _endpoints.size(); j++) {
|
||||
// Find the best endpoint (socket) to send this out.
|
||||
// TODO if we have multiple IPv4, or multiple IPv6 endpoints,
|
||||
// we have to track which one we're using in the PeerState and
|
||||
// somehow set that in the UDPPacket so we're consistent
|
||||
UDPEndpoint ep;
|
||||
try {
|
||||
ep = _endpoints.get(j);
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
// whups, list changed
|
||||
break;
|
||||
}
|
||||
if ((isIPv4 && ep.isIPv4()) ||
|
||||
((!isIPv4) && ep.isIPv6())) {
|
||||
// BLOCKING if queue is full
|
||||
ep.getSender().add(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// not handled
|
||||
_log.error("No endpoint to send " + packet);
|
||||
packet.release();
|
||||
}
|
||||
}
|
||||
|
@ -267,6 +267,12 @@ class PeerState {
|
||||
* and so PacketBuilder.buildPacket() works correctly.
|
||||
*/
|
||||
public static final int MIN_MTU = 620;
|
||||
|
||||
/**
|
||||
* IPv6/UDP header is 48 bytes, so we want MTU % 16 == 0.
|
||||
*/
|
||||
public static final int MIN_IPV6_MTU = 1280;
|
||||
public static final int MAX_IPV6_MTU = 1472; // TODO 1488
|
||||
private static final int DEFAULT_MTU = MIN_MTU;
|
||||
|
||||
/*
|
||||
@ -315,9 +321,15 @@ class PeerState {
|
||||
_receivePeriodBegin = now;
|
||||
_lastCongestionOccurred = -1;
|
||||
_remotePort = remotePort;
|
||||
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());
|
||||
|
||||
|
@ -24,6 +24,13 @@ import net.i2p.util.SimpleTimer;
|
||||
* Entry points are runTest() to start a new test as Alice,
|
||||
* and receiveTest() for all received test packets.
|
||||
*
|
||||
* IPv6 info: All Alice-Bob and Alice-Charlie communication is via IPv4.
|
||||
* The Bob-Charlie communication may be via IPv6, however Charlie must
|
||||
* be IPv4-capable.
|
||||
* The IP address (of Alice) in the message must be IPv4 if present,
|
||||
* as we only support testing of IPv4.
|
||||
* Testing of IPv6 could be added in the future.
|
||||
*
|
||||
* From udp.html on the website:
|
||||
|
||||
<p>The automation of collaborative reachability testing for peers is
|
||||
@ -166,6 +173,8 @@ class PeerTestManager {
|
||||
|
||||
/**
|
||||
* The next few methods are for when we are Alice
|
||||
*
|
||||
* @param bobIP IPv4 only
|
||||
*/
|
||||
public synchronized void runTest(InetAddress bobIP, int bobPort, SessionKey bobCipherKey, SessionKey bobMACKey) {
|
||||
if (_currentTest != null) {
|
||||
@ -173,7 +182,7 @@ class PeerTestManager {
|
||||
_log.warn("We are already running a test: " + _currentTest + ", aborting test with bob = " + bobIP);
|
||||
return;
|
||||
}
|
||||
if (DataHelper.eq(bobIP.getAddress(), 0, _transport.getExternalIP(), 0, 2)) {
|
||||
if (_transport.isTooClose(bobIP.getAddress())) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Not running test with Bob too close to us " + bobIP);
|
||||
return;
|
||||
@ -304,7 +313,7 @@ class PeerTestManager {
|
||||
// The reply is from Bob
|
||||
|
||||
int ipSize = testInfo.readIPSize();
|
||||
if (ipSize != 4 && ipSize != 16) {
|
||||
if (ipSize != 4) {
|
||||
// There appears to be a bug where Bob is sending us a zero-length IP.
|
||||
// We could proceed without setting the IP, but then when Charlie
|
||||
// sends us his message, we will think we are behind a symmetric NAT
|
||||
@ -366,6 +375,8 @@ class PeerTestManager {
|
||||
throw new UnknownHostException("port 0");
|
||||
test.setAlicePortFromCharlie(testPort);
|
||||
byte ip[] = new byte[testInfo.readIPSize()];
|
||||
if (ip.length != 4)
|
||||
throw new UnknownHostException("not IPv4");
|
||||
testInfo.readIP(ip, 0);
|
||||
InetAddress addr = InetAddress.getByAddress(ip);
|
||||
test.setAliceIPFromCharlie(addr);
|
||||
@ -485,7 +496,7 @@ class PeerTestManager {
|
||||
int fromPort = from.getPort();
|
||||
if (fromPort < 1024 || fromPort > 65535 ||
|
||||
(!_transport.isValid(fromIP)) ||
|
||||
DataHelper.eq(fromIP, 0, _transport.getExternalIP(), 0, 2) ||
|
||||
_transport.isTooClose(fromIP) ||
|
||||
_context.blocklist().isBlocklisted(fromIP)) {
|
||||
// spoof check, and don't respond to privileged ports
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -505,6 +516,7 @@ class PeerTestManager {
|
||||
if ((testPort > 0 && (testPort < 1024 || testPort > 65535)) ||
|
||||
(testIP != null &&
|
||||
((!_transport.isValid(testIP)) ||
|
||||
testIP.length != 4 ||
|
||||
_context.blocklist().isBlocklisted(testIP)))) {
|
||||
// spoof check, and don't respond to privileged ports
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -544,7 +556,7 @@ class PeerTestManager {
|
||||
Long lNonce = Long.valueOf(nonce);
|
||||
PeerTestState state = _activeTests.get(lNonce);
|
||||
|
||||
if (testIP != null && DataHelper.eq(testIP, 0, _transport.getExternalIP(), 0, 2)) {
|
||||
if (testIP != null && _transport.isTooClose(testIP)) {
|
||||
// spoof check - have to do this after receiveTestReply(), since
|
||||
// the field should be us there.
|
||||
// Let's also eliminate anybody in the same /16
|
||||
@ -641,6 +653,8 @@ class PeerTestManager {
|
||||
byte aliceIPData[] = new byte[sz];
|
||||
try {
|
||||
testInfo.readIP(aliceIPData, 0);
|
||||
if (sz != 4)
|
||||
throw new UnknownHostException("not IPv4");
|
||||
int alicePort = testInfo.readPort();
|
||||
if (alicePort == 0)
|
||||
throw new UnknownHostException("port 0");
|
||||
@ -706,7 +720,12 @@ class PeerTestManager {
|
||||
PeerState charlie;
|
||||
RouterInfo charlieInfo = null;
|
||||
if (state == null) { // pick a new charlie
|
||||
charlie = _transport.pickTestPeer(from);
|
||||
if (from.getIP().length != 4) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("PeerTest over IPv6 from Alice as Bob? " + from);
|
||||
return;
|
||||
}
|
||||
charlie = _transport.pickTestPeer(CHARLIE, from);
|
||||
} else {
|
||||
charlie = _transport.getPeerState(new RemoteHostId(state.getCharlieIP().getAddress(), state.getCharliePort()));
|
||||
}
|
||||
|
@ -2,16 +2,17 @@ package net.i2p.router.transport.udp;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.LHMCache;
|
||||
|
||||
/**
|
||||
* basic helper to parse out peer info from a udp address
|
||||
* FIXME public for ConfigNetHelper
|
||||
*/
|
||||
public class UDPAddress {
|
||||
class UDPAddress {
|
||||
private final String _host;
|
||||
private InetAddress _hostAddress;
|
||||
private final int _port;
|
||||
@ -37,6 +38,23 @@ public class UDPAddress {
|
||||
public static final String PROP_INTRO_KEY_PREFIX = "ikey";
|
||||
public static final String PROP_INTRO_TAG_PREFIX = "itag";
|
||||
static final int MAX_INTRODUCERS = 3;
|
||||
private static final String[] PROP_INTRO_HOST;
|
||||
private static final String[] PROP_INTRO_PORT;
|
||||
private static final String[] PROP_INTRO_IKEY;
|
||||
private static final String[] PROP_INTRO_TAG;
|
||||
static {
|
||||
// object churn
|
||||
PROP_INTRO_HOST = new String[MAX_INTRODUCERS];
|
||||
PROP_INTRO_PORT = new String[MAX_INTRODUCERS];
|
||||
PROP_INTRO_IKEY = new String[MAX_INTRODUCERS];
|
||||
PROP_INTRO_TAG = new String[MAX_INTRODUCERS];
|
||||
for (int i = 0; i < MAX_INTRODUCERS; i++) {
|
||||
PROP_INTRO_HOST[i] = PROP_INTRO_HOST_PREFIX + i;
|
||||
PROP_INTRO_PORT[i] = PROP_INTRO_PORT_PREFIX + i;
|
||||
PROP_INTRO_IKEY[i] = PROP_INTRO_KEY_PREFIX + i;
|
||||
PROP_INTRO_TAG[i] = PROP_INTRO_TAG_PREFIX + i;
|
||||
}
|
||||
}
|
||||
|
||||
public UDPAddress(RouterAddress addr) {
|
||||
// TODO make everything final
|
||||
@ -49,33 +67,38 @@ public class UDPAddress {
|
||||
_port = addr.getPort();
|
||||
try {
|
||||
String mtu = addr.getOption(PROP_MTU);
|
||||
if (mtu != null)
|
||||
_mtu = MTU.rectify(Integer.parseInt(mtu));
|
||||
if (mtu != null) {
|
||||
boolean isIPv6 = _host != null && _host.contains(":");
|
||||
_mtu = MTU.rectify(isIPv6, Integer.parseInt(mtu));
|
||||
}
|
||||
} catch (NumberFormatException nfe) {}
|
||||
String key = addr.getOption(PROP_INTRO_KEY);
|
||||
if (key != null)
|
||||
_introKey = Base64.decode(key.trim());
|
||||
if (key != null) {
|
||||
byte[] ik = Base64.decode(key.trim());
|
||||
if (ik != null && ik.length == SessionKey.KEYSIZE_BYTES)
|
||||
_introKey = ik;
|
||||
}
|
||||
|
||||
for (int i = MAX_INTRODUCERS; i >= 0; i--) {
|
||||
String host = addr.getOption(PROP_INTRO_HOST_PREFIX + i);
|
||||
for (int i = MAX_INTRODUCERS - 1; i >= 0; i--) {
|
||||
String host = addr.getOption(PROP_INTRO_HOST[i]);
|
||||
if (host == null) continue;
|
||||
String port = addr.getOption(PROP_INTRO_PORT_PREFIX + i);
|
||||
String port = addr.getOption(PROP_INTRO_PORT[i]);
|
||||
if (port == null) continue;
|
||||
String k = addr.getOption(PROP_INTRO_KEY_PREFIX + i);
|
||||
String k = addr.getOption(PROP_INTRO_IKEY[i]);
|
||||
if (k == null) continue;
|
||||
byte ikey[] = Base64.decode(k);
|
||||
if ( (ikey == null) || (ikey.length != SessionKey.KEYSIZE_BYTES) )
|
||||
continue;
|
||||
String t = addr.getOption(PROP_INTRO_TAG_PREFIX + i);
|
||||
String t = addr.getOption(PROP_INTRO_TAG[i]);
|
||||
if (t == null) continue;
|
||||
int p = -1;
|
||||
int p;
|
||||
try {
|
||||
p = Integer.parseInt(port);
|
||||
if (p <= 0 || p > 65535) continue;
|
||||
if (p < UDPTransport.MIN_PEER_PORT || p > 65535) continue;
|
||||
} catch (NumberFormatException nfe) {
|
||||
continue;
|
||||
}
|
||||
long tag = -1;
|
||||
long tag;
|
||||
try {
|
||||
tag = Long.parseLong(t);
|
||||
if (tag <= 0) continue;
|
||||
@ -131,14 +154,10 @@ public class UDPAddress {
|
||||
}
|
||||
|
||||
public String getHost() { return _host; }
|
||||
|
||||
InetAddress getHostAddress() {
|
||||
if (_hostAddress == null) {
|
||||
try {
|
||||
_hostAddress = InetAddress.getByName(_host);
|
||||
} catch (UnknownHostException uhe) {
|
||||
_hostAddress = null;
|
||||
}
|
||||
}
|
||||
if (_hostAddress == null)
|
||||
_hostAddress = getByName(_host);
|
||||
return _hostAddress;
|
||||
}
|
||||
|
||||
@ -150,18 +169,17 @@ public class UDPAddress {
|
||||
byte[] getIntroKey() { return _introKey; }
|
||||
|
||||
int getIntroducerCount() { return (_introAddresses == null ? 0 : _introAddresses.length); }
|
||||
|
||||
InetAddress getIntroducerHost(int i) {
|
||||
if (_introAddresses[i] == null) {
|
||||
try {
|
||||
_introAddresses[i] = InetAddress.getByName(_introHosts[i]);
|
||||
} catch (UnknownHostException uhe) {
|
||||
_introAddresses[i] = null;
|
||||
}
|
||||
}
|
||||
if (_introAddresses[i] == null)
|
||||
_introAddresses[i] = getByName(_introHosts[i]);
|
||||
return _introAddresses[i];
|
||||
}
|
||||
|
||||
int getIntroducerPort(int i) { return _introPorts[i]; }
|
||||
|
||||
byte[] getIntroducerKey(int i) { return _introKeys[i]; }
|
||||
|
||||
long getIntroducerTag(int i) { return _introTags[i]; }
|
||||
|
||||
/**
|
||||
@ -192,4 +210,71 @@ public class UDPAddress {
|
||||
}
|
||||
return rv.toString();
|
||||
}
|
||||
|
||||
////////////////
|
||||
// cache copied from Addresses.java but caching InetAddress instead of byte[]
|
||||
|
||||
|
||||
/**
|
||||
* Textual IP to InetAddress, because InetAddress.getByName() is slow.
|
||||
*
|
||||
* @since IPv6
|
||||
*/
|
||||
private static final Map<String, InetAddress> _inetAddressCache;
|
||||
|
||||
static {
|
||||
long maxMemory = Runtime.getRuntime().maxMemory();
|
||||
if (maxMemory == Long.MAX_VALUE)
|
||||
maxMemory = 96*1024*1024l;
|
||||
long min = 128;
|
||||
long max = 2048;
|
||||
// 512 nominal for 128 MB
|
||||
int size = (int) Math.max(min, Math.min(max, 1 + (maxMemory / (256*1024))));
|
||||
_inetAddressCache = new LHMCache(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Caching version of InetAddress.getByName(host), which is slow.
|
||||
* Caches numeric host names only.
|
||||
* Will resolve but not cache DNS host names.
|
||||
*
|
||||
* Unlike InetAddress.getByName(), we do NOT allow numeric IPs
|
||||
* of the form d.d.d, d.d, or d, as these are almost certainly mistakes.
|
||||
*
|
||||
* @param host DNS or IPv4 or IPv6 host name; if null returns null
|
||||
* @return InetAddress or null
|
||||
* @since IPv6
|
||||
*/
|
||||
private static InetAddress getByName(String host) {
|
||||
if (host == null)
|
||||
return null;
|
||||
InetAddress rv;
|
||||
synchronized (_inetAddressCache) {
|
||||
rv = _inetAddressCache.get(host);
|
||||
}
|
||||
if (rv == null) {
|
||||
try {
|
||||
boolean isIPv4 = host.replaceAll("[0-9\\.]", "").length() == 0;
|
||||
if (isIPv4 && host.replaceAll("[0-9]", "").length() != 3)
|
||||
return null;
|
||||
rv = InetAddress.getByName(host);
|
||||
if (isIPv4 ||
|
||||
host.replaceAll("[0-9a-fA-F:]", "").length() == 0) {
|
||||
synchronized (_inetAddressCache) {
|
||||
_inetAddressCache.put(host, rv);
|
||||
}
|
||||
}
|
||||
} catch (UnknownHostException uhe) {}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since IPv6
|
||||
*/
|
||||
static void clearCache() {
|
||||
synchronized(_inetAddressCache) {
|
||||
_inetAddressCache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,19 @@
|
||||
package net.i2p.router.transport.udp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.SocketException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Coordinate the low level datagram socket, managing the UDPSender and
|
||||
* UDPReceiver
|
||||
* Coordinate the low-level datagram socket, creating and managing the UDPSender and
|
||||
* UDPReceiver.
|
||||
*/
|
||||
class UDPEndpoint {
|
||||
private final RouterContext _context;
|
||||
@ -20,8 +24,11 @@ class UDPEndpoint {
|
||||
private UDPReceiver _receiver;
|
||||
private DatagramSocket _socket;
|
||||
private final InetAddress _bindAddress;
|
||||
private final boolean _isIPv4, _isIPv6;
|
||||
private static final AtomicInteger _counter = new AtomicInteger();
|
||||
|
||||
/**
|
||||
* @param transport may be null for unit testing ONLY
|
||||
* @param listenPort -1 or the requested port, may not be honored
|
||||
* @param bindAddress null ok
|
||||
*/
|
||||
@ -31,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;
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.router.util.CDQEntry;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -300,8 +301,7 @@ class UDPPacket implements CDQEntry {
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
buf.append(_packet.getLength());
|
||||
buf.append(" byte pkt with ");
|
||||
buf.append(_packet.getAddress().getHostAddress()).append(":");
|
||||
buf.append(_packet.getPort());
|
||||
buf.append(Addresses.toString(_packet.getAddress().getAddress(), _packet.getPort()));
|
||||
//buf.append(" id=").append(System.identityHashCode(this));
|
||||
if (_messageType >= 0)
|
||||
buf.append(" msgType=").append(_messageType);
|
||||
|
@ -730,6 +730,7 @@ class UDPPacketReader {
|
||||
return (int)DataHelper.fromLong(_message, offset, 2);
|
||||
}
|
||||
|
||||
/** @deprecated unused */
|
||||
public int readAliceIPSize() {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
@ -737,6 +738,7 @@ class UDPPacketReader {
|
||||
offset += 2;
|
||||
return (int)DataHelper.fromLong(_message, offset, 1);
|
||||
}
|
||||
/** @deprecated unused */
|
||||
public void readAliceIP(byte target[], int targetOffset) {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
@ -746,6 +748,7 @@ class UDPPacketReader {
|
||||
offset++;
|
||||
System.arraycopy(_message, offset, target, targetOffset, sz);
|
||||
}
|
||||
/** @deprecated unused */
|
||||
public int readAlicePort() {
|
||||
int offset = readBodyOffset();
|
||||
offset += DataHelper.fromLong(_message, offset, 1);
|
||||
|
@ -3,11 +3,9 @@ package net.i2p.router.transport.udp;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.FIFOBandwidthLimiter;
|
||||
import net.i2p.router.util.CoDelBlockingQueue;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
@ -20,37 +18,31 @@ import net.i2p.util.SystemVersion;
|
||||
* waiting around too long, they are dropped. Packets should be pulled off
|
||||
* from the queue ASAP by a {@link PacketHandler}
|
||||
*
|
||||
* There is a UDPReceiver for each UDPEndpoint.
|
||||
* It contains a thread but no queue. Received packets are queued
|
||||
* in the common PacketHandler queue.
|
||||
*/
|
||||
class UDPReceiver {
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
private final DatagramSocket _socket;
|
||||
private String _name;
|
||||
private final BlockingQueue<UDPPacket> _inboundQueue;
|
||||
private volatile boolean _keepRunning;
|
||||
private final Runner _runner;
|
||||
private final UDPTransport _transport;
|
||||
private static int __id;
|
||||
private final int _id;
|
||||
private final PacketHandler _handler;
|
||||
|
||||
private static final boolean _isAndroid = SystemVersion.isAndroid();
|
||||
|
||||
private static final int TYPE_POISON = -99999;
|
||||
private static final int MIN_QUEUE_SIZE = 16;
|
||||
private static final int MAX_QUEUE_SIZE = 192;
|
||||
|
||||
public UDPReceiver(RouterContext ctx, UDPTransport transport, DatagramSocket socket, String name) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(UDPReceiver.class);
|
||||
_id = ++__id;
|
||||
_name = name;
|
||||
long maxMemory = Runtime.getRuntime().maxMemory();
|
||||
if (maxMemory == Long.MAX_VALUE)
|
||||
maxMemory = 96*1024*1024l;
|
||||
int qsize = (int) Math.max(MIN_QUEUE_SIZE, Math.min(MAX_QUEUE_SIZE, maxMemory / (2*1024*1024)));
|
||||
_inboundQueue = new CoDelBlockingQueue(ctx, "UDP-Receiver", qsize);
|
||||
_socket = socket;
|
||||
_transport = transport;
|
||||
_handler = transport.getPacketHandler();
|
||||
if (_handler == null)
|
||||
throw new IllegalStateException();
|
||||
_runner = new Runner();
|
||||
//_context.statManager().createRateStat("udp.receivePacketSize", "How large packets received are", "udp", UDPTransport.RATES);
|
||||
//_context.statManager().createRateStat("udp.receiveRemaining", "How many packets are left sitting on the receiver's queue", "udp", UDPTransport.RATES);
|
||||
@ -62,24 +54,12 @@ class UDPReceiver {
|
||||
public synchronized void startup() {
|
||||
//adjustDropProbability();
|
||||
_keepRunning = true;
|
||||
I2PThread t = new I2PThread(_runner, _name + '.' + _id, true);
|
||||
I2PThread t = new I2PThread(_runner, _name, true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
public synchronized void shutdown() {
|
||||
_keepRunning = false;
|
||||
_inboundQueue.clear();
|
||||
for (int i = 0; i < _transport.getPacketHandlerCount(); i++) {
|
||||
UDPPacket poison = UDPPacket.acquire(_context, false);
|
||||
poison.setMessageType(TYPE_POISON);
|
||||
_inboundQueue.offer(poison);
|
||||
}
|
||||
for (int i = 1; i <= 5 && !_inboundQueue.isEmpty(); i++) {
|
||||
try {
|
||||
Thread.sleep(i * 50);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
_inboundQueue.clear();
|
||||
}
|
||||
|
||||
/*********
|
||||
@ -171,7 +151,7 @@ class UDPReceiver {
|
||||
}
|
||||
|
||||
// drop anything apparently from our IP (any port)
|
||||
if (Arrays.equals(from.getIP(), _transport.getExternalIP())) {
|
||||
if (Arrays.equals(from.getIP(), _transport.getExternalIP()) && !_transport.allowLocal()) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping (spoofed?) packet from ourselves");
|
||||
packet.release();
|
||||
@ -194,7 +174,7 @@ class UDPReceiver {
|
||||
if (!rejected) {
|
||||
****/
|
||||
try {
|
||||
_inboundQueue.put(packet);
|
||||
_handler.queueReceived(packet);
|
||||
} catch (InterruptedException ie) {
|
||||
packet.release();
|
||||
_keepRunning = false;
|
||||
@ -229,24 +209,6 @@ class UDPReceiver {
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Blocking call to retrieve the next inbound packet, or null if we have
|
||||
* shut down.
|
||||
*
|
||||
*/
|
||||
public UDPPacket receiveNext() {
|
||||
UDPPacket rv = null;
|
||||
//int remaining = 0;
|
||||
while (_keepRunning && rv == null) {
|
||||
try {
|
||||
rv = _inboundQueue.take();
|
||||
} catch (InterruptedException ie) {}
|
||||
if (rv != null && rv.getMessageType() == TYPE_POISON)
|
||||
return null;
|
||||
}
|
||||
//_context.statManager().addRateData("udp.receiveRemaining", remaining, 0);
|
||||
return rv;
|
||||
}
|
||||
|
||||
private class Runner implements Runnable {
|
||||
//private volatile boolean _socketChanged;
|
||||
@ -288,7 +250,10 @@ class UDPReceiver {
|
||||
// DatagramSocket javadocs: If the message is longer than the packet's length, the message is truncated.
|
||||
throw new IOException("packet too large! truncated and dropped from: " + packet.getRemoteHost());
|
||||
}
|
||||
if (size > 0) {
|
||||
if (_context.commSystem().isDummy()) {
|
||||
// testing
|
||||
packet.release();
|
||||
} else if (size > 0) {
|
||||
//FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestInbound(size, "UDP receiver");
|
||||
//_context.bandwidthLimiter().requestInbound(req, size, "UDP receiver");
|
||||
FIFOBandwidthLimiter.Request req =
|
||||
|
@ -14,6 +14,9 @@ import net.i2p.util.Log;
|
||||
/**
|
||||
* Lowest level packet sender, pushes anything on its queue ASAP.
|
||||
*
|
||||
* There is a UDPSender for each UDPEndpoint.
|
||||
* It contains a thread and a queue. Packet to be sent are queued
|
||||
* by the PacketPusher.
|
||||
*/
|
||||
class UDPSender {
|
||||
private final RouterContext _context;
|
||||
@ -23,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) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -42,10 +42,14 @@ class TunnelGatewayPumper implements Runnable {
|
||||
_context = ctx;
|
||||
_wantsPumping = new LinkedHashSet(16);
|
||||
_backlogged = new HashSet(16);
|
||||
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();
|
||||
}
|
||||
|
@ -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"; }
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user