Start new IPv6 branch

- Add new TransportUtil for getting/setting IPv6 config
- Prep for supporting multiple RouterAddresses per-transport
- RouterAddress hashCode/equals tweaks
This commit is contained in:
zzz
2013-05-02 12:55:35 +00:00
parent 6265bdf026
commit 3ec78e27b4
11 changed files with 255 additions and 77 deletions

View File

@@ -238,14 +238,18 @@ public class RouterAddress extends DataStructureImpl {
DataHelper.writeProperties(out, _options);
}
/**
* Transport, IP, 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(getIP(), addr.getIP()) &&
DataHelper.eq(_transportStyle, addr._transportStyle);
//DataHelper.eq(_options, addr._options) &&
//DataHelper.eq(_expiration, addr._expiration);
@@ -253,13 +257,13 @@ public class RouterAddress extends DataStructureImpl {
/**
* 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();
}
/**

View File

@@ -28,8 +28,8 @@ public abstract class CommSystemFacade implements Service {
public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException { }
public void renderStatusHTML(Writer out) throws IOException { renderStatusHTML(out, null, 0); }
/** Create the set of RouterAddress structures based on the router's config */
public Set<RouterAddress> createAddresses() { return Collections.EMPTY_SET; }
/** Create the list of RouterAddress structures based on the router's config */
public List<RouterAddress> createAddresses() { return Collections.EMPTY_LIST; }
public int countActivePeers() { return 0; }
public int countActiveSendPeers() { return 0; }

View File

@@ -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;
@@ -167,7 +166,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
}
@Override
public List getMostRecentErrorMessages() {
public List<String> getMostRecentErrorMessages() {
return _manager.getMostRecentErrorMessages();
}
@@ -190,26 +189,29 @@ 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();
List<RouterAddress> addresses = new ArrayList(_manager.getAddresses());
Transport ntcp = _manager.getTransport(NTCPTransport.STYLE);
boolean hasNTCP = ntcp != null && ntcp.hasCurrentAddress();
boolean newCreated = false;
if (!addresses.containsKey(NTCPTransport.STYLE)) {
if (!hasNTCP) {
RouterAddress addr = createNTCPAddress(_context);
if (_log.shouldLog(Log.INFO))
_log.info("NTCP address: " + addr);
if (addr != null) {
addresses.put(NTCPTransport.STYLE, addr);
addresses.add(addr);
newCreated = true;
}
}
if (_log.shouldLog(Log.INFO))
_log.info("Creating addresses: " + addresses + " isNew? " + newCreated, new Exception("creator"));
return new HashSet(addresses.values());
return addresses;
}
public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
@@ -278,9 +280,20 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
NTCPTransport t = (NTCPTransport) _manager.getTransport(NTCPTransport.STYLE);
if (t == null)
return;
RouterAddress oldAddr = t.getCurrentAddress();
//////// FIXME just take first IPv4 address for now
List<RouterAddress> oldAddrs = t.getCurrentAddresses();
RouterAddress oldAddr = null;
for (RouterAddress ra : oldAddrs) {
byte[] ipx = ra.getIP();
if (ipx != null && ipx.length == 4) {
oldAddr = ra;
break;
}
}
if (_log.shouldLog(Log.INFO))
_log.info("Changing NTCP Address? was " + oldAddr);
RouterAddress newAddr = new RouterAddress();
newAddr.setTransportStyle(NTCPTransport.STYLE);
Properties newProps = new Properties();

View File

@@ -32,10 +32,29 @@ public interface Transport {
*
*/
public void send(OutNetMessage msg);
public RouterAddress startListening();
public void startListening();
public void stopListening();
public RouterAddress getCurrentAddress();
public RouterAddress updateAddress();
/**
* 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();
public static final String SOURCE_UPNP = "upnp";
public static final String SOURCE_INTERFACE = "local";
public static final String SOURCE_CONFIG = "config"; // unused
@@ -51,7 +70,7 @@ 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();

View File

@@ -10,7 +10,6 @@ package net.i2p.router.transport;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@@ -23,6 +22,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;
@@ -50,7 +50,7 @@ import net.i2p.util.SimpleTimer;
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;
@@ -87,6 +87,8 @@ 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
@@ -166,7 +168,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
@@ -464,27 +466,61 @@ public abstract class TransportImpl implements Transport {
/** Do we increase the advertised cost when approaching conn limits? */
public static final boolean ADJUST_COST = true;
/** 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;
}
/**
* 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);
if (address == null) {
_currentAddresses.clear();
} else {
byte[] ip = address.getIP();
if (ip == null) {
_log.error("WTF null ip for " + address);
return;
}
int len = ip.length;
for (RouterAddress ra : _currentAddresses) {
byte[] ipx = ra.getIP();
if (ipx != null && ipx.length == len)
_currentAddresses.remove(ra);
}
_currentAddresses.add(address);
}
if (_listener != null)
_listener.transportAddressChanged();
}

View File

@@ -14,6 +14,7 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -248,7 +249,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;
@@ -332,43 +333,67 @@ 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;
}
/**
* @since IPv6
*/
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 Map<String, Integer> getPorts() {
Map<String, Integer> rv = new HashMap(_transports.size());
private Set<Port> getPorts() {
Set<Port> rv = new HashSet(4);
for (Transport t : _transports.values()) {
int port = t.getRequestedPort();
if (t.getCurrentAddress() != null) {
String s = t.getCurrentAddress().getOption("port");
if (s != null) {
try {
port = Integer.parseInt(s);
} catch (NumberFormatException nfe) {}
}
for (RouterAddress ra : t.getCurrentAddresses()) {
int p = ra.getPort();
if (p > 0)
port = p;
// 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)) {
Transport udp = getTransport(UDPTransport.STYLE);
if (udp != null)
port = t.getRequestedPort();
}
if (port > 0)
rv.add(new Port(t.getStyle(), port));
}
// 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)) {
Transport udp = getTransport(UDPTransport.STYLE);
if (udp != null)
port = t.getRequestedPort();
}
if (port > 0)
rv.put(t.getStyle(), Integer.valueOf(port));
}
return rv;
}
@@ -485,8 +510,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());
}
@@ -510,11 +535,15 @@ public class TransportManager implements TransportEventListener {
StringBuilder buf = new StringBuilder(4*1024);
buf.append("<h3>").append(_("Router Transport Addresses")).append("</h3><pre>\n");
for (Transport t : _transports.values()) {
if (t.getCurrentAddress() != null)
buf.append(t.getCurrentAddress());
else
if (t.hasCurrentAddress()) {
for (RouterAddress ra : t.getCurrentAddresses()) {
buf.append(ra.toString());
buf.append("\n\n");
}
} else {
buf.append(_("{0} is used for outbound connections only", t.getStyle()));
buf.append("\n\n");
buf.append("\n\n");
}
}
buf.append("</pre>\n");
out.write(buf.toString());

View File

@@ -0,0 +1,78 @@
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.util.HashMap;
import java.util.Map;
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>();
static {
for (IPv6Config cfg : IPv6Config.values()) {
BY_NAME.put(cfg.toConfigString(), cfg);
}
}
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 IPv6Config.IPV6_DISABLED;
return getIPv6Config(cfg);
}
public static IPv6Config getIPv6Config(String cfg) {
if (cfg == null)
return IPv6Config.IPV6_DISABLED;
IPv6Config c = BY_NAME.get(cfg);
if (c != null)
return c;
return IPv6Config.IPV6_DISABLED;
}
}

View File

@@ -52,6 +52,7 @@ import org.freenetproject.ForwardPortStatus;
*
* @see "http://www.upnp.org/"
* @see "http://en.wikipedia.org/wiki/Universal_Plug_and_Play"
* @since 0.7.4
*/
/*

View File

@@ -25,6 +25,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 +107,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 +122,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;

View File

@@ -458,30 +458,28 @@ 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();
bindAddress();
}
/**
* Only called by CSFI.
* Caller should stop the transport first, then
* verify stopped with isAlive()
* @return appears to be ignored by caller
*/
public synchronized RouterAddress restartListening(RouterAddress addr) {
public synchronized void 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;
return;
if (_log.shouldLog(Log.WARN)) _log.warn("Restarting ntcp transport listening");
startIt();
@@ -489,7 +487,7 @@ public class NTCPTransport extends TransportImpl {
_myAddress = null;
else
_myAddress = new NTCPAddress(addr);
return bindAddress();
bindAddress();
}
/**

View File

@@ -1446,9 +1446,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
// we don't need the following, since we have our own queueing
protected void outboundMessageReady() { throw new UnsupportedOperationException("Not used for UDP"); }
public RouterAddress startListening() {
public void startListening() {
startup();
return _externalAddress;
}
public void stopListening() {
@@ -1470,9 +1469,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
* @since 0.7.12
*/
@Override
public RouterAddress updateAddress() {
public List<RouterAddress> updateAddress() {
rebuildExternalAddress(false);
return getCurrentAddress();
return getCurrentAddresses();
}
private void rebuildExternalAddress() { rebuildExternalAddress(true); }