From 05ffa63dc0703e58ec7d4b6a0829db9f398d94cc Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Sat, 2 Jun 2018 15:05:24 +0000
Subject: [PATCH] NTCP: Start NTCP2 implementation, all disabled for now

---
 .../src/net/i2p/data/router/RouterInfo.java   |  17 ++
 .../i2p/router/transport/TransportImpl.java   |  86 ++++++----
 .../router/transport/ntcp/EstablishBase.java  |  27 ++-
 .../router/transport/ntcp/EstablishState.java |   7 +
 .../transport/ntcp/InboundEstablishState.java |   9 +-
 .../router/transport/ntcp/NTCPConnection.java |   9 +-
 .../router/transport/ntcp/NTCPTransport.java  | 154 ++++++++++++++++--
 .../ntcp/OutboundEstablishState.java          |   9 +-
 8 files changed, 262 insertions(+), 56 deletions(-)

diff --git a/router/java/src/net/i2p/data/router/RouterInfo.java b/router/java/src/net/i2p/data/router/RouterInfo.java
index f0c24abcda..95b8ed728d 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 19987fd56c..5853ecc23d 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 492385e72d..815682dda4 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 2a40f69c17..24695b07dd 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 bd899b43d8..122823474a 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 65fa325534..cd8a61f025 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 e3fe2bb356..2528fb64fd 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 = "&thinsp;/&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 12decc5599..d689a65b4c 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.
-- 
GitLab