/* * Copyright 2015-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package sockslib.server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sockslib.client.SocksProxy; import sockslib.client.SocksSocket; import sockslib.common.ProtocolErrorException; import sockslib.common.SocksException; import sockslib.common.methods.SocksMethod; import sockslib.server.io.Pipe; import sockslib.server.io.PipeListener; import sockslib.server.io.SocketPipe; import sockslib.server.msg.CommandMessage; import sockslib.server.msg.CommandResponseMessage; import sockslib.server.msg.MethodSelectionMessage; import sockslib.server.msg.MethodSelectionResponseMessage; import sockslib.server.msg.ServerReply; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; /** * The class <code>Socks5Handler</code> represents a handler that can handle SOCKS5 protocol. * * @author Youchao Feng * @version 1.0 * @date Apr 16, 2015 11:03:49 AM */ public class Socks5Handler implements SocksHandler { /** * Logger that subclasses also can use. */ protected static final Logger logger = LoggerFactory.getLogger(Socks5Handler.class); /** * Protocol version. */ private static final int VERSION = 0x5; /** * Session. */ private Session session; /** * Method selector. */ private MethodSelector methodSelector; private int bufferSize; private int idleTime = 2000; private SocksProxy proxy; private SocksProxyServer socksProxyServer; private SessionManager sessionManager; @Override public void handle(Session session) throws Exception { sessionManager = getSocksProxyServer().getSessionManager(); sessionManager.sessionOnCreate(session); MethodSelectionMessage msg = new MethodSelectionMessage(); session.read(msg); if (msg.getVersion() != VERSION) { throw new ProtocolErrorException(); } SocksMethod selectedMethod = methodSelector.select(msg); logger.debug("SESSION[{}] Response client:{}", session.getId(), selectedMethod.getMethodName()); // send select method. session.write(new MethodSelectionResponseMessage(VERSION, selectedMethod)); // do method. selectedMethod.doMethod(session); CommandMessage commandMessage = new CommandMessage(); session.read(commandMessage); // Read command request. // logger.info("SESSION[{}] request:{} {}:{}", session.getId(), commandMessage.getCommand(), // commandMessage.getAddressType() != AddressType.DOMAIN_NAME ? // commandMessage.getInetAddress() : // commandMessage.getHost(), commandMessage.getPort()); // If there is a SOCKS exception in command message, It will send a right response to client. if (commandMessage.hasSocksException()) { ServerReply serverReply = commandMessage.getSocksException().getServerReply(); session.write(new CommandResponseMessage(serverReply)); logger.info("SESSION[{}] will close, because {}", session.getId(), serverReply); return; } /**************************** DO COMMAND ******************************************/ sessionManager.sessionOnCommand(session, commandMessage); switch (commandMessage.getCommand()) { case BIND: doBind(session, commandMessage); break; case CONNECT: doConnect(session, commandMessage); break; case UDP_ASSOCIATE: doUDPAssociate(session, commandMessage); break; } } @Override public void doConnect(Session session, CommandMessage commandMessage) throws SocksException, IOException { ServerReply reply = null; Socket socket = null; InetAddress bindAddress = null; int bindPort = 0; InetAddress remoteServerAddress = commandMessage.getInetAddress(); int remoteServerPort = commandMessage.getPort(); // set default bind address. byte[] defaultAddress = {0, 0, 0, 0}; bindAddress = InetAddress.getByAddress(defaultAddress); // DO connect try { // Connect directly. if (proxy == null) { socket = new Socket(remoteServerAddress, remoteServerPort); } else { socket = new SocksSocket(proxy, remoteServerAddress, remoteServerPort); } bindAddress = socket.getLocalAddress(); bindPort = socket.getLocalPort(); reply = ServerReply.SUCCEEDED; } catch (IOException e) { if (e.getMessage().equals("Connection refused")) { reply = ServerReply.CONNECTION_REFUSED; } else if (e.getMessage().equals("Operation timed out")) { reply = ServerReply.TTL_EXPIRED; } else if (e.getMessage().equals("Network is unreachable")) { reply = ServerReply.NETWORK_UNREACHABLE; } else if (e.getMessage().equals("Connection timed out")) { reply = ServerReply.TTL_EXPIRED; } else { reply = ServerReply.GENERAL_SOCKS_SERVER_FAILURE; } logger.info("SESSION[{}] connect {} [{}] exception:{}", session.getId(), new InetSocketAddress(remoteServerAddress, remoteServerPort), reply, e.getMessage()); } CommandResponseMessage responseMessage = new CommandResponseMessage(VERSION, reply, bindAddress, bindPort); session.write(responseMessage); if (reply != ServerReply.SUCCEEDED) { // 如果返回失败信息,则退出该方法。 session.close(); return; } Pipe pipe = new SocketPipe(session.getSocket(), socket); pipe.setName("SESSION[" + session.getId() + "]"); pipe.setBufferSize(bufferSize); if(getSocksProxyServer().getPipeInitializer() != null){ pipe = getSocksProxyServer().getPipeInitializer().initialize(pipe); } pipe.start(); // This method will build tow thread to run tow internal pipes. // wait for pipe exit. while (pipe.isRunning()) { try { Thread.sleep(idleTime); } catch (InterruptedException e) { pipe.stop(); session.close(); logger.info("SESSION[{}] closed", session.getId()); } } } @Override public void doBind(Session session, CommandMessage commandMessage) throws SocksException, IOException { ServerSocket serverSocket = new ServerSocket(commandMessage.getPort()); int bindPort = serverSocket.getLocalPort(); Socket socket = null; logger.info("Create TCP server bind at {} for session[{}]", serverSocket .getLocalSocketAddress(), session.getId()); session.write(new CommandResponseMessage(VERSION, ServerReply.SUCCEEDED, serverSocket .getInetAddress(), bindPort)); socket = serverSocket.accept(); session.write(new CommandResponseMessage(VERSION, ServerReply.SUCCEEDED, socket .getLocalAddress(), socket.getLocalPort())); Pipe pipe = new SocketPipe(session.getSocket(), socket); pipe.setBufferSize(bufferSize); pipe.start(); // wait for pipe exit. while (pipe.isRunning()) { try { Thread.sleep(idleTime); } catch (InterruptedException e) { pipe.stop(); session.close(); logger.info("Session[{}] closed", session.getId()); } } serverSocket.close(); // throw new NotImplementException("Not implement BIND command"); } @Override public void doUDPAssociate(Session session, CommandMessage commandMessage) throws SocksException, IOException { UDPRelayServer udpRelayServer = new UDPRelayServer(((InetSocketAddress) session.getClientAddress()).getAddress(), commandMessage.getPort()); InetSocketAddress socketAddress = (InetSocketAddress) udpRelayServer.start(); logger.info("Create UDP relay server at[{}] for {}", socketAddress, commandMessage .getSocketAddress()); session.write(new CommandResponseMessage(VERSION, ServerReply.SUCCEEDED, InetAddress .getLocalHost(), socketAddress.getPort())); while (udpRelayServer.isRunning()) { try { Thread.sleep(idleTime); } catch (InterruptedException e) { session.close(); logger.info("Session[{}] closed", session.getId()); } if (session.isClose()) { udpRelayServer.stop(); logger.debug("UDP relay server for session[{}] is closed", session.getId()); } } } @Override public void setSession(Session session) { this.session = session; } @Override public void run() { try { handle(session); } catch (Exception e) { sessionManager.sessionOnException(session, e); // logger.error("SESSION[{}]: {}", session.getId(), e.getMessage()); } finally { session.close(); sessionManager.sessionOnClose(session); // logger.info("SESSION[{}] closed, {}", session.getId(), session.getNetworkMonitor().toString // ()); } } @Override public MethodSelector getMethodSelector() { return methodSelector; } @Override public void setMethodSelector(MethodSelector methodSelector) { this.methodSelector = methodSelector; } @Override public int getBufferSize() { return bufferSize; } @Override public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } @Override public int getIdleTime() { return idleTime; } @Override public void setIdleTime(int idleTime) { this.idleTime = idleTime; } public SocksProxy getProxy() { return proxy; } @Override public void setProxy(SocksProxy proxy) { this.proxy = proxy; } @Override public SocksProxyServer getSocksProxyServer() { return socksProxyServer; } @Override public void setSocksProxyServer(SocksProxyServer socksProxyServer) { this.socksProxyServer = socksProxyServer; } }