diff --git a/router/java/src/net/i2p/data/router/RouterInfo.java b/router/java/src/net/i2p/data/router/RouterInfo.java index f0c24abcda17695f8bce9762ad508061c9cd87a2..95b8ed728db95a9c0db1568cbc3d4a8ae6317069 100644 --- a/router/java/src/net/i2p/data/router/RouterInfo.java +++ b/router/java/src/net/i2p/data/router/RouterInfo.java @@ -490,6 +490,23 @@ public class RouterInfo extends DatabaseEntry { } return ret; } + + /** + * For multiple addresses per-transport (IPv4 or IPv6) + * Return addresses matching either of two styles + * + * @return non-null + * @since 0.9.35 + */ + public List<RouterAddress> getTargetAddresses(String transportStyle1, String transportStyle2) { + List<RouterAddress> ret = new ArrayList<RouterAddress>(_addresses.size()); + for (RouterAddress addr : _addresses) { + String style = addr.getTransportStyle(); + if (style.equals(transportStyle1) || style.equals(transportStyle2)) + ret.add(addr); + } + return ret; + } /** * Actually validate the signature diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index 19987fd56c2e8bfa0fc54c57061f0464619df0e1..5853ecc23deb813ec6e276f8c0b60081d4fdcb4e 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -660,45 +660,54 @@ public abstract class TransportImpl implements Transport { * @since IPv6 */ protected List<RouterAddress> getTargetAddresses(RouterInfo target) { - List<RouterAddress> rv = target.getTargetAddresses(getStyle()); + List<RouterAddress> rv; + String alt = getAltStyle(); + if (alt != null) + rv = target.getTargetAddresses(getStyle(), alt); + else + rv = target.getTargetAddresses(getStyle()); if (rv.isEmpty()) return rv; - // Shuffle so everybody doesn't use the first one - if (rv.size() > 1) + if (rv.size() > 1) { + // Shuffle so everybody doesn't use the first one Collections.shuffle(rv, _context.random()); - TransportUtil.IPv6Config config = getIPv6Config(); - int adj; - switch (config) { - case IPV6_DISABLED: - adj = 10; - /**** IPv6 addresses will be rejected in isPubliclyRoutable() - for (Iterator<RouterAddress> iter = rv.iterator(); iter.hasNext(); ) { - byte[] ip = iter.next().getIP(); - if (ip != null && ip.length == 16) - iter.remove(); - } - ****/ - 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; - /**** IPv6 addresses will be rejected in isPubliclyRoutable() - for (Iterator<RouterAddress> iter = rv.iterator(); iter.hasNext(); ) { - byte[] ip = iter.next().getIP(); - if (ip != null && ip.length == 4) - iter.remove(); - } - ****/ - break; - } - if (rv.size() > 1) + TransportUtil.IPv6Config config = getIPv6Config(); + int adj; + switch (config) { + case IPV6_DISABLED: + adj = 10; + /**** IPv6 addresses will be rejected in isPubliclyRoutable() + for (Iterator<RouterAddress> iter = rv.iterator(); iter.hasNext(); ) { + byte[] ip = iter.next().getIP(); + if (ip != null && ip.length == 16) + iter.remove(); + } + ****/ + 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; + /**** IPv6 addresses will be rejected in isPubliclyRoutable() + for (Iterator<RouterAddress> iter = rv.iterator(); iter.hasNext(); ) { + byte[] ip = iter.next().getIP(); + if (ip != null && ip.length == 4) + iter.remove(); + } + ****/ + break; + } Collections.sort(rv, new AddrComparator(adj)); + } return rv; } @@ -976,6 +985,13 @@ public abstract class TransportImpl implements Transport { } } + /** + * An alternate supported style, or null. + * @return null, override to add support + * @since 0.9.35 + */ + public String getAltStyle() { return null; } + /** * @since 0.9.3 */ diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java index 492385e72d227899a31b0767a4a4979c2cc27b85..815682dda4a73bfe4a05ce43c5269035a1820c7a 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishBase.java @@ -226,6 +226,13 @@ abstract class EstablishBase implements EstablishState { } } + /** + * Get the NTCP version + * @return 1, 2, or 0 if unknown + * @since 0.9.35 + */ + public abstract int getVersion(); + /** Anything left over in the byte buffer after verification is extra * * All data must be copied out of the buffer as Reader.processRead() @@ -337,12 +344,16 @@ abstract class EstablishBase implements EstablishState { _state = State.VERIFIED; } - @Override public void prepareOutbound() { - Log log =RouterContext.getCurrentContext().logManager().getLog(VerifiedEstablishState.class); + public int getVersion() { return 1; } + + @Override + public void prepareOutbound() { + Log log = RouterContext.getCurrentContext().logManager().getLog(VerifiedEstablishState.class); log.warn("prepareOutbound() on verified state, doing nothing!"); } - @Override public String toString() { return "VerifiedEstablishState: ";} + @Override + public String toString() { return "VerifiedEstablishState: ";} } /** @@ -355,12 +366,16 @@ abstract class EstablishBase implements EstablishState { _state = State.CORRUPT; } - @Override public void prepareOutbound() { - Log log =RouterContext.getCurrentContext().logManager().getLog(VerifiedEstablishState.class); + public int getVersion() { return 1; } + + @Override + public void prepareOutbound() { + Log log = RouterContext.getCurrentContext().logManager().getLog(VerifiedEstablishState.class); log.warn("prepareOutbound() on verified state, doing nothing!"); } - @Override public String toString() { return "FailedEstablishState: ";} + @Override + public String toString() { return "FailedEstablishState: ";} } /** diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java index 2a40f69c1780a204592f58c48a08b7a656219a87..24695b07ddb719b473088ac9d18539049da539a5 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java @@ -49,6 +49,13 @@ interface EstablishState { */ public byte[] getExtraBytes(); + /** + * Get the NTCP version + * @return 1, 2, or 0 if unknown + * @since 0.9.35 + */ + public int getVersion(); + /** * Release resources on timeout. * @param e may be null diff --git a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java index bd899b43d8bacce79ffb3183c5e064b6cf5dbea0..122823474a257701bc0168bafe63000fa616fe1b 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/InboundEstablishState.java @@ -21,7 +21,7 @@ import net.i2p.util.SimpleByteCache; /** * - * We are Bob + * NTCP 1 or 2. We are Bob. * */ class InboundEstablishState extends EstablishBase { @@ -69,6 +69,13 @@ class InboundEstablishState extends EstablishBase { receiveInbound(src); } + /** + * Get the NTCP version + * @return 1, 2, or 0 if unknown + * @since 0.9.35 + */ + public int getVersion() { return 1; } + /** * we are Bob, so receive these bytes as part of an inbound connection * This method receives messages 1 and 3, and sends messages 2 and 4. diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java index 65fa325534241f0de69add1445073f3e860a1e39..cd8a61f025b8da5bf4abce07f1ee812c54526e46 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java @@ -201,8 +201,10 @@ public class NTCPConnection implements Closeable { /** * Create an outbound unconnected NTCP connection * + * @param version must be 1 or 2 */ - public NTCPConnection(RouterContext ctx, NTCPTransport transport, RouterIdentity remotePeer, RouterAddress remAddr) { + public NTCPConnection(RouterContext ctx, NTCPTransport transport, RouterIdentity remotePeer, + RouterAddress remAddr, int version) { _context = ctx; _log = ctx.logManager().getLog(getClass()); _created = ctx.clock().now(); @@ -216,7 +218,10 @@ public class NTCPConnection implements Closeable { //_outbound = new CoDelPriorityBlockingQueue(ctx, "NTCP-Connection", 32); _outbound = new PriBlockingQueue<OutNetMessage>(ctx, "NTCP-Connection", 32); _isInbound = false; - _establishState = new OutboundEstablishState(ctx, transport, this); + //if (version == 1) + _establishState = new OutboundEstablishState(ctx, transport, this); + //else + // _establishState = // TODO _decryptBlockBuf = new byte[BLOCK_SIZE]; _curReadState = new ReadState(); _inboundListener = new InboundListener(); diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index e3fe2bb356e33ebf16ea5adb2e7a81cd80aa44c3..2528fb64fd4b18ab1a07b46224a5b4bce068b34f 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -14,17 +14,20 @@ import java.util.ArrayList; import java.util.Collection; 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; import java.util.Map; +import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import net.i2p.crypto.SigType; +import net.i2p.data.Base64; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.router.RouterAddress; @@ -104,15 +107,31 @@ public class NTCPTransport extends TransportImpl { private long _lastBadSkew; private static final long[] RATES = { 10*60*1000 }; - // Opera doesn't have the char, TODO check UA - //private static final String THINSP = " / "; - private static final String THINSP = " / "; - /** * RI sigtypes supported in 0.9.16 */ public static final String MIN_SIGTYPE_VERSION = "0.9.16"; + // NTCP2 stuff + public static final String STYLE = "NTCP"; + private static final String STYLE2 = "NTCP2"; + private static final String PROP_NTCP2_ENABLE = "i2np.ntcp2.enable"; + private static final boolean DEFAULT_NTCP2_ENABLE = false; + private boolean _enableNTCP2; + private static final String NTCP2_PROTO_SHORT = "NXK2CS"; + private static final String OPT_NTCP2_SK = 'N' + NTCP2_PROTO_SHORT + "2s"; + private static final int NTCP2_INT_VERSION = 2; + private static final String NTCP2_VERSION = Integer.toString(NTCP2_INT_VERSION); + /** b64 static private key */ + private static final String PROP_NTCP2_SP = "i2np.ntcp2.sp"; + /** b64 static IV */ + private static final String PROP_NTCP2_IV = "i2np.ntcp2.iv"; + private static final int NTCP2_IV_LEN = 16; + private static final int NTCP2_KEY_LEN = 32; + private final byte[] _ntcp2StaticPrivkey; + private final byte[] _ntcp2StaticIV; + private final String _b64Ntcp2StaticPubkey; + private final String _b64Ntcp2StaticIV; public NTCPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh) { super(ctx); @@ -202,6 +221,51 @@ public class NTCPTransport extends TransportImpl { _nearCapacityBid = new SharedBid(90); // not better than ssu - save our conns for inbound _nearCapacityCostBid = new SharedBid(105); _transientFail = new SharedBid(TransportBid.TRANSIENT_FAIL); + + _enableNTCP2 = ctx.getProperty(PROP_NTCP2_ENABLE, DEFAULT_NTCP2_ENABLE); + if (_enableNTCP2) { + boolean shouldSave = false; + byte[] priv = null; + byte[] iv = null; + String b64Pub = null; + String b64IV = null; + String s = ctx.getProperty(PROP_NTCP2_SP); + if (s != null) { + priv = Base64.decode(s); + } + if (priv == null || priv.length != NTCP2_KEY_LEN) { + priv = new byte[NTCP2_KEY_LEN]; + ctx.random().nextBytes(priv); + shouldSave = true; + } + s = ctx.getProperty(PROP_NTCP2_IV); + if (s != null) { + iv = Base64.decode(s); + b64IV = s; + } + if (iv == null || iv.length != NTCP2_IV_LEN) { + iv = new byte[NTCP2_IV_LEN]; + ctx.random().nextBytes(iv); + shouldSave = true; + } + if (shouldSave) { + Map<String, String> changes = new HashMap<String, String>(2); + String b64Priv = Base64.encode(priv); + b64IV = Base64.encode(iv); + changes.put(PROP_NTCP2_SP, b64Priv); + changes.put(PROP_NTCP2_IV, b64IV); + ctx.router().saveConfig(changes, null); + } + _ntcp2StaticPrivkey = priv; + _ntcp2StaticIV = iv; + _b64Ntcp2StaticPubkey = "TODO"; // priv->pub + _b64Ntcp2StaticIV = b64IV; + } else { + _ntcp2StaticPrivkey = null; + _ntcp2StaticIV = null; + _b64Ntcp2StaticPubkey = null; + _b64Ntcp2StaticIV = null; + } } /** @@ -242,11 +306,16 @@ public class NTCPTransport extends TransportImpl { isNew = true; RouterAddress addr = getTargetAddress(target); if (addr != null) { - con = new NTCPConnection(_context, this, ident, addr); - //if (_log.shouldLog(Log.DEBUG)) - // _log.debug("Send on a new con: " + con + " at " + addr + " for " + ih); - // Note that outbound conns go in the map BEFORE establishment - _conByIdent.put(ih, con); + int ver = getNTCPVersion(addr); + if (ver != 0) { + con = new NTCPConnection(_context, this, ident, addr, ver); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Send on a new con: " + con + " at " + addr + " for " + ih); + // Note that outbound conns go in the map BEFORE establishment + _conByIdent.put(ih, con); + } else { + fail = true; + } } else { // race, RI changed out from under us // call afterSend below outside of conLock @@ -674,6 +743,7 @@ public class NTCPTransport extends TransportImpl { OrderedProperties props = new OrderedProperties(); props.setProperty(RouterAddress.PROP_HOST, ia.getHostAddress()); props.setProperty(RouterAddress.PROP_PORT, Integer.toString(port)); + addNTCP2Options(props); int cost = getDefaultCost(ia instanceof Inet6Address); myAddress = new RouterAddress(STYLE, props, cost); replaceAddress(myAddress); @@ -786,6 +856,7 @@ public class NTCPTransport extends TransportImpl { OrderedProperties props = new OrderedProperties(); props.setProperty(RouterAddress.PROP_HOST, bindTo); props.setProperty(RouterAddress.PROP_PORT, Integer.toString(port)); + addNTCP2Options(props); int cost = getDefaultCost(false); myAddress = new RouterAddress(STYLE, props, cost); } @@ -872,6 +943,16 @@ public class NTCPTransport extends TransportImpl { public String getStyle() { return STYLE; } + /** + * An alternate supported style, or null. + * @return "NTCP2" or null + * @since 0.9.35 + */ + @Override + public String getAltStyle() { + return _enableNTCP2 ? STYLE2 : null; + } + /** * Hook for NTCPConnection */ @@ -982,11 +1063,63 @@ public class NTCPTransport extends TransportImpl { OrderedProperties props = new OrderedProperties(); props.setProperty(RouterAddress.PROP_HOST, name); props.setProperty(RouterAddress.PROP_PORT, Integer.toString(p)); + addNTCP2Options(props); int cost = getDefaultCost(false); RouterAddress addr = new RouterAddress(STYLE, props, cost); return addr; } + /** + * Add the required options to the properties for a NTCP2 address + * + * @since 0.9.35 + */ + private void addNTCP2Options(Properties props) { + if (!_enableNTCP2) + return; + props.setProperty("i", _b64Ntcp2StaticIV); + props.setProperty("n", NTCP2_PROTO_SHORT); + props.setProperty("s", _b64Ntcp2StaticPubkey); + props.setProperty("v", NTCP2_VERSION); + } + + /** + * Is NTCP2 enabled? + * + * @since 0.9.35 + */ + boolean isNTCP2Enabled() { return _enableNTCP2; } + + /** + * Get the valid NTCP version of this NTCP address. + * + * @return the valid version 1 or 2, or 0 if unusable + * @since 0.9.35 + */ + private int getNTCPVersion(RouterAddress addr) { + int rv; + String style = addr.getTransportStyle(); + if (style.equals(STYLE)) { + if (!_enableNTCP2) + return 1; + rv = 1; + } else if (style.equals(STYLE2)) { + if (!_enableNTCP2) + return 0; + rv = 2; + } else { + return 0; + } + if (addr.getOption("s") == null || + addr.getOption("i") == null || + !NTCP2_VERSION.equals(addr.getOption("v")) || + !NTCP2_PROTO_SHORT.equals(addr.getOption("n"))) { + return (rv == 1) ? 1 : 0; + } + // todo validate s/i b64, or just catch it later? + return rv; + } + /** * Return a single configured IP (as a String) or null if not configured or invalid. * Resolves a hostname to an IP. @@ -1171,6 +1304,7 @@ public class NTCPTransport extends TransportImpl { int cost; if (oldAddr == null) { cost = getDefaultCost(isIPv6); + addNTCP2Options(newProps); } else { cost = oldAddr.getCost(); newProps.putAll(oldAddr.getOptionsMap()); @@ -1463,8 +1597,6 @@ public class NTCPTransport extends TransportImpl { _lastInboundIPv6 = 0; } - public static final String STYLE = "NTCP"; - public void renderStatusHTML(java.io.Writer out, int sortFlags) throws IOException {} /** diff --git a/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java index 12decc559908d170ba13e9eeb6242a6dcdf5ef3f..d689a65b4ce1d96a0305a99d9adf8da8d0e2e122 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/OutboundEstablishState.java @@ -18,7 +18,7 @@ import net.i2p.util.SimpleByteCache; /** * - * We are Alice + * NTCP 1 only. We are Alice. * */ class OutboundEstablishState extends EstablishBase { @@ -48,6 +48,13 @@ class OutboundEstablishState extends EstablishBase { receiveOutbound(src); } + /** + * Get the NTCP version + * @return 1 + * @since 0.9.35 + */ + public int getVersion() { return 1; } + /** * We are Alice, so receive these bytes as part of an outbound connection. * This method receives messages 2 and 4, and sends message 3.