From da21c0ddb7854348ed474c1102f7f264486c003f Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Mon, 2 Nov 2009 16:43:04 +0000
Subject: [PATCH]     * UDP: Pick a random port on first install or bind
 failure -       No more port 8887 to prevent easy state-level blocking

---
 initialNews.xml                               |  7 --
 .../i2p/router/transport/udp/UDPEndpoint.java | 76 ++++++++++++++---
 .../router/transport/udp/UDPTransport.java    | 83 +++++++++++++------
 3 files changed, 119 insertions(+), 47 deletions(-)

diff --git a/initialNews.xml b/initialNews.xml
index 40892c28db..35800d7d53 100644
--- a/initialNews.xml
+++ b/initialNews.xml
@@ -11,10 +11,6 @@ While you are waiting, please <b>adjust your bandwidth settings</b> on the
 <a href="config.jsp">configuration page</a>.
 </li>
 <li>
-If you can, open up <b>port 8887</b> on your firewall, then <b>enable inbound TCP</b> on the
-<a href="config.jsp">configuration page</a>.
-</li>
-<li>
 Once you have a "shared clients" destination listed on the left,
 please <b>check out</b> our
 <a href="http://www.i2p2.i2p/faq.html">FAQ</a>.
@@ -35,9 +31,6 @@ Passe bitte In der Wartezeit <b>deine Einstellungen zur Bandbreite</b> auf der
 <a href="config.jsp">Einstellungsseite</a> an.
 </li>
 <li>
-Bitte &ouml;ffne sobald m&ouml;glich  den <b>Port 8887</b> in deiner Firewall, aktiviere danach den <b>eingehenden TCP Verkehr</b> auf der <a href="config.jsp">Einstellungsseite</a>.
-</li>
-<li>
 Sobald auf der linken Seite eine "shared clients" Verbindung aufgelistet ist <b>besuche bitte</b> unsere <a href="http://www.i2p2.i2p/faq_de.html">FAQ</a>.
 </li>
 <li>
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java
index f828361e26..cffb8fd8bd 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java
@@ -22,7 +22,11 @@ public class UDPEndpoint {
     private DatagramSocket _socket;
     private InetAddress _bindAddress;
     
-    public UDPEndpoint(RouterContext ctx, UDPTransport transport, int listenPort, InetAddress bindAddress) throws SocketException {
+    /**
+     *  @param listenPort -1 or the requested port, may not be honored
+     *  @param bindAddress null ok
+     */
+    public UDPEndpoint(RouterContext ctx, UDPTransport transport, int listenPort, InetAddress bindAddress) {
         _context = ctx;
         _log = ctx.logManager().getLog(UDPEndpoint.class);
         _transport = transport;
@@ -30,23 +34,20 @@ public class UDPEndpoint {
         _listenPort = listenPort;
     }
     
+    /** caller should call getListenPort() after this to get the actual bound port and determine success */
     public void startup() {
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Starting up the UDP endpoint");
         shutdown();
-        try {
-            if (_bindAddress == null)
-                _socket = new DatagramSocket(_listenPort);
-            else
-                _socket = new DatagramSocket(_listenPort, _bindAddress);
-            _sender = new UDPSender(_context, _socket, "UDPSender");
-            _receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver");
-            _sender.startup();
-            _receiver.startup();
-        } catch (SocketException se) {
-            _transport.setReachabilityStatus(CommSystemFacade.STATUS_HOSED);
-            _log.log(Log.CRIT, "Unable to bind on port " + _listenPort, se);
+        _socket = getSocket();
+        if (_socket == null) {
+            _log.log(Log.CRIT, "UDP Unable to open a port");
+            return;
         }
+        _sender = new UDPSender(_context, _socket, "UDPSender");
+        _receiver = new UDPReceiver(_context, _transport, _socket, "UDPReceiver");
+        _sender.startup();
+        _receiver.startup();
     }
     
     public void shutdown() {
@@ -60,6 +61,8 @@ public class UDPEndpoint {
     }
     
     public void setListenPort(int newPort) { _listenPort = newPort; }
+
+/*******
     public void updateListenPort(int newPort) {
         if (newPort == _listenPort) return;
         try {
@@ -76,7 +79,54 @@ public class UDPEndpoint {
                 _log.error("Unable to bind on " + _listenPort);
         }
     }
+********/
     
+    /** 8998 is monotone, and 32000 is the wrapper, so let's stay between those */
+    private static final int MIN_RANDOM_PORT = 9111;
+    private static final int MAX_RANDOM_PORT = 31777;
+    private static final int MAX_PORT_RETRIES = 20;
+
+    /**
+     *  Open socket using requested port in _listenPort and  bind host in _bindAddress.
+     *  If _listenPort <= 0, or requested port is busy, repeatedly try a new random port.
+     *  @return null on failure
+     *  Sets _listenPort to actual port or -1 on failure
+     */
+    private DatagramSocket getSocket() {
+        DatagramSocket socket = null;
+        int port = _listenPort;
+
+        for (int i = 0; i < MAX_PORT_RETRIES; i++) {
+             if (port <= 0) {
+                 // try random ports rather than just do new DatagramSocket()
+                 // so we stay out of the way of other I2P stuff
+                 port = MIN_RANDOM_PORT + _context.random().nextInt(MAX_RANDOM_PORT - MIN_RANDOM_PORT);
+             }
+             try {
+                 if (_bindAddress == null)
+                     socket = new DatagramSocket(port);
+                 else
+                     socket = new DatagramSocket(port, _bindAddress);
+                 break;
+             } catch (SocketException se) {
+                 if (_log.shouldLog(Log.WARN))
+                     _log.warn("Binding to port " + port + " failed: " + se);
+             }
+             port = -1;
+        }
+        if (socket == null) {
+            _log.log(Log.CRIT, "SSU Unable to bind to a port on " + _bindAddress);
+        } else if (port != _listenPort) {
+            if (_listenPort > 0)
+                _log.error("SSU Unable to bind to requested port " + _listenPort + ", using random port " + port);
+            else
+                _log.error("SSU selected random port " + port);
+        }
+        _listenPort = port;
+        return socket;
+    }
+
+    /** call after startup() to get actual port or -1 on startup failure */
     public int getListenPort() { return _listenPort; }
     public UDPSender getSender() { return _sender; }
     
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
index 2bdf8a466f..2556bdb7be 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
@@ -100,6 +100,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
     
     public static final String STYLE = "SSU";
     public static final String PROP_INTERNAL_PORT = "i2np.udp.internalPort";
+    /** now unused, we pick a random port */
     public static final int DEFAULT_INTERNAL_PORT = 8887;
     /** since fixed port defaults to true, this doesnt do anything at the moment.
      *  We should have an exception if it matches the existing low port. */
@@ -137,6 +138,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
     public static final String PROP_FORCE_INTRODUCERS = "i2np.udp.forceIntroducers";
     /** do we allow direct SSU connections, sans introducers?  */
     public static final String PROP_ALLOW_DIRECT = "i2np.udp.allowDirect";
+    /** this is rarely if ever used, default is to bind to wildcard address */
     public static final String PROP_BIND_INTERFACE = "i2np.udp.bindInterface";
         
     /** how many relays offered to us will we use at a time? */
@@ -226,40 +228,41 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         System.arraycopy(_context.routerHash().getData(), 0, _introKey.getData(), 0, SessionKey.KEYSIZE_BYTES);
         
         rebuildExternalAddress();
+
+        // bind host
+        String bindTo = _context.getProperty(PROP_BIND_INTERFACE);
+        InetAddress bindToAddr = null;
+        if (bindTo != null) {
+            try {
+                bindToAddr = InetAddress.getByName(bindTo);
+            } catch (UnknownHostException uhe) {
+                _log.log(Log.CRIT, "Invalid SSU bind interface specified [" + bindTo + "]", uhe);
+                setReachabilityStatus(CommSystemFacade.STATUS_HOSED);
+                return;
+            }
+        }
         
-        int port = -1;
+        // Requested bind port
+        // This may be -1 or may not be honored if busy,
+        // we will check below after starting up the endpoint.
+        int port;
+        int oldIPort = _context.getProperty(PROP_INTERNAL_PORT, -1);
+        int oldEPort = _context.getProperty(PROP_EXTERNAL_PORT, -1);
         if (_externalListenPort <= 0) {
             // no explicit external port, so lets try an internal one
-            port = _context.getProperty(PROP_INTERNAL_PORT, DEFAULT_INTERNAL_PORT);
-            // attempt to use it as our external port - this will be overridden by
-            // externalAddressReceived(...)
-            _context.router().setConfigSetting(PROP_EXTERNAL_PORT, port+"");
-            _context.router().saveConfig();
+            if (oldIPort > 0)
+                port = oldIPort;
+            else
+                port = oldEPort;
         } else {
             port = _externalListenPort;
-            if (_log.shouldLog(Log.INFO))
-                _log.info("Binding to the explicitly specified external port: " + port);
         }
+        if (_log.shouldLog(Log.INFO))
+            _log.info("Binding to the port: " + port);
         if (_endpoint == null) {
-            String bindTo = _context.getProperty(PROP_BIND_INTERFACE);
-            InetAddress bindToAddr = null;
-            if (bindTo != null) {
-                try {
-                    bindToAddr = InetAddress.getByName(bindTo);
-                } catch (UnknownHostException uhe) {
-                    if (_log.shouldLog(Log.ERROR))
-                        _log.error("Invalid SSU bind interface specified [" + bindTo + "]", uhe);
-                    bindToAddr = null;
-                }
-            }
-            try {
-                _endpoint = new UDPEndpoint(_context, this, port, bindToAddr);
-            } catch (SocketException se) {
-                if (_log.shouldLog(Log.CRIT))
-                    _log.log(Log.CRIT, "Unable to listen on the UDP port (" + port + ")", se);
-                return;
-            }
+            _endpoint = new UDPEndpoint(_context, this, port, bindToAddr);
         } else {
+            // todo, set bind address too
             _endpoint.setListenPort(port);
         }
         
@@ -278,7 +281,24 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         if (_flooder == null)
             _flooder = new UDPFlooder(_context, this);
         
+        // Startup the endpoint with the requested port, check the actual port, and
+        // take action if it failed or was different than requested or it needs to be saved
         _endpoint.startup();
+        int newPort = _endpoint.getListenPort();
+        _externalListenPort = newPort;
+        if (newPort <= 0) {
+            _log.log(Log.CRIT, "Unable to open UDP port");
+            setReachabilityStatus(CommSystemFacade.STATUS_HOSED);
+            return;
+        }
+        if (newPort != port || newPort != oldIPort || newPort != oldEPort) {
+            // attempt to use it as our external port - this will be overridden by
+            // externalAddressReceived(...)
+            _context.router().setConfigSetting(PROP_INTERNAL_PORT, newPort+"");
+            _context.router().setConfigSetting(PROP_EXTERNAL_PORT, newPort+"");
+            _context.router().saveConfig();
+        }
+
         _establisher.startup();
         _handler.startup();
         _fragments.startup();
@@ -321,11 +341,16 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
     public int getLocalPort() { return _externalListenPort; }
     public InetAddress getLocalAddress() { return _externalListenHost; }
     public int getExternalPort() { return _externalListenPort; }
+
+    /**
+     *  _externalListenPort should always be set (by startup()) before this is called,
+     *  so the returned value should be > 0
+     */
     @Override
     public int getRequestedPort() {
         if (_externalListenPort > 0)
             return _externalListenPort;
-        return _context.getProperty(PROP_INTERNAL_PORT, DEFAULT_INTERNAL_PORT);
+        return _context.getProperty(PROP_INTERNAL_PORT, -1);
     }
 
     /**
@@ -2003,6 +2028,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         buf.append("      <td align=\"center\"><b>").append(resentTotal);
         buf.append("</b></td> <td align=\"center\"><b>").append(dupRecvTotal).append("</b></td>\n");
         buf.append(" </tr></table></div>\n");
+
+      /*****
         long bytesTransmitted = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes();
         // NPE here early
         double averagePacketSize = _context.statManager().getRate("udp.sendPacketSize").getLifetimeAverageValue();
@@ -2012,6 +2039,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         double bwResent = (nondupSent <= 0 ? 0d : ((((double)resentTotal)*averagePacketSize) / nondupSent));
         buf.append("<h3>Percentage of bytes retransmitted (lifetime): ").append(formatPct(bwResent));
         buf.append("</h3><i>(Includes retransmission required by packet loss)</i>\n");
+      *****/
+
         out.write(buf.toString());
         buf.setLength(0);
         out.write(KEY);
-- 
GitLab