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 static final 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         ProxyIoSessionrg/apache/mina/proxy/session/ProxyIoSession.html#ProxyIoSession">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"../../../../../org/apache/mina/core/buffer/IoBuffer.html#IoBuffer">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                 if (LOGGER.isDebugEnabled()) {
157                     LOGGER.debug(" Data Read: {} ({})", handler, buf);
158                 }
159 
160                 // Keep sending handshake data to the handler until we run out
161                 // of data or the handshake is finished
162                 while (buf.hasRemaining() && !handler.isHandshakeComplete()) {
163                     if (LOGGER.isDebugEnabled()) {
164                         LOGGER.debug(" Pre-handshake - passing to handler");
165                     }
166 
167                     int pos = buf.position();
168                     handler.messageReceived(nextFilter, buf);
169 
170                     // Data not consumed or session closing
171                     if (buf.position() == pos || session.isClosing()) {
172                         return;
173                     }
174                 }
175 
176                 // Pass on any remaining data to the next filter
177                 if (buf.hasRemaining()) {
178                     if (LOGGER.isDebugEnabled()) {
179                         LOGGER.debug(" Passing remaining data to next filter");
180                     }
181 
182                     nextFilter.messageReceived(session, buf);
183                 }
184             }
185         }
186     }
187 
188     /**
189      * Filters outgoing writes, queueing them up if necessary while a handshake 
190      * is ongoing.
191      * 
192      * @param nextFilter the next filter in filter chain
193      * @param session the session object
194      * @param writeRequest the data to write
195      */
196     @Override
197     public void filterWrite(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest) {
198         writeData(nextFilter, session, writeRequest, false);
199     }
200 
201     /**
202      * Actually write data. Queues the data up unless it relates to the handshake or the 
203      * handshake is done.
204      * 
205      * @param nextFilter the next filter in filter chain
206      * @param session the session object
207      * @param writeRequest the data to write
208      * @param isHandshakeData true if writeRequest is written by the proxy classes.
209      */
210     public void writeData(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest,
211             final boolean isHandshakeData) {
212         ProxyLogicHandler handler = getProxyHandler(session);
213 
214         synchronized (handler) {
215             if (handler.isHandshakeComplete()) {
216                 // Handshake is done - write data as normal
217                 nextFilter.filterWrite(session, writeRequest);
218             } else if (isHandshakeData) {
219                 if (LOGGER.isDebugEnabled()) {
220                     LOGGER.debug("   handshake data: {}", writeRequest.getMessage());
221                 }
222 
223                 // Writing handshake data
224                 nextFilter.filterWrite(session, writeRequest);
225             } else {
226                 // Writing non-handshake data before the handshake finished
227                 if (!session.isConnected()) {
228                     // Not even connected - ignore
229                     if (LOGGER.isDebugEnabled()) {
230                         LOGGER.debug(" Write request on closed session. Request ignored.");
231                     }
232                 } else {
233                     // Queue the data to be sent as soon as the handshake completes
234                     if (LOGGER.isDebugEnabled()) {
235                         LOGGER.debug(" Handshaking is not complete yet. Buffering write request.");
236                     }
237                     
238                     handler.enqueueWriteRequest(nextFilter, writeRequest);
239                 }
240             }
241         }
242     }
243 
244     /**
245      * Filter handshake related messages from reaching the messageSent callbacks of 
246      * downstream filters.
247      * 
248      * @param nextFilter the next filter in filter chain
249      * @param session the session object
250      * @param writeRequest the data written
251      */
252     @Override
253     public void messageSent(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest)
254             throws Exception {
255         if (writeRequest.getMessage() != null && writeRequest.getMessage() instanceof ProxyHandshakeIoBuffer) {
256             // Ignore buffers used in handshaking
257             return;
258         }
259 
260         nextFilter.messageSent(session, writeRequest);
261     }
262 
263     /**
264      * Called when the session is created. Will create the handler able to handle
265      * the {@link ProxyIoSession#getRequest()} request stored in the session. Event
266      * is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
267      * in the chain when the handshake would have succeed. This will prevent the rest of 
268      * the filter chain from being affected by this filter internals. 
269      * 
270      * Please note that this event can occur multiple times because of some http 
271      * proxies not handling keep-alive connections thus needing multiple sessions 
272      * during the handshake.
273      * 
274      * @param nextFilter the next filter in filter chain
275      * @param session the session object
276      */
277     @Override
278     public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {
279         if (LOGGER.isDebugEnabled()) {
280             LOGGER.debug("Session created: " + session);
281         }
282         
283         ProxyIoSessionrg/apache/mina/proxy/session/ProxyIoSession.html#ProxyIoSession">ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
284         
285         if (LOGGER.isDebugEnabled()) {
286             LOGGER.debug("  get proxyIoSession: " + proxyIoSession);
287         }
288         
289         proxyIoSession.setProxyFilter(this);
290 
291         // Create a HTTP proxy handler and start handshake.
292         ProxyLogicHandler handler = proxyIoSession.getHandler();
293 
294         // This test prevents from loosing handler conversationnal state when
295         // reconnection occurs during an http handshake.
296         if (handler == null) {
297             ProxyRequest request = proxyIoSession.getRequest();
298 
299             if (request instanceof SocksProxyRequest) {
300                 SocksProxyRequest/../../org/apache/mina/proxy/handlers/socks/SocksProxyRequest.html#SocksProxyRequest">SocksProxyRequest req = (SocksProxyRequest) request;
301                 if (req.getProtocolVersion() == SocksProxyConstants.SOCKS_VERSION_4) {
302                     handler = new Socks4LogicHandler(proxyIoSession);
303                 } else {
304                     handler = new Socks5LogicHandler(proxyIoSession);
305                 }
306             } else {
307                 handler = new HttpSmartProxyHandler(proxyIoSession);
308             }
309 
310             proxyIoSession.setHandler(handler);
311             handler.doHandshake(nextFilter);
312         }
313 
314         proxyIoSession.getEventQueue().enqueueEventIfNecessary(
315                 new IoSessionEvent(nextFilter, session, IoSessionEventType.CREATED));
316     }
317 
318     /**
319      * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
320      * in the chain when the handshake would have succeed. This will prevent the rest of 
321      * the filter chain from being affected by this filter internals.
322      * 
323      * @param nextFilter the next filter in filter chain
324      * @param session the session object
325      */
326     @Override
327     public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception {
328         ProxyIoSessionrg/apache/mina/proxy/session/ProxyIoSession.html#ProxyIoSession">ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
329         proxyIoSession.getEventQueue().enqueueEventIfNecessary(
330                 new IoSessionEvent(nextFilter, session, IoSessionEventType.OPENED));
331     }
332 
333     /**
334      * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
335      * in the chain when the handshake would have succeed. This will prevent the rest of 
336      * the filter chain from being affected by this filter internals.
337      * 
338      * @param nextFilter the next filter in filter chain
339      * @param session the session object
340      */
341     @Override
342     public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception {
343         ProxyIoSessionrg/apache/mina/proxy/session/ProxyIoSession.html#ProxyIoSession">ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
344         proxyIoSession.getEventQueue().enqueueEventIfNecessary(new IoSessionEvent(nextFilter, session, status));
345     }
346 
347     /**
348      * Event is stored in an {@link IoSessionEventQueue} for later delivery to the next filter
349      * in the chain when the handshake would have succeed. This will prevent the rest of 
350      * the filter chain from being affected by this filter internals.
351      * 
352      * @param nextFilter the next filter in filter chain
353      * @param session the session object
354      */
355     @Override
356     public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception {
357         ProxyIoSessionrg/apache/mina/proxy/session/ProxyIoSession.html#ProxyIoSession">ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION);
358         proxyIoSession.getEventQueue().enqueueEventIfNecessary(
359                 new IoSessionEvent(nextFilter, session, IoSessionEventType.CLOSED));
360     }
361 }