/* * 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.client; import sockslib.common.AnonymousCredentials; import sockslib.common.Credentials; import sockslib.common.SocksCommand; import sockslib.common.SocksException; import sockslib.common.UsernamePasswordCredentials; import sockslib.common.methods.GssApiMethod; import sockslib.common.methods.NoAuthenticationRequiredMethod; import sockslib.common.methods.SocksMethod; import sockslib.common.methods.SocksMethodRegistry; import sockslib.common.methods.UsernamePasswordMethod; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; /** * The class <code>Socks5</code> has implements SOCKS5 protocol. * * @author Youchao Feng * @version 1.0 * @date Mar 16, 2015 4:57:32 PM * @see <a href="http://www.ietf.org/rfc/rfc1928.txt">SOCKS Protocol Version 5</a> */ public class Socks5 implements SocksProxy { /** * Version of SOCKS protocol. */ public static final byte SOCKS_VERSION = 0x05; /** * Reserved field. */ public static final byte RESERVED = 0x00; public static final int REP_SUCCEEDED = 0x00; public static final int REP_GENERAL_SOCKS_SERVER_FAILURE = 0x01; public static final int REP_CONNECTION_NOT_ALLOWED_BY_RULESET = 0x02; public static final int REP_NETWORK_UNREACHABLE = 0x03; public static final int REP_HOST_UNREACHABLE = 0x04; public static final int REP_CONNECTION_REFUSED = 0x05; public static final int REP_TTL_EXPIRED = 0x06; public static final int REP_COMMAND_NOT_SUPPORTED = 0x07; public static final int REP_ADDRESS_TYPE_NOT_SUPPORTED = 0x08; /** * Authentication succeeded code. */ public static final byte AUTHENTICATION_SUCCEEDED = 0x00; /** * Logger. */ protected static final Logger logger = LoggerFactory.getLogger(Socks5.class); private SocksProxy chainProxy; /** * Authentication. */ private Credentials credentials = new AnonymousCredentials(); /** * SOCKS5 server's address. IPv4 or IPv6 address. */ private InetAddress inetAddress; /** * SOCKS5 server's port; */ private int port = SOCKS_DEFAULT_PORT; /** * The socket that will connect to SOCKS5 server. */ private Socket proxySocket; /** * SOCKS5 client acceptable methods. */ private List<SocksMethod> acceptableMethods; /** * Use to send a request to SOCKS server and receive a method that SOCKS server selected . */ private SocksMethodRequester socksMethodRequester = new GenericSocksMethodRequester(); /** * Use to send command to SOCKS5 sever */ private SocksCommandSender socksCmdSender = new GenericSocksCommandSender(); /** * Resolve remote server's domain name in SOCKS server if it's false. It's default false. */ private boolean alwaysResolveAddressLocally = false; /** * Constructs a Socks5 instance. * * @param socketAddress SOCKS5 server's address. * @param username Username of the authentication. * @param password Password of the authentication. */ public Socks5(SocketAddress socketAddress, String username, String password) { this(socketAddress); setCredentials(new UsernamePasswordCredentials(username, password)); } /** * Constructs a Socks5 instance. * * @param host SOCKS5's server host. * @param port SOCKS5's server port. * @throws UnknownHostException If the host can't be resolved. */ public Socks5(String host, int port) throws UnknownHostException { this(InetAddress.getByName(host), port); } /** * Constructs a Socks5 instance. * * @param inetAddress SOCKS5 server's address. * @param port SOCKS5 server's port. */ public Socks5(InetAddress inetAddress, int port) { this(new InetSocketAddress(inetAddress, port)); } /** * Constructs a Socks5 instance with a java.net.SocketAddress instance. * * @param socketAddress SOCKS5 server's address. */ public Socks5(SocketAddress socketAddress) { this(null, socketAddress); } public Socks5(SocksProxy chainProxy, SocketAddress socketAddress) { init(); if (socketAddress instanceof InetSocketAddress) { inetAddress = ((InetSocketAddress) socketAddress).getAddress(); port = ((InetSocketAddress) socketAddress).getPort(); this.setChainProxy(chainProxy); } else { throw new IllegalArgumentException("Only supports java.net.InetSocketAddress"); } } /** * Constructs a Socks instance. * * @param host SOCKS5 server's host. * @param port SOCKS5 server's port. * @param credentials credentials. * @throws UnknownHostException If the host can't be resolved. */ public Socks5(String host, int port, Credentials credentials) throws UnknownHostException { init(); this.inetAddress = InetAddress.getByName(host); this.port = port; this.credentials = credentials; } /** * Constructs a Socks5 instance without any parameter. */ private void init() { acceptableMethods = new ArrayList<>(); acceptableMethods.add(new NoAuthenticationRequiredMethod()); acceptableMethods.add(new GssApiMethod()); acceptableMethods.add(new UsernamePasswordMethod()); } @Override public void buildConnection() throws SocksException, IOException { if (inetAddress == null) { throw new IllegalArgumentException("Please set inetAddress before calling buildConnection."); } if (proxySocket == null) { proxySocket = createProxySocket(inetAddress, port); } else if (!proxySocket.isConnected()) { proxySocket.connect(new InetSocketAddress(inetAddress, port)); } SocksMethod method = socksMethodRequester.doRequest(acceptableMethods, proxySocket, SOCKS_VERSION); method.doMethod(this); } @Override public CommandReplyMessage requestConnect(String host, int port) throws SocksException, IOException { if (!alwaysResolveAddressLocally) { // resolve address in SOCKS server return socksCmdSender.send(proxySocket, SocksCommand.CONNECT, host, port, SOCKS_VERSION); } else { // resolve address in local. InetAddress address = InetAddress.getByName(host); return socksCmdSender.send(proxySocket, SocksCommand.CONNECT, address, port, SOCKS_VERSION); } } @Override public CommandReplyMessage requestConnect(InetAddress address, int port) throws SocksException, IOException { return socksCmdSender.send(proxySocket, SocksCommand.CONNECT, address, port, SOCKS_VERSION); } @Override public CommandReplyMessage requestConnect(SocketAddress address) throws SocksException, IOException { return socksCmdSender.send(proxySocket, SocksCommand.CONNECT, address, SOCKS_VERSION); } @Override public CommandReplyMessage requestBind(String host, int port) throws SocksException, IOException { return socksCmdSender.send(proxySocket, SocksCommand.BIND, host, port, SOCKS_VERSION); } @Override public CommandReplyMessage requestBind(InetAddress inetAddress, int port) throws SocksException, IOException { return socksCmdSender.send(proxySocket, SocksCommand.BIND, inetAddress, port, SOCKS_VERSION); } @Override public Socket accept() throws SocksException, IOException { CommandReplyMessage messge = socksCmdSender.checkServerReply(proxySocket.getInputStream()); logger.debug("accept a connection from:{}", messge.getSocketAddress()); return this.proxySocket; } @Override public CommandReplyMessage requestUdpAssociate(String host, int port) throws SocksException, IOException { return socksCmdSender.send(proxySocket, SocksCommand.UDP_ASSOCIATE, new InetSocketAddress (host, port), SOCKS_VERSION); } @Override public CommandReplyMessage requestUdpAssociate(InetAddress address, int port) throws SocksException, IOException { return socksCmdSender.send(proxySocket, SocksCommand.UDP_ASSOCIATE, new InetSocketAddress (address, port), SOCKS_VERSION); } @Override public int getPort() { return port; } @Override public Socks5 setPort(int port) { this.port = port; return this; } @Override public Socket getProxySocket() { return proxySocket; } @Override public Socks5 setProxySocket(Socket proxySocket) { this.proxySocket = proxySocket; return this; } @Override public InputStream getInputStream() throws IOException { return proxySocket.getInputStream(); } @Override public OutputStream getOutputStream() throws IOException { return proxySocket.getOutputStream(); } @Override public List<SocksMethod> getAcceptableMethods() { return acceptableMethods; } @Override public Socks5 setAcceptableMethods(List<SocksMethod> acceptableMethods) { this.acceptableMethods = acceptableMethods; SocksMethodRegistry.overWriteRegistry(acceptableMethods); return this; } @Override public Credentials getCredentials() { return credentials; } @Override public Socks5 setCredentials(Credentials credentials) { this.credentials = credentials; return this; } @Override public SocksMethodRequester getSocksMethodRequester() { return socksMethodRequester; } @Override public Socks5 setSocksMethodRequester(SocksMethodRequester requester) { this.socksMethodRequester = requester; return this; } @Override public SocksProxy copy() { Socks5 socks5 = new Socks5(inetAddress, port); socks5.setAcceptableMethods(acceptableMethods).setAlwaysResolveAddressLocally (alwaysResolveAddressLocally).setCredentials(credentials).setSocksMethodRequester (socksMethodRequester).setChainProxy(chainProxy); return socks5; } @Override public SocksProxy copyWithoutChainProxy() { return copy().setChainProxy(null); } @Override public int getSocksVersion() { return SOCKS_VERSION; } @Override public SocksProxy getChainProxy() { return chainProxy; } @Override public SocksProxy setChainProxy(SocksProxy chainProxy) { this.chainProxy = chainProxy; return this; } @Override public Socks5 setHost(String host) throws UnknownHostException { inetAddress = InetAddress.getByName(host); return this; } @Override public InetAddress getInetAddress() { return inetAddress; } /** * Sets SOCKS5 proxy server's IP address. * * @param inetAddress IP address of SOCKS5 proxy server. * @return The instance of {@link Socks5}. */ public Socks5 setInetAddress(InetAddress inetAddress) { this.inetAddress = inetAddress; return this; } @Override public String toString() { StringBuilder stringBuffer = new StringBuilder("[SOCKS5:"); stringBuffer.append(new InetSocketAddress(inetAddress, port)).append("]"); if (getChainProxy() != null) { return stringBuffer.append(" --> ").append(getChainProxy().toString()).toString(); } return stringBuffer.toString(); } @Override public Socket createProxySocket(InetAddress address, int port) throws IOException { return new Socket(address, port); } @Override public Socket createProxySocket() throws IOException { return new Socket(); } public boolean isAlwaysResolveAddressLocally() { return alwaysResolveAddressLocally; } public Socks5 setAlwaysResolveAddressLocally(boolean alwaysResolveAddressLocally) { this.alwaysResolveAddressLocally = alwaysResolveAddressLocally; return this; } }