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 nextFilter next filter in the filter chain
104      * @param session the MINA session
105      * @param cause the original exception
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 }