View Javadoc

    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one
    3    *  or more contributor license agreements.  See the NOTICE file
    4    *  distributed with this work for additional information
    5    *  regarding copyright ownership.  The ASF licenses this file
    6    *  to you under the Apache License, Version 2.0 (the
    7    *  "License"); you may not use this file except in compliance
    8    *  with the License.  You may obtain a copy of the License at
    9    *
    10   *    http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   *  Unless required by applicable law or agreed to in writing,
    13   *  software distributed under the License is distributed on an
    14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   *  KIND, either express or implied.  See the License for the
    16   *  specific language governing permissions and limitations
    17   *  under the License.
    18   *
    19   */
    20  package org.apache.mina.proxy.filter;
    21
    22  import org.apache.mina.core.buffer.IoBuffer;
    23  import org.apache.mina.core.filterchain.IoFilter;
    24  import org.apache.mina.core.filterchain.IoFilterAdapter;
    25  import org.apache.mina.core.filterchain.IoFilterChain;
    26  import org.apache.mina.core.session.IdleStatus;
    27  import org.apache.mina.core.session.IoSession;
    28  import org.apache.mina.core.write.WriteRequest;
    29  import org.apache.mina.proxy.ProxyAuthException;
    30  import org.apache.mina.proxy.ProxyConnector;
    31  import org.apache.mina.proxy.ProxyLogicHandler;
    32  import org.apache.mina.proxy.event.IoSessionEvent;
    33  import org.apache.mina.proxy.event.IoSessionEventQueue;
    34  import org.apache.mina.proxy.event.IoSessionEventType;
    35  import org.apache.mina.proxy.handlers.ProxyRequest;
    36  import org.apache.mina.proxy.handlers.http.HttpSmartProxyHandler;
    37  import org.apache.mina.proxy.handlers.socks.Socks4LogicHandler;
    38  import org.apache.mina.proxy.handlers.socks.Socks5LogicHandler;
    39  import org.apache.mina.proxy.handlers.socks.SocksProxyConstants;
    40  import org.apache.mina.proxy.handlers.socks.SocksProxyRequest;
    41  import org.apache.mina.proxy.session.ProxyIoSession;
    42  import org.slf4j.Logger;
    43  import org.slf4j.LoggerFactory;
    44
    45  /**
    46   * ProxyFilter.java - Proxy {@link IoFilter}.
    47   * Automatically inserted into the {@link IoFilter} chain by {@link ProxyConnector}.
    48   * Sends the initial handshake message to the proxy and handles any response
    49   * to the handshake. Once the handshake has completed and the proxied connection has been
    50   * established this filter becomes transparent to data flowing through the connection.
    51   * <p>
    52   * Based upon SSLFilter from mina-filter-ssl.
    53   *
    54   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    55   * @since MINA 2.0.0-M3
    56   */
    57  public class ProxyFilter extends IoFilterAdapter {
    58      private final static Logger LOGGER = LoggerFactory.getLogger(ProxyFilter.class);
    59
    60      /**
    61       * Create a new {@link ProxyFilter}.
    62       */
    63      public ProxyFilter() {
    64          // Do nothing
    65      }
    66
    67      /**
    68       * Called before the filter is added into the filter chain.
    69       * Checks if chain already holds an {@link ProxyFilter} instance.
    70       *
    71       * @param chain the filter chain
    72       * @param name the name assigned to this filter
    73       * @param nextFilter the next filter
    74       * @throws IllegalStateException if chain already contains an instance of
    75       * {@link ProxyFilter}
    76       */
    77      @Override
    78      public void onPreAdd(final IoFilterChain chain, final String name, final NextFilter nextFilter) {
    79          if (chain.contains(ProxyFilter.class)) {
    80              throw new IllegalStateException("A filter chain cannot contain more than one ProxyFilter.");
    81          }
    82      }
    83
    84      /**
    85       * Called when the filter is removed from the filter chain.
    86       * Cleans the {@link ProxyIoSession} instance from the session.
    87       *
    88       * @param chain the filter chain
    89       * @param name the name assigned to this filter
    90       * @param nextFilter the next filter
    91       */
    92      @Override
    93      public void onPreRemove(final IoFilterChain chain, final String name, final NextFilter nextFilter) {
    94          IoSession session = chain.getSession();
    95          session.removeAttribute(ProxyIoSession.PROXY_SESSION);
    96      }
    97
    98      /**
    99       * Called when an exception occurs in the chain. A flag is set in the
    100      * {@link ProxyIoSession} session's instance to signal that handshake
    101      * failed.
    102      *
    103      * @param chain the filter chain
    104      * @param name the name assigned to this filter
    105      * @param nextFilter the next filter
    106      */
    107     @Override
    108     public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception {
    109         ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
    110         proxyIoSession.setAuthenticationFailed(true);
    111         super.exceptionCaught(nextFilter, session, cause);
    112     }
    113
    114     /**
    115      * Get the {@link ProxyLogicHandler} for a given session.
    116      *
    117      * @param session the session object
    118      * @return the handler which will handle handshaking with the proxy
    119      */
    120     private ProxyLogicHandler getProxyHandler(final IoSession session) {
    121         ProxyLogicHandler handler = ((ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION)).getHandler();
    122
    123         if (handler == null) {
    124             throw new IllegalStateException();
    125         }
    126
    127         // Sanity check
    128         if (handler.getProxyIoSession().getProxyFilter() != this) {
    129             throw new IllegalArgumentException("Not managed by this filter.");
    130         }
    131
    132         return handler;
    133     }
    134
    135     /**
    136      * Receives data from the remote host, passes to the handler if a handshake is in progress,
    137      * otherwise passes on transparently.
    138      *
    139      * @param nextFilter the next filter in filter chain
    140      * @param session the session object
    141      * @param message the object holding the received data
    142      */
    143     @Override
    144     public void messageReceived(final NextFilter nextFilter, final IoSession session, final Object message)
    145             throws ProxyAuthException {
    146         ProxyLogicHandler handler = getProxyHandler(session);
    147
    148         synchronized (handler) {
    149             IoBuffer buf = (IoBuffer) message;
    150
    151             if (handler.isHandshakeComplete()) {
    152                 // Handshake done - pass data on as-is
    153                 nextFilter.messageReceived(session, buf);
    154
    155             } else {
    156                 LOGGER.debug(" Data Read: {} ({})", handler, buf);
    157
    158                 // Keep sending handshake data to the handler until we run out
    159                 // of data or the handshake is finished
    160                 while (buf.hasRemaining() && !handler.isHandshakeComplete()) {
    161                     LOGGER.debug(" Pre-handshake - passing to handler");
    162
    163                     int pos = buf.position();
    164                     handler.messageReceived(nextFilter, buf);
    165
    166                     // Data not consumed or session closing
    167                     if (buf.position() == pos || session.isClosing()) {
    168                         return;
    169                     }
    170                 }
    171
    172                 // Pass on any remaining data to the next filter
    173                 if (buf.hasRemaining()) {
    174                     LOGGER.debug(" Passing remaining data to next filter");
    175
    176                     nextFilter.messageReceived(session, buf);
    177                 }
    178             }
    179         }
    180     }
    181
    182     /**
    183      * Filters outgoing writes, queueing them up if necessary while a handshake
    184      * is ongoing.
    185      *
    186      * @param nextFilter the next filter in filter chain
    187      * @param session the session object
    188      * @param writeRequest the data to write
    189      */
    190     @Override
    191     public void filterWrite(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest) {
    192         writeData(nextFilter, session, writeRequest, false);
    193     }
    194
    195     /**
    196      * Actually write data. Queues the data up unless it relates to the handshake or the
    197      * handshake is done.
    198      *
    199      * @param nextFilter the next filter in filter chain
    200      * @param session the session object
    201      * @param writeRequest the data to write
    202      * @param isHandshakeData true if writeRequest is written by the proxy classes.
    203      */
    204     public void writeData(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest,
    205             final boolean isHandshakeData) {
    206         ProxyLogicHandler handler = getProxyHandler(session);
    207
    208         synchronized (handler) {
    209             if (handler.isHandshakeComplete()) {
    210                 // Handshake is done - write data as normal
    211                 nextFilter.filterWrite(session, writeRequest);
    212             } else if (isHandshakeData) {
    213                 LOGGER.debug("   handshake data: {}", writeRequest.getMessage());
    214
    215                 // Writing handshake data
    216                 nextFilter.filterWrite(session, writeRequest);
    217             } else {
    218                 // Writing non-handshake data before the handshake finished
    219                 if (!session.isConnected()) {
    220                     // Not even connected - ignore
    221                     LOGGER.debug(" Write request on closed session. Request ignored.");
    222                 } else {
    223                     // Queue the data to be sent as soon as the handshake completes
    224                     LOGGER.debug(" Handshaking is not complete yet. Buffering write request.");
    225                     handler.enqueueWriteRequest(nextFilter, writeRequest);
    226                 }
    227             }
    228         }
    229     }
    230
    231     /**
    232      * Filter handshake related messages from reaching the messageSent callbacks of
    233      * downstream filters.
    234      *
    235      * @param nextFilter the next filter in filter chain
    236      * @param session the session object
    237      * @param writeRequest the data written
    238      */
    239     @Override
    240     public void messageSent(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest)
    241             throws Exception {
    242         if (writeRequest.getMessage() != null && writeRequest.getMessage() instanceof ProxyHandshakeIoBuffer) {
    243             // Ignore buffers used in handshaking
    244             return;
    245         }
    246
    247         nextFilter.messageSent(session, writeRequest);
    248     }
    249
    250     /**
    251      * Called when the session is created. Will create the handler able to handle
    252      * the {@link ProxyIoSession#getRequest()} request stored in the session. Event
    253      * is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
    254      * in the chain when the handshake would have succeed. This will prevent the rest of
    255      * the filter chain from being affected by this filter internals.
    256      *
    257      * Please note that this event can occur multiple times because of some http
    258      * proxies not handling keep-alive connections thus needing multiple sessions
    259      * during the handshake.
    260      *
    261      * @param nextFilter the next filter in filter chain
    262      * @param session the session object
    263      */
    264     @Override
    265     public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {
    266         LOGGER.debug("Session created: " + session);
    267         ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
    268         LOGGER.debug("  get proxyIoSession: " + proxyIoSession);
    269         proxyIoSession.setProxyFilter(this);
    270
    271         // Create a HTTP proxy handler and start handshake.
    272         ProxyLogicHandler handler = proxyIoSession.getHandler();
    273
    274         // This test prevents from loosing handler conversationnal state when
    275         // reconnection occurs during an http handshake.
    276         if (handler == null) {
    277             ProxyRequest request = proxyIoSession.getRequest();
    278
    279             if (request instanceof SocksProxyRequest) {
    280                 SocksProxyRequest req = (SocksProxyRequest) request;
    281                 if (req.getProtocolVersion() == SocksProxyConstants.SOCKS_VERSION_4) {
    282                     handler = new Socks4LogicHandler(proxyIoSession);
    283                 } else {
    284                     handler = new Socks5LogicHandler(proxyIoSession);
    285                 }
    286             } else {
    287                 handler = new HttpSmartProxyHandler(proxyIoSession);
    288             }
    289
    290             proxyIoSession.setHandler(handler);
    291             handler.doHandshake(nextFilter);
    292         }
    293
    294         proxyIoSession.getEventQueue().enqueueEventIfNecessary(
    295                 new IoSessionEvent(nextFilter, session, IoSessionEventType.CREATED));
    296     }
    297
    298     /**
    299      * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
    300      * in the chain when the handshake would have succeed. This will prevent the rest of
    301      * the filter chain from being affected by this filter internals.
    302      *
    303      * @param nextFilter the next filter in filter chain
    304      * @param session the session object
    305      */
    306     @Override
    307     public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception {
    308         ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
    309         proxyIoSession.getEventQueue().enqueueEventIfNecessary(
    310                 new IoSessionEvent(nextFilter, session, IoSessionEventType.OPENED));
    311     }
    312
    313     /**
    314      * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
    315      * in the chain when the handshake would have succeed. This will prevent the rest of
    316      * the filter chain from being affected by this filter internals.
    317      *
    318      * @param nextFilter the next filter in filter chain
    319      * @param session the session object
    320      */
    321     @Override
    322     public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception {
    323         ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
    324         proxyIoSession.getEventQueue().enqueueEventIfNecessary(new IoSessionEvent(nextFilter, session, status));
    325     }
    326
    327     /**
    328      * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
    329      * in the chain when the handshake would have succeed. This will prevent the rest of
    330      * the filter chain from being affected by this filter internals.
    331      *
    332      * @param nextFilter the next filter in filter chain
    333      * @param session the session object
    334      */
    335     @Override
    336     public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception {
    337         ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
    338         proxyIoSession.getEventQueue().enqueueEventIfNecessary(
    339                 new IoSessionEvent(nextFilter, session, IoSessionEventType.CLOSED));
    340     }
    341 }