/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
* with an additional exception. For further details, see the
* licensing terms in I2PTunnel.java.
*
* Copyright (c) 2004 by human
*/
package net.i2p.i2ptunnel.socks;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppManager;
import net.i2p.app.Outproxy;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
/*
* Class that manages SOCKS5 connections, and forwards them to
* destination hosts or (eventually) some outproxy.
*
* @author human
*/
class SOCKS5Server extends SOCKSServer {
private static final int SOCKS_VERSION_5 = 0x05;
private boolean setupCompleted = false;
private final boolean authRequired;
/**
* Create a SOCKS5 server that communicates with the client using
* the specified socket. This method should not be invoked
* directly: new SOCKS5Server objects should be created by using
* SOCKSServerFactory.createSOCSKServer(). It is assumed that the
* SOCKS VER field has been stripped from the input stream of the
* client socket.
*
* @param clientSock client socket
* @param props non-null
*/
public SOCKS5Server(I2PAppContext ctx, Socket clientSock, Properties props) {
super(ctx, clientSock, props);
this.authRequired =
Boolean.parseBoolean(props.getProperty(I2PTunnelHTTPClientBase.PROP_AUTH)) &&
props.containsKey(I2PTunnelHTTPClientBase.PROP_USER) &&
props.containsKey(I2PTunnelHTTPClientBase.PROP_PW);
}
public Socket getClientSocket() throws SOCKSException {
setupServer();
return clientSock;
}
protected void setupServer() throws SOCKSException {
if (setupCompleted) { return; }
DataInputStream in;
DataOutputStream out;
try {
in = new DataInputStream(clientSock.getInputStream());
out = new DataOutputStream(clientSock.getOutputStream());
init(in, out);
if (manageRequest(in, out) == Command.UDP_ASSOCIATE)
handleUDP(in, out);
} catch (IOException e) {
throw new SOCKSException("Connection error: " + e);
}
setupCompleted = true;
}
/**
* SOCKS5 connection initialization. This method assumes that
* SOCKS "VER" field has been stripped from the input stream.
*/
private void init(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
int nMethods = in.readUnsignedByte();
int method = Method.NO_ACCEPTABLE_METHODS;
for (int i = 0; i < nMethods; ++i) {
int meth = in.readUnsignedByte();
if (((!authRequired) && meth == Method.NO_AUTH_REQUIRED) ||
(authRequired && meth == Method.USERNAME_PASSWORD)) {
// That's fine, we do support this method
method = meth;
}
}
switch (method) {
case Method.USERNAME_PASSWORD:
_log.debug("username/password authentication required");
sendInitReply(Method.USERNAME_PASSWORD, out);
verifyPassword(in, out);
return;
case Method.NO_AUTH_REQUIRED:
_log.debug("no authentication required");
sendInitReply(Method.NO_AUTH_REQUIRED, out);
return;
default:
_log.debug("no suitable authentication methods found (" + Integer.toHexString(method) + ")");
sendInitReply(Method.NO_ACCEPTABLE_METHODS, out);
throw new SOCKSException("Unsupported authentication method");
}
}
/**
* Wait for the username/password message and verify or throw SOCKSException on failure
* @since 0.8.2
*/
private void verifyPassword(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
int c = in.readUnsignedByte();
if (c != AUTH_VERSION) {
_log.logAlways(Log.WARN, "SOCKS proxy authentication failed");
throw new SOCKSException("Unsupported authentication version");
}
c = in.readUnsignedByte();
if (c <= 0) {
_log.logAlways(Log.WARN, "SOCKS proxy authentication failed");
throw new SOCKSException("Bad authentication");
}
byte[] user = new byte[c];
String u = new String(user, "UTF-8");
in.readFully(user);
c = in.readUnsignedByte();
if (c <= 0) {
_log.logAlways(Log.WARN, "SOCKS proxy authentication failed, user: " + u);
throw new SOCKSException("Bad authentication");
}
byte[] pw = new byte[c];
in.readFully(pw);
// Hopefully these are in UTF-8, since that's what our config file is in
// these throw UnsupportedEncodingException which is an IOE
String p = new String(pw, "UTF-8");
String configUser = props.getProperty(I2PTunnelHTTPClientBase.PROP_USER);
String configPW = props.getProperty(I2PTunnelHTTPClientBase.PROP_PW);
if ((!u.equals(configUser)) || (!p.equals(configPW))) {
_log.logAlways(Log.WARN, "SOCKS proxy authentication failed, user: " + u);
sendAuthReply(AUTH_FAILURE, out);
throw new SOCKSException("SOCKS authorization failure");
}
if (_log.shouldLog(Log.INFO))
_log.info("SOCKS authorization success, user: " + u);
sendAuthReply(AUTH_SUCCESS, out);
}
/**
* SOCKS5 request management. This method assumes that all the
* stuff preceding or enveloping the actual request (e.g. protocol
* initialization, integrity/confidentiality encapsulations, etc)
* has been stripped out of the input/output streams.
*/
private int manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
int socksVer = in.readUnsignedByte();
if (socksVer != SOCKS_VERSION_5) {
_log.debug("error in SOCKS5 request (protocol != 5?)");
throw new SOCKSException("Invalid protocol version in request: " + socksVer);
}
int command = in.readUnsignedByte();
switch (command) {
case Command.CONNECT:
break;
case Command.BIND:
_log.debug("BIND command is not supported!");
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("BIND command not supported");
case Command.UDP_ASSOCIATE:
/*** if(!Boolean.parseBoolean(tunnel.getOptions().getProperty("i2ptunnel.socks.allowUDP"))) {
_log.debug("UDP ASSOCIATE command is not supported!");
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("UDP ASSOCIATE command not supported");
***/
break;
default:
_log.debug("unknown command in request (" + Integer.toHexString(command) + ")");
sendRequestReply(Reply.COMMAND_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("Invalid command in request");
}
// Reserved byte, should be 0x00
in.readByte();
addressType = in.readUnsignedByte();
switch (addressType) {
case AddressType.IPV4:
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 4; ++i) {
int octet = in.readUnsignedByte();
builder.append(Integer.toString(octet));
if (i != 3) {
builder.append(".");
}
}
connHostName = builder.toString();
// Check if the requested IP should be mapped to a domain name
String mappedDomainName = getMappedDomainNameForIP(connHostName);
if (mappedDomainName != null) {
_log.debug("IPV4 address " + connHostName + " was mapped to domain name " + mappedDomainName);
addressType = AddressType.DOMAINNAME;
connHostName = mappedDomainName;
} else if (command != Command.UDP_ASSOCIATE)
_log.warn("IPV4 address type in request: " + connHostName + ". Is your client secure?");
break;
case AddressType.DOMAINNAME:
{
int addrLen = in.readUnsignedByte();
if (addrLen == 0) {
_log.debug("0-sized address length?");
throw new SOCKSException("Illegal DOMAINNAME length");
}
byte addr[] = new byte[addrLen];
in.readFully(addr);
connHostName = DataHelper.getUTF8(addr);
}
_log.debug("DOMAINNAME address type in request: " + connHostName);
break;
case AddressType.IPV6:
if (command != Command.UDP_ASSOCIATE) {
_log.warn("IP V6 address type in request! Is your client secure?" + " (IPv6 is not supported, anyway :-)");
sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("IPV6 addresses not supported");
}
break;
default:
_log.debug("unknown address type in request (" + Integer.toHexString(command) + ")");
sendRequestReply(Reply.ADDRESS_TYPE_NOT_SUPPORTED, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("Invalid addresses type in request");
}
connPort = in.readUnsignedShort();
if (connPort == 0) {
_log.debug("trying to connect to TCP port 0? Dropping!");
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
throw new SOCKSException("Invalid port number in request");
}
return command;
}
protected void confirmConnection() throws SOCKSException {
DataOutputStream out;
try {
out = new DataOutputStream(clientSock.getOutputStream());
sendRequestReply(Reply.SUCCEEDED, AddressType.IPV4, InetAddress.getByName("127.0.0.1"), null, 1, out);
} catch (IOException e) {
throw new SOCKSException("Connection error: " + e);
}
}
/**
* Send the specified reply during SOCKS5 initialization
*/
private void sendInitReply(int replyCode, DataOutputStream out) throws IOException {
byte[] reply = new byte[2];
reply[0] = SOCKS_VERSION_5;
reply[1] = (byte) replyCode;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending init reply:\n" + HexDump.dump(reply));
out.write(reply);
}
/**
* Send the specified reply during SOCKS5 authorization
* @since 0.8.2
*/
private void sendAuthReply(int replyCode, DataOutputStream out) throws IOException {
byte[] reply = new byte[2];
reply[0] = AUTH_VERSION;
reply[1] = (byte) replyCode;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending auth reply:\n" + HexDump.dump(reply));
out.write(reply);
}
/**
* Send the specified reply to a request of the client. Either
* one of inetAddr or domainName can be null, depending on
* addressType.
*/
private void sendRequestReply(int replyCode, int addressType, InetAddress inetAddr, String domainName,
int bindPort, DataOutputStream out) throws IOException {
ByteArrayOutputStream reps = new ByteArrayOutputStream();
DataOutputStream dreps = new DataOutputStream(reps);
dreps.write(SOCKS_VERSION_5);
dreps.write(replyCode);
// Reserved byte, should be 0x00
dreps.write(0x00);
dreps.write(addressType);
switch (addressType) {
case AddressType.IPV4:
dreps.write(inetAddr.getAddress());
break;
case AddressType.DOMAINNAME:
dreps.writeByte(domainName.length());
dreps.writeBytes(domainName);
break;
default:
_log.error("unknown address type passed to sendReply() (" + Integer.toHexString(addressType) + ")!");
return;
}
dreps.writeShort(bindPort);
byte[] reply = reps.toByteArray();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Sending request reply:\n" + HexDump.dump(reply));
}
out.write(reply);
}
/**
* Get an I2PSocket that can be used to send/receive 8-bit clean data
* to/from the destination of the SOCKS connection.
*
* @return an I2PSocket connected with the destination
*/
public I2PSocket getDestinationI2PSocket(I2PSOCKSTunnel t) throws SOCKSException {
setupServer();
if (connHostName == null) {
_log.error("BUG: destination host name has not been initialized!");
throw new SOCKSException("BUG! See the logs!");
}
if (connPort == 0) {
_log.error("BUG: destination port has not been initialized!");
throw new SOCKSException("BUG! See the logs!");
}
DataOutputStream out; // for errors
try {
out = new DataOutputStream(clientSock.getOutputStream());
} catch (IOException e) {
throw new SOCKSException("Connection error: " + e);
}
// FIXME: here we should read our config file, select an
// outproxy, and instantiate the proper socket class that
// handles the outproxy itself (SOCKS4a, SOCKS5, HTTP CONNECT...).
I2PSocket destSock;
try {
if (connHostName.toLowerCase(Locale.US).endsWith(".i2p")) {
// Let's not do a new Dest for every request, huh?
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
Destination dest = _context.namingService().lookup(connHostName);
if (dest == null) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Host not found");
}
if (_log.shouldDebug())
_log.debug("connecting to " + connHostName + "...");
Properties overrides = new Properties();
I2PSocketOptions sktOpts = t.buildOptions(overrides);
sktOpts.setPort(connPort);
destSock = t.createI2PSocket(dest, sktOpts);
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
String err = "No localhost accesses allowed through the Socks Proxy";
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
/****
} else if (connPort == 80) {
// rewrite GET line to include hostname??? or add Host: line???
// or forward to local eepProxy (but that's a Socket not an I2PSocket)
// use eepProxy configured outproxies?
String err = "No handler for HTTP outproxy implemented";
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
****/
} else {
Outproxy outproxy = getOutproxyPlugin();
if (outproxy != null) {
// In HTTPClient, we use OutproxyRunner to run a Socket,
// but here, we wrap a Socket in a I2PSocket and use the regular Runner.
try {
destSock = new SocketWrapper(outproxy.connect(connHostName, connPort));
} catch (IOException ioe) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe2) {}
throw new SOCKSException("connect failed via outproxy plugin", ioe);
}
} else {
List<String> proxies = t.getProxies(connPort);
if (proxies == null || proxies.isEmpty()) {
String err = "No outproxy configured for port " + connPort + " and no default configured either";
_log.error(err);
try {
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException(err);
}
int p = _context.random().nextInt(proxies.size());
String proxy = proxies.get(p);
if (_log.shouldLog(Log.DEBUG))
_log.debug("connecting to proxy " + proxy + " for " + connHostName + " port " + connPort);
try {
destSock = outproxyConnect(t, proxy);
} catch (SOCKSException se) {
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw se;
}
}
}
confirmConnection();
_log.debug("connection confirmed - exchanging data...");
} catch (DataFormatException e) {
if (_log.shouldLog(Log.WARN))
_log.warn("socks error", e);
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error in destination format");
} catch (SocketException e) {
if (_log.shouldLog(Log.WARN))
_log.warn("socks error", e);
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error connecting: " + e);
} catch (IOException e) {
if (_log.shouldLog(Log.WARN))
_log.warn("socks error", e);
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error connecting: " + e);
} catch (I2PException e) {
if (_log.shouldLog(Log.WARN))
_log.warn("socks error", e);
try {
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
} catch (IOException ioe) {}
throw new SOCKSException("Error connecting: " + e);
}
return destSock;
}
/**
* Act as a SOCKS 5 client to connect to an outproxy
* @return open socket or throws error
* @since 0.8.2
*/
private I2PSocket outproxyConnect(I2PSOCKSTunnel tun, String proxy) throws IOException, SOCKSException, DataFormatException, I2PException {
Properties overrides = new Properties();
overrides.setProperty("option.i2p.streaming.connectDelay", "1000");
I2PSocketOptions proxyOpts = tun.buildOptions(overrides);
Destination dest = _context.namingService().lookup(proxy);
if (dest == null)
throw new SOCKSException("Outproxy not found");
I2PSocket destSock = tun.createI2PSocket(dest, proxyOpts);
DataOutputStream out = null;
DataInputStream in = null;
try {
out = new DataOutputStream(destSock.getOutputStream());
boolean authAvail = Boolean.parseBoolean(props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH));
String configUser = null;
String configPW = null;
if (authAvail) {
configUser = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER_PREFIX + proxy);
configPW = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW_PREFIX + proxy);
if (configUser == null || configPW == null) {
configUser = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER);
configPW = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW);
if (configUser == null || configPW == null)
authAvail = false;
}
}
// send the init
out.writeByte(SOCKS_VERSION_5);
if (authAvail) {
out.writeByte(2);
out.writeByte(Method.NO_AUTH_REQUIRED);
out.writeByte(Method.USERNAME_PASSWORD);
} else {
out.writeByte(1);
out.writeByte(Method.NO_AUTH_REQUIRED);
}
out.flush();
// read init reply
in = new DataInputStream(destSock.getInputStream());
// is this right or should we not try to do 5-to-4 conversion?
int hisVersion = in.readByte();
if (hisVersion != SOCKS_VERSION_5 /* && addrtype == AddressType.DOMAINNAME */ )
throw new SOCKSException("SOCKS Outproxy is not Version 5");
//else if (hisVersion != 4)
// throw new SOCKSException("Unsupported SOCKS Outproxy Version");
int method = in.readByte();
if (method == Method.NO_AUTH_REQUIRED) {
// good
} else if (method == Method.USERNAME_PASSWORD) {
if (authAvail) {
// send the auth
out.writeByte(AUTH_VERSION);
byte[] user = configUser.getBytes("UTF-8");
byte[] pw = configPW.getBytes("UTF-8");
out.writeByte(user.length);
out.write(user);
out.writeByte(pw.length);
out.write(pw);
out.flush();
// read the auth reply
if (in.readByte() != AUTH_VERSION)
throw new SOCKSException("Bad auth version from outproxy");
if (in.readByte() != AUTH_SUCCESS)
throw new SOCKSException("Outproxy authorization failure");
} else {
throw new SOCKSException("Outproxy requires authorization, please configure username/password");
}
} else {
throw new SOCKSException("Outproxy authorization failure");
}
// send the connect command
out.writeByte(SOCKS_VERSION_5);
out.writeByte(Command.CONNECT);
out.writeByte(0); // reserved
out.writeByte(addressType);
if (addressType == AddressType.IPV4) {
out.write(InetAddress.getByName(connHostName).getAddress());
} else if (addressType == AddressType.DOMAINNAME) {
byte[] d = connHostName.getBytes("ISO-8859-1");
out.writeByte(d.length);
out.write(d);
} else {
// shouldn't happen
throw new SOCKSException("Unknown address type for outproxy?");
}
out.writeShort(connPort);
out.flush();
// read the connect reply
hisVersion = in.readByte();
if (hisVersion != SOCKS_VERSION_5)
throw new SOCKSException("Outproxy response is not Version 5");
int reply = in.readByte();
in.readByte(); // reserved
int type = in.readByte();
int count = 0;
if (type == AddressType.IPV4) {
count = 4;
} else if (type == AddressType.DOMAINNAME) {
count = in.readUnsignedByte();
} else if (type == AddressType.IPV6) {
count = 16;
} else {
throw new SOCKSException("Unsupported address type in outproxy response");
}
byte[] addr = new byte[count];
in.readFully(addr); // address
in.readUnsignedShort(); // port
if (reply != Reply.SUCCEEDED)
throw new SOCKSException("Outproxy rejected request, response = " + reply);
// throw away the address in the response
// todo pass the response through?
} catch (IOException e) {
try { destSock.close(); } catch (IOException ioe) {}
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (out != null) try { out.close(); } catch (IOException ioe) {}
throw e;
} catch (SOCKSException e) {
try { destSock.close(); } catch (IOException ioe) {}
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (out != null) try { out.close(); } catch (IOException ioe) {}
throw e;
}
// that's it, caller will send confirmation to our client
return destSock;
}
// This isn't really the right place for this, we can't stop the tunnel once it starts.
private static SOCKSUDPTunnel _tunnel;
private static final Object _startLock = new Object();
private static final byte[] dummyIP = new byte[4];
/**
* We got a UDP associate command.
* Loop here looking for more, never return normally,
* or else I2PSocksTunnel will create a streaming lib connection.
*
* Do UDP Socks clients actually send more than one Associate request?
* RFC 1928 isn't clear... maybe not.
*/
private void handleUDP(DataInputStream in, DataOutputStream out) throws SOCKSException {
List<Integer> ports = new ArrayList<Integer>(1);
synchronized (_startLock) {
if (_tunnel == null) {
// tunnel options?
_tunnel = new SOCKSUDPTunnel(new I2PTunnel());
_tunnel.startRunning();
}
}
while (true) {
// Set it up. connHostName and connPort are the client's info.
InetAddress ia = null;
try {
ia = InetAddress.getByAddress(connHostName, dummyIP);
} catch (UnknownHostException uhe) {} // won't happen, no resolving done here
int myPort = _tunnel.add(ia, connPort);
ports.add(Integer.valueOf(myPort));
try {
sendRequestReply(Reply.SUCCEEDED, AddressType.IPV4, InetAddress.getByName("127.0.0.1"), null, myPort, out);
} catch (IOException ioe) { break; }
// wait for more ???
try {
int command = manageRequest(in, out);
// don't do this...
if (command != Command.UDP_ASSOCIATE)
break;
} catch (IOException ioe) { break; }
catch (SOCKSException ioe) { break; }
}
for (Integer i : ports)
_tunnel.remove(i);
// Prevent I2PSocksTunnel from calling getDestinationI2PSocket() above
// to create a streaming lib connection...
// This isn't very elegant...
//
throw new SOCKSException("End of UDP Processing");
}
/*
* Some namespaces to enclose SOCKS protocol codes
*/
private static class Method {
private static final int NO_AUTH_REQUIRED = 0x00;
private static final int USERNAME_PASSWORD = 0x02;
private static final int NO_ACCEPTABLE_METHODS = 0xff;
}
private static class AddressType {
private static final int IPV4 = 0x01;
private static final int DOMAINNAME = 0x03;
private static final int IPV6 = 0x04;
}
private static class Command {
private static final int CONNECT = 0x01;
private static final int BIND = 0x02;
private static final int UDP_ASSOCIATE = 0x03;
}
private static class Reply {
private static final int SUCCEEDED = 0x00;
private static final int GENERAL_SOCKS_SERVER_FAILURE = 0x01;
private static final int CONNECTION_NOT_ALLOWED_BY_RULESET = 0x02;
private static final int NETWORK_UNREACHABLE = 0x03;
private static final int HOST_UNREACHABLE = 0x04;
private static final int CONNECTION_REFUSED = 0x05;
private static final int TTL_EXPIRED = 0x06;
private static final int COMMAND_NOT_SUPPORTED = 0x07;
private static final int ADDRESS_TYPE_NOT_SUPPORTED = 0x08;
}
private static final int AUTH_VERSION = 1;
private static final int AUTH_SUCCESS = 0;
private static final int AUTH_FAILURE = 1;
}