/*
* 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.common.methods.SocksMethod;
import sockslib.common.net.MonitorSocketWrapper;
import sockslib.common.net.NetworkMonitor;
import sockslib.server.listener.PipeInitializer;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* The class <code>BasicSocksProxyServer</code> is a implementation of {@link SocksProxyServer}
* .<br>
* You can build a SOCKS5 server easily by following codes:<br>
* <pre>
* ProxyServer proxyServer = new BasicSocksProxyServer(Socks5Handler.class);
* proxyServer.start(); // Create a SOCKS5 server bind at 1080.
* </pre>
* <p>
* If you want change the port, you can using following codes:
* </p>
* <pre>
* proxyServer.start(9999);
* </pre>
*
* @author Youchao Feng
* @version 1.0
* @date Apr 19, 2015 1:10:17 PM
*/
public class BasicSocksProxyServer implements SocksProxyServer, Runnable {
protected static final Logger logger = LoggerFactory.getLogger(BasicSocksProxyServer.class);
/**
* Number of threads in thread pool.
*/
protected static final int THREAD_NUMBER = 100;
/**
* Thread pool used to process each connection.
*/
private ExecutorService executorService;
/**
* Session manager
*/
private SessionManager sessionManager = new BasicSessionManager();
/**
* The next session's ID.
*/
private long nextSessionId = 0;
/**
* Server socket.
*/
private ServerSocket serverSocket;
/**
* SOCKS socket handler class.
*/
private Class<? extends SocksHandler> socksHandlerClass;
/**
* Sessions that server managed.
*/
private Map<Long, Session> sessions;
/**
* A flag.
*/
private boolean stop = false;
/**
* Thread that start the server.
*/
private Thread thread;
/**
* Timeout for a session.
*/
private int timeout = 10000;
private boolean daemon = false;
/**
* Method selector.
*/
private MethodSelector methodSelector = new SocksMethodSelector();
/**
* Buffer size.
*/
private int bufferSize = 1024 * 1024 * 5;
private int bindPort = DEFAULT_SOCKS_PORT;
private InetAddress bindAddr;
private SocksProxy proxy;
private NetworkMonitor networkMonitor = new NetworkMonitor();
private PipeInitializer pipeInitializer;
/**
* Constructs a {@link BasicSocksProxyServer} by a {@link SocksHandler} class. The bind port is
* 1080.
*
* @param socketHandlerClass {@link SocksHandler} class.
*/
public BasicSocksProxyServer(Class<? extends SocksHandler> socketHandlerClass) {
this(socketHandlerClass, DEFAULT_SOCKS_PORT, Executors.newFixedThreadPool(THREAD_NUMBER));
}
/**
* Constructs a {@link BasicSocksProxyServer} by a {@link SocksHandler} class and a port.
*
* @param socketHandlerClass {@link SocksHandler} class.
* @param port The port that SOCKS server will listen.
*/
public BasicSocksProxyServer(Class<? extends SocksHandler> socketHandlerClass, int port) {
this(socketHandlerClass, port, Executors.newFixedThreadPool(THREAD_NUMBER));
}
/**
* Constructs a {@link BasicSocksProxyServer} by a {@link SocksHandler} class and a
* ExecutorService.
*
* @param socketHandlerClass {@link SocksHandler} class.
* @param executorService Thread pool.
*/
public BasicSocksProxyServer(Class<? extends SocksHandler> socketHandlerClass, ExecutorService
executorService) {
this(socketHandlerClass, DEFAULT_SOCKS_PORT, executorService);
}
/**
* Constructs a {@link BasicSocksProxyServer} by a {@link SocksHandler} class , a port and a
* ExecutorService.
*
* @param socketHandlerClass {@link SocksHandler} class.
* @param port The port that SOCKS server will listen.
* @param executorService Thread pool.
*/
public BasicSocksProxyServer(Class<? extends SocksHandler> socketHandlerClass, int port,
ExecutorService executorService) {
this.socksHandlerClass =
checkNotNull(socketHandlerClass, "Argument [socksHandlerClass] may not be null");
this.executorService =
checkNotNull(executorService, "Argument [executorService] may not be null");
this.bindPort = port;
sessions = new HashMap<>();
}
@Override
public void run() {
logger.info("Start proxy server at port:{}", bindPort);
while (!stop) {
try {
Socket socket = serverSocket.accept();
socket = processSocketBeforeUse(socket);
socket.setSoTimeout(timeout);
Session session = sessionManager.newSession(socket);
SocksHandler socksHandler = createSocksHandler();
/* initialize socks handler */
socksHandler.setSession(session);
initializeSocksHandler(socksHandler);
executorService.execute(socksHandler);
} catch (IOException e) {
// Catches the exception that cause by shutdown method.
if (e.getMessage().equals("Socket closed") && stop) {
logger.debug("Server shutdown");
return;
}
logger.debug(e.getMessage(), e);
}
}
}
@Override
public void shutdown() {
stop = true;
executorService.shutdown();
if (thread != null) {
thread.interrupt();
}
try {
closeAllSession();
if (serverSocket != null && serverSocket.isBound()) {
serverSocket.close();
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
@Override
public void start() throws IOException {
serverSocket = createServerSocket(bindPort, bindAddr);
thread = new Thread(this);
thread.setName("fs-thread");
thread.setDaemon(daemon);
thread.start();
}
protected ServerSocket createServerSocket(int bindPort, InetAddress bindAddr) throws IOException {
return new ServerSocket(bindPort, 50, bindAddr);
}
@Override
public SocksHandler createSocksHandler() {
try {
return socksHandlerClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
logger.error(e.getMessage(), e);
}
return null;
}
@Override
public void initializeSocksHandler(SocksHandler socksHandler) {
socksHandler.setMethodSelector(methodSelector);
socksHandler.setBufferSize(bufferSize);
socksHandler.setProxy(proxy);
socksHandler.setSocksProxyServer(this);
}
/**
* Closes all sessions.
*/
protected void closeAllSession() {
for (long key : sessions.keySet()) {
sessions.get(key).close();
}
}
public ExecutorService getExecutorService() {
return executorService;
}
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
private synchronized long getNextSessionId() {
nextSessionId++;
return nextSessionId;
}
@Override
public Map<Long, Session> getManagedSessions() {
return sessions;
}
@Override
public void setSupportMethods(SocksMethod... methods) {
methodSelector.setSupportMethod(methods);
}
@Override
public int getTimeout() {
return timeout;
}
@Override
public void setTimeout(int timeout) {
this.timeout = timeout;
}
@Override
public int getBufferSize() {
return bufferSize;
}
@Override
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
@Override
public SocksProxy getProxy() {
return proxy;
}
@Override
public void setProxy(SocksProxy proxy) {
this.proxy = proxy;
}
@Override
public InetAddress getBindAddr() {
return bindAddr;
}
@Override
public void setBindAddr(InetAddress bindAddr) {
this.bindAddr = bindAddr;
}
@Override
public int getBindPort() {
return bindPort;
}
@Override
public void setBindPort(int bindPort) {
this.bindPort = bindPort;
}
@Override
public boolean isDaemon() {
return daemon;
}
@Override
public void setDaemon(boolean daemon) {
this.daemon = daemon;
}
public Thread getServerThread() {
return thread;
}
public NetworkMonitor getNetworkMonitor() {
return networkMonitor;
}
public void setNetworkMonitor(NetworkMonitor networkMonitor) {
this.networkMonitor = checkNotNull(networkMonitor);
}
protected Socket processSocketBeforeUse(Socket socket) {
return new MonitorSocketWrapper(socket, networkMonitor);
}
@Override
public SessionManager getSessionManager() {
return sessionManager;
}
@Override
public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public PipeInitializer getPipeInitializer() {
return pipeInitializer;
}
@Override
public void setPipeInitializer(PipeInitializer pipeInitializer) {
this.pipeInitializer = pipeInitializer;
}
}