Changeset 4f84550 for apps


Ignore:
Timestamp:
Feb 1, 2019 1:39:57 PM (16 months ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
30015c1
Parents:
428fb26
Message:

i2ptunnel: Caching of outproxy selection, avoid last-failed outproxy

Location:
apps/i2ptunnel/java/src/net/i2p/i2ptunnel
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java

    r428fb26 r4f84550  
    226226                        if (!usingInternalOutproxy) {
    227227                            // The request must be forwarded to a outproxy
    228                             currentProxy = selectProxy();
     228                            currentProxy = selectProxy(hostLowerCase);
    229229                            if (currentProxy == null) {
    230230                                if (_log.shouldLog(Log.WARN))
     
    348348            else
    349349                response = SUCCESS_RESPONSE.getBytes("UTF-8");
    350             OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
    351             Thread t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
     350            OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy,
     351                                                currentProxy, requestId, targetRequest, false);
     352            I2PTunnelRunner t = new I2PTunnelRunner(s, i2ps, sockLock, data, response, mySockets, onTimeout);
     353            if (usingWWWProxy) {
     354                // isSSL must be false for ConnectClient
     355                t.setSuccessCallback(new OnProxySuccess(currentProxy, host, false));
     356            }
    352357            // we are called from an unlimited thread pool, so run inline
    353358            //t.start();
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java

    r428fb26 r4f84550  
    368368    public static final String PROP_JUMP_SERVERS = "i2ptunnel.httpclient.jumpServers";
    369369    public static final String PROP_DISABLE_HELPER = "i2ptunnel.httpclient.disableAddressHelper";
    370     /** @since 0.9.11 */
    371     public static final String PROP_SSL_OUTPROXIES = "i2ptunnel.httpclient.SSLOutproxies";
    372370    /** @since 0.9.14 */
    373371    public static final String PROP_ACCEPT = "i2ptunnel.httpclient.sendAccept";
     
    407405            InputReader reader = new InputReader(s.getInputStream());
    408406            String line, method = null, protocol = null, host = null, destination = null;
     407            String hostLowerCase = null;
    409408            StringBuilder newRequest = new StringBuilder();
    410409            boolean ahelperPresent = false;
     
    582581                    // and it is removed from the request line.
    583582
    584                     String hostLowerCase = host.toLowerCase(Locale.US);
     583                    hostLowerCase = host.toLowerCase(Locale.US);
    585584                    if(hostLowerCase.equals(LOCAL_SERVER)) {
    586585                        // so we don't do any naming service lookups
     
    870869                            if ("https".equals(protocol) ||
    871870                                method.toUpperCase(Locale.US).equals("CONNECT"))
    872                                 currentProxy = selectSSLProxy();
     871                                currentProxy = selectSSLProxy(hostLowerCase);
    873872                            else
    874                                 currentProxy = selectProxy();
     873                                currentProxy = selectProxy(hostLowerCase);
    875874                            if(_log.shouldLog(Log.DEBUG)) {
    876875                                _log.debug("After selecting outproxy for " + host + ": " + currentProxy);
     
    946945                        // But we don't create a Host: line if it wasn't sent to us
    947946                        line = "Host: " + host;
    948                         if(_log.shouldLog(Log.INFO)) {
    949                             _log.info(getPrefix(requestId) + "Setting host = " + host);
     947                        if (_log.shouldDebug()) {
     948                            _log.debug(getPrefix(requestId) + "Setting host = " + host);
    950949                        }
    951950                    } else if(lowercaseLine.startsWith("user-agent: ")) {
     
    13071306                sktOpts.setPort(remotePort);
    13081307            i2ps = createI2PSocket(clientDest, sktOpts);
    1309             OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
    1310             Thread t;
    1311             if (method.toUpperCase(Locale.US).equals("CONNECT")) {
     1308            boolean isConnect = method.toUpperCase(Locale.US).equals("CONNECT");
     1309            OnTimeout onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy,
     1310                                                currentProxy, requestId, hostLowerCase, isConnect);
     1311            I2PTunnelRunner t;
     1312            if (isConnect) {
    13121313                byte[] data;
    13131314                byte[] response;
     
    13231324                byte[] data = newRequest.toString().getBytes("ISO-8859-1");
    13241325                t = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
     1326            }
     1327            if (usingWWWProxy) {
     1328                t.setSuccessCallback(new OnProxySuccess(currentProxy, hostLowerCase, isConnect));
    13251329            }
    13261330            // we are called from an unlimited thread pool, so run inline
     
    13461350            if (i2ps != null) try { i2ps.close(); } catch (IOException ioe) {}
    13471351        }
    1348     }
    1349 
    1350     /**
    1351      *  Unlike selectProxy(), we parse the option on the fly so it
    1352      *  can be changed. selectProxy() requires restart...
    1353      *  @return null if none
    1354      *  @since 0.9.11
    1355      */
    1356     private String selectSSLProxy() {
    1357         String s = getTunnel().getClientOptions().getProperty(PROP_SSL_OUTPROXIES);
    1358         if (s == null)
    1359             return null;
    1360         String[] p = DataHelper.split(s, "[,; \r\n\t]");
    1361         if (p.length == 0)
    1362             return null;
    1363         // todo doesn't check for ""
    1364         if (p.length == 1)
    1365             return p[0];
    1366         int i = _context.random().nextInt(p.length);
    1367         return p[i];
    13681352    }
    13691353
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java

    r428fb26 r4f84550  
    1919import java.net.URI;
    2020import java.net.URISyntaxException;
     21import java.util.Arrays;
    2122import java.util.ArrayList;
    2223import java.util.BitSet;
     
    4344import net.i2p.util.EventDispatcher;
    4445import net.i2p.util.InternalSocket;
     46import net.i2p.util.LHMCache;
    4547import net.i2p.util.Log;
    4648import net.i2p.util.PasswordManager;
     
    6567    /** @since 0.9.11, moved to Base in 0.9.29 */
    6668    public static final String PROP_USE_OUTPROXY_PLUGIN = "i2ptunnel.useLocalOutproxy";
     69    /** @since 0.9.11, moved to Base in 0.9.39 */
     70    public static final String PROP_SSL_OUTPROXIES = "i2ptunnel.httpclient.SSLOutproxies";
     71
    6772    /**
    6873     *  This is a standard soTimeout, not a total timeout.
     
    123128    private final ConcurrentHashMap<String, NonceInfo> _nonces;
    124129    private final AtomicInteger _nonceCleanCounter = new AtomicInteger();
     130    // clearnet host to proxy
     131    private final Map<String, String> _proxyCache = new LHMCache<String, String>(32);
     132    // very simple, remember last-failed only
     133    private String _lastFailedProxy;
     134    // clearnet host to proxy
     135    private final Map<String, String> _proxySSLCache = new LHMCache<String, String>(32);
     136    // very simple, remember last-failed only
     137    private String _lastFailedSSLProxy;
    125138
    126139    protected String getPrefix(long requestId) {
    127140        return "HTTPClient[" + _clientId + '/' + requestId + "]: ";
    128141    }
     142
     143    // TODO standard proxy config changes require tunnel restart;
     144    // SSL proxy config is parsed on the fly;
     145    // allow both to be changed and store the SSL proxy list.
     146    // TODO should track more than one failed proxy
    129147   
    130     protected String selectProxy() {
     148    /**
     149     *  Simple random selection, with caching by hostname,
     150     *  and avoidance of the last one to fail.
     151     *
     152     *  @param host the clearnet hostname we're targeting
     153     *  @return null if none configured
     154     */
     155    protected String selectProxy(String host) {
     156        String rv;
    131157        synchronized (_proxyList) {
    132158            int size = _proxyList.size();
    133159            if (size <= 0)
    134160                return null;
    135             int index = _context.random().nextInt(size);
    136             return _proxyList.get(index);
    137         }
     161            if (size == 1)
     162                return _proxyList.get(0);
     163            rv = _proxyCache.get(host);
     164            if (rv == null) {
     165                List<String> tmpList;
     166                if (_lastFailedProxy != null) {
     167                    // don't use last failed one
     168                    tmpList = new ArrayList<String>(_proxyList);
     169                    tmpList.remove(_lastFailedProxy);
     170                    size = tmpList.size();
     171                } else {
     172                    tmpList = _proxyList;
     173                }
     174                int index = _context.random().nextInt(size);
     175                rv = tmpList.get(index);
     176                _proxyCache.put(host, rv);
     177            }
     178        }
     179        if (_log.shouldInfo())
     180            _log.info("Selected proxy for " + host + ": " + rv);
     181        return rv;
     182    }
     183
     184    /**
     185     *  Only for SSL via HTTPClient. ConnectClient should use selectProxy()
     186     *
     187     *  Unlike selectProxy(), we parse the option on the fly so it
     188     *  can be changed. selectProxy() requires restart...
     189     *
     190     *  @return null if none configured
     191     *  @since 0.9.11, moved from I2PTunnelHTTPClient in 0.9.39
     192     */
     193    protected String selectSSLProxy(String host) {
     194        String s = getTunnel().getClientOptions().getProperty(PROP_SSL_OUTPROXIES);
     195        if (s == null)
     196            return null;
     197        String[] p = DataHelper.split(s, "[,; \r\n\t]");
     198        int size = p.length;
     199        if (size == 0)
     200            return null;
     201        // todo doesn't check for ""
     202        if (size == 1)
     203            return p[0];
     204        String rv;
     205        synchronized (_proxySSLCache) {
     206            rv = _proxySSLCache.get(host);
     207            if (rv == null) {
     208                List<String> tmpList;
     209                if (_lastFailedSSLProxy != null) {
     210                    // don't use last failed one
     211                    tmpList = new ArrayList<String>(Arrays.asList(p));
     212                    tmpList.remove(_lastFailedSSLProxy);
     213                    size = tmpList.size();
     214                } else {
     215                    tmpList = Arrays.asList(p);
     216                }
     217                int index = _context.random().nextInt(size);
     218                rv = tmpList.get(index);
     219                _proxySSLCache.put(host, rv);
     220            }
     221        }
     222        if (_log.shouldInfo())
     223            _log.info("Selected SSL proxy for " + host + ": " + rv);
     224        return rv;
     225    }
     226   
     227    /**
     228     *  Update the cache and note if failed.
     229     *
     230     *  @param proxy which
     231     *  @param host clearnet hostname targeted
     232     *  @param isSSL set to FALSE for ConnectClient
     233     *  @param ok success or failure
     234     *  @since 0.9.39
     235     */
     236    protected void noteProxyResult(String proxy, String host, boolean isSSL, boolean ok) {
     237        if (isSSL) {
     238            synchronized (_proxySSLCache) {
     239                if (ok) {
     240                    if (proxy.equals(_lastFailedSSLProxy))
     241                        _lastFailedSSLProxy = null;
     242                    _proxySSLCache.put(host, proxy);
     243                } else {
     244                    _lastFailedSSLProxy = proxy;
     245                    if (proxy.equals(_proxySSLCache.get(host)))
     246                        _proxySSLCache.remove(host);
     247                }
     248            }
     249        } else {
     250            synchronized (_proxyList) {
     251                if (_proxyList.size() > 1) {
     252                    if (ok) {
     253                        if (proxy.equals(_lastFailedProxy))
     254                            _lastFailedProxy = null;
     255                        _proxyCache.put(host, proxy);
     256                    } else {
     257                        _lastFailedProxy = proxy;
     258                        if (proxy.equals(_proxyCache.get(host)))
     259                            _proxyCache.remove(host);
     260                    }
     261                }
     262            }
     263        }
     264        if (_log.shouldInfo())
     265            _log.info("Proxy result: to " + host + " through " + proxy + " SSL? " + isSSL + " success? " + ok);
    138266    }
    139267
     
    611739        private final String _wwwProxy;
    612740        private final long _requestId;
    613 
     741        private final String _targetHost;
     742        private final boolean _isSSL;
     743
     744        /**
     745         *  @param target the URI for an HTTP request, or the host name for CONNECT
     746         */
    614747        public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy, String wwwProxy, long id) {
    615748            _socket = s;
     
    619752            _wwwProxy = wwwProxy;
    620753            _requestId = id;
     754            _targetHost = null;
     755            _isSSL = false;
     756        }
     757
     758        /**
     759         *  @param target the URI for an HTTP request, or the host name for CONNECT
     760         *  @param targetHost if non-null, call noteProxyResult() with this as host
     761         *  @param isSSL to pass to noteProxyResult(). FALSE for ConnectClient.
     762         *  @since 0.9.39
     763         */
     764        public OnTimeout(Socket s, OutputStream out, String target, boolean usingProxy,
     765                         String wwwProxy, long id, String targetHost, boolean isSSL) {
     766            _socket = s;
     767            _out = out;
     768            _target = target;
     769            _usingProxy = usingProxy;
     770            _wwwProxy = wwwProxy;
     771            _requestId = id;
     772            _targetHost = targetHost;
     773            _isSSL = isSSL;
    621774        }
    622775
     
    625778         */
    626779        public void onFail(Exception ex) {
     780            if (_usingProxy && _targetHost != null) {
     781                noteProxyResult(_wwwProxy, _targetHost, _isSSL, false);
     782            }
    627783            Throwable cause = ex != null ? ex.getCause() : null;
    628784            if (cause != null && cause instanceof I2PSocketException) {
     
    633789            }
    634790            closeSocket(_socket);
     791        }
     792    }
     793
     794    /**
     795     *  @since 0.9.39
     796     */
     797    protected class OnProxySuccess implements I2PTunnelRunner.SuccessCallback {
     798        private final String _proxy, _host;
     799        private final boolean _isSSL;
     800
     801        /** @param isSSL FALSE for ConnectClient */
     802        public OnProxySuccess(String proxy, String host, boolean isSSL) {
     803            _proxy = proxy; _host = host; _isSSL = isSSL;
     804        }
     805
     806        public void onSuccess() {
     807            noteProxyResult(_proxy, _host, _isSSL, true);
    635808        }
    636809    }
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java

    r428fb26 r4f84550  
    6161    private final Runnable onTimeout;
    6262    private final FailCallback _onFail;
     63    private SuccessCallback _onSuccess;
    6364    private long totalSent;
    6465    private long totalReceived;
     
    7374         */
    7475        public void onFail(Exception e);
     76    }
     77
     78    /**
     79     *  @since 0.9.39
     80     */
     81    public interface SuccessCallback {
     82        /**
     83         *  @param e may be null
     84         */
     85        public void onSuccess();
    7586    }
    7687
     
    227238    public long getStartedOn() {
    228239        return startedOn;
     240    }
     241
     242    /**
     243     * Will be called if we get any data back.
     244     * This is called after the first byte of data is received, not on completion.
     245     * Only one of SuccessCallback, onTimeout, or onFail will be called.
     246     *
     247     * @since 0.9.39
     248     */
     249    public void setSuccessCallback(SuccessCallback sc) {
     250        _onSuccess = sc;
    229251    }
    230252
     
    283305            if (!(s instanceof InternalSocket))
    284306                in = new BufferedInputStream(in, 2*NETWORK_BUFFER_SIZE);
    285             toI2P = new StreamForwarder(in, i2pout, true);
    286             fromI2P = new StreamForwarder(i2pin, out, false);
     307            toI2P = new StreamForwarder(in, i2pout, true, null);
     308            fromI2P = new StreamForwarder(i2pin, out, false, _onSuccess);
    287309            toI2P.start();
    288310            // We are already a thread, so run the second one inline
     
    295317            }
    296318            if (_log.shouldLog(Log.DEBUG))
    297                 _log.debug("At least one forwarder completed, closing and joining");
     319                _log.debug("Both forwarders completed, sent: " + totalSent + " received: " + totalReceived);
    298320           
    299321            // this task is useful for the httpclient
    300322            if ((onTimeout != null || _onFail != null) && totalReceived <= 0) {
    301                 if (_log.shouldLog(Log.DEBUG))
    302                     _log.debug("runner has a timeout job, totalReceived = " + totalReceived
    303                                + " totalSent = " + totalSent + " job = " + onTimeout);
     323                //if (_log.shouldLog(Log.DEBUG))
     324                //     _log.debug("runner has a timeout job, totalReceived = " + totalReceived
     325                //                + " totalSent = " + totalSent + " job = " + onTimeout);
    304326                // Run even if totalSent > 0, as that's probably POST data.
    305327                // This will be run even if initialSocketData != null, it's the timeout job's
     
    461483        private final boolean _toI2P;
    462484        private final ByteCache _cache;
     485        private final SuccessCallback _callback;
    463486        private volatile Exception _failure;
    464487
    465488        /**
    466489         *  Does not start itself. Caller must start()
     490         *  @param cb may be null, only used for toI2P == false
    467491         */
    468         public StreamForwarder(InputStream in, OutputStream out, boolean toI2P) {
     492        public StreamForwarder(InputStream in, OutputStream out, boolean toI2P, SuccessCallback cb) {
    469493            this.in = in;
    470494            this.out = out;
    471495            _toI2P = toI2P;
     496            _callback = cb;
    472497            direction = (toI2P ? "toI2P" : "fromI2P");
    473498            _cache = ByteCache.getInstance(32, NETWORK_BUFFER_SIZE);
     
    496521                    if (len > 0) {
    497522                        out.write(buffer, 0, len);
    498                         if (_toI2P)
     523                        if (_toI2P) {
    499524                            totalSent += len;
    500                         else
     525                        } else {
     526                            if (totalReceived == 0 && _callback != null)
     527                                _callback.onSuccess();
    501528                            totalReceived += len;
     529                        }
    502530                        //updateActivity();
    503531                    }
Note: See TracChangeset for help on using the changeset viewer.