/*
* 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 sockslib.common.Socks5DatagramPacketHandler;
import sockslib.common.net.MonitorDatagramSocketWrapper;
import sockslib.common.net.NetworkMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
/**
* The class <code>UDPRelayServer</code> represents a UDP relay server.
* <p>
* The UDP relay server will receive datagram packets from a client and transmit them to the
* specified server. It will also receive datagram packets from other UDP servers and send them to
* client. UDP relay server must need to know the client's IP address and port to find out where the
* datagram packet from, because UDP is not long connection protocol.
* </p>
*
* @author Youchao Feng
* @version 1.0
* @date Apr 22, 2015 12:54:50 AM
*/
public class UDPRelayServer implements Runnable {
/**
* Logger that subclasses also can use.
*/
protected static final Logger logger = LoggerFactory.getLogger(UDPRelayServer.class);
/**
* SOCKS5 datagram packet handle.
*/
private Socks5DatagramPacketHandler datagramPacketHandler = new Socks5DatagramPacketHandler();
/**
* UDP server.
*/
private DatagramSocket server;
/**
* Buffer size.
*/
private int bufferSize = 1024 * 1024 * 5;
/**
* Running thread.
*/
private Thread thread;
/**
* A status flag.
*/
private boolean running = false;
/**
* Client's IP address.
*/
private InetAddress clientAddress;
/**
* Client's port.
*/
private int clientPort;
private NetworkMonitor networkMonitor;
/**
* Constructs a {@link UDPRelayServer} instance.
*/
public UDPRelayServer() {
}
/**
* Constructs a {@link UDPRelayServer} instance with client's IP address and port. The UDP relay
* server will use client's IP and port to find out where the datagram packet from.
*
* @param clientInetAddress Client's IP address.
* @param clientPort Client's port.
*/
public UDPRelayServer(InetAddress clientInetAddress, int clientPort) {
this(new InetSocketAddress(clientInetAddress, clientPort));
}
public UDPRelayServer(SocketAddress clientSocketAddress) {
if (clientSocketAddress instanceof InetSocketAddress) {
clientAddress = ((InetSocketAddress) clientSocketAddress).getAddress();
clientPort = ((InetSocketAddress) clientSocketAddress).getPort();
} else {
throw new IllegalArgumentException("Only support java.net.InetSocketAddress");
}
}
/**
* Starts a UDP relay server.
*
* @return Server bind socket address.
* @throws SocketException If a SOCKS protocol error occurred.
*/
public SocketAddress start() throws SocketException {
running = true;
server = new DatagramSocket();
if (networkMonitor != null) {
server = new MonitorDatagramSocketWrapper(server, networkMonitor);
}
SocketAddress socketAddress = server.getLocalSocketAddress();
thread = new Thread(this);
thread.start();
return socketAddress;
}
/**
* Stop the UDP relay server.
*/
public void stop() {
if (running) {
running = false;
thread.interrupt();
if (!server.isClosed()) {
server.close();
}
}
}
@Override
public void run() {
try {
byte[] buffer = new byte[bufferSize];
while (running) {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
server.receive(packet);
if (isFromClient(packet)) {
datagramPacketHandler.decapsulate(packet);
server.send(packet);
} else {
packet =
datagramPacketHandler.encapsulate(packet, new InetSocketAddress(clientAddress,
clientPort));
server.send(packet);
}
}
} catch (IOException e) {
if (e.getMessage().equalsIgnoreCase("Socket closed") && !running) {
logger.debug("UDP relay server stopped");
} else {
logger.error(e.getMessage(), e);
}
}
}
/**
* Returns <code>true</code> if the the datagram packet from client.
*
* @param packet Datagram packet the UDP server received.
* @return If the datagram packet is sent from client, it will return <code>true</code>.
*/
protected boolean isFromClient(DatagramPacket packet) {
if (packet.getPort() == clientPort && clientAddress.equals(packet.getAddress())) {
return true;
}
// client is in local.
else if (packet.getPort() == clientPort && clientAddress.getHostAddress().startsWith("127.")) {
return true;
}
return false;
}
/**
* Return UDP server.
*
* @return UDP server.
*/
public DatagramSocket getServer() {
return server;
}
/**
* Sets UDP server.
*
* @param server UDP server.
*/
public void setServer(DatagramSocket server) {
this.server = server;
}
/**
* Returns buffer size.
*
* @return Buffer size.
*/
public int getBufferSize() {
return bufferSize;
}
/**
* Sets buffer size.
*
* @param bufferSize Buffer size.
*/
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
/**
* Returns datagram packet handler.
*
* @return the instance of {@link Socks5DatagramPacketHandler}.
*/
public Socks5DatagramPacketHandler getDatagramPacketHandler() {
return datagramPacketHandler;
}
/**
* Sets datagram packet handler.
*
* @param datagramPacketHandler Datagram packet handler.
*/
public void setDatagramPacketHandler(Socks5DatagramPacketHandler datagramPacketHandler) {
this.datagramPacketHandler = datagramPacketHandler;
}
/**
* Returns client's IP address.
*
* @return client's IP address.
*/
public InetAddress getClientAddress() {
return clientAddress;
}
/**
* Sets client's IP address.
*
* @param clientAddress client's IP address.
*/
public void setClientAddress(InetAddress clientAddress) {
this.clientAddress = clientAddress;
}
/**
* Returns client's port.
*
* @return client's port.
*/
public int getClientPort() {
return clientPort;
}
/**
* Sets client's port.
*
* @param clientPort client's port.
*/
public void setClientPort(int clientPort) {
this.clientPort = clientPort;
}
/**
* Return <code>true</code> if the UDP relay server is running.
*
* @return If the UDP relay server is running, it will return <code>true</code>.
*/
public boolean isRunning() {
return running;
}
public NetworkMonitor getNetworkMonitor() {
return networkMonitor;
}
public void setNetworkMonitor(NetworkMonitor networkMonitor) {
this.networkMonitor = networkMonitor;
}
public Thread getServerThread() {
return thread;
}
}