001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.proxy.filter; 021 022import org.apache.mina.core.buffer.IoBuffer; 023import org.apache.mina.core.filterchain.IoFilter; 024import org.apache.mina.core.filterchain.IoFilterAdapter; 025import org.apache.mina.core.filterchain.IoFilterChain; 026import org.apache.mina.core.session.IdleStatus; 027import org.apache.mina.core.session.IoSession; 028import org.apache.mina.core.write.WriteRequest; 029import org.apache.mina.proxy.ProxyAuthException; 030import org.apache.mina.proxy.ProxyConnector; 031import org.apache.mina.proxy.ProxyLogicHandler; 032import org.apache.mina.proxy.event.IoSessionEvent; 033import org.apache.mina.proxy.event.IoSessionEventQueue; 034import org.apache.mina.proxy.event.IoSessionEventType; 035import org.apache.mina.proxy.handlers.ProxyRequest; 036import org.apache.mina.proxy.handlers.http.HttpSmartProxyHandler; 037import org.apache.mina.proxy.handlers.socks.Socks4LogicHandler; 038import org.apache.mina.proxy.handlers.socks.Socks5LogicHandler; 039import org.apache.mina.proxy.handlers.socks.SocksProxyConstants; 040import org.apache.mina.proxy.handlers.socks.SocksProxyRequest; 041import org.apache.mina.proxy.session.ProxyIoSession; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045/** 046 * ProxyFilter.java - Proxy {@link IoFilter}. 047 * Automatically inserted into the {@link IoFilter} chain by {@link ProxyConnector}. 048 * Sends the initial handshake message to the proxy and handles any response 049 * to the handshake. Once the handshake has completed and the proxied connection has been 050 * established this filter becomes transparent to data flowing through the connection. 051 * <p> 052 * Based upon SSLFilter from mina-filter-ssl. 053 * 054 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 055 * @since MINA 2.0.0-M3 056 */ 057public class ProxyFilter extends IoFilterAdapter { 058 private final static Logger LOGGER = LoggerFactory.getLogger(ProxyFilter.class); 059 060 /** 061 * Create a new {@link ProxyFilter}. 062 */ 063 public ProxyFilter() { 064 // Do nothing 065 } 066 067 /** 068 * Called before the filter is added into the filter chain. 069 * Checks if chain already holds an {@link ProxyFilter} instance. 070 * 071 * @param chain the filter chain 072 * @param name the name assigned to this filter 073 * @param nextFilter the next filter 074 * @throws IllegalStateException if chain already contains an instance of 075 * {@link ProxyFilter} 076 */ 077 @Override 078 public void onPreAdd(final IoFilterChain chain, final String name, final NextFilter nextFilter) { 079 if (chain.contains(ProxyFilter.class)) { 080 throw new IllegalStateException("A filter chain cannot contain more than one ProxyFilter."); 081 } 082 } 083 084 /** 085 * Called when the filter is removed from the filter chain. 086 * Cleans the {@link ProxyIoSession} instance from the session. 087 * 088 * @param chain the filter chain 089 * @param name the name assigned to this filter 090 * @param nextFilter the next filter 091 */ 092 @Override 093 public void onPreRemove(final IoFilterChain chain, final String name, final NextFilter nextFilter) { 094 IoSession session = chain.getSession(); 095 session.removeAttribute(ProxyIoSession.PROXY_SESSION); 096 } 097 098 /** 099 * 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}