Introducing Socket Broker
Introduction
Network proxy servers provide mediated access to applications and services using standard communication protocols. Reverse proxy servers enable administrators to configure security measures such as filtering or authentication before requests reach protected network services. Forward proxy servers enforce network policies, such as logging outgoing connections, caching common responses, or limiting the scope of allowed communication ports. Regardless of the particular reason for deployment, accessing network resources through a proxy server is a common task in many environments.
Initial versions of Java provided limited support for proxy access using system properties, but Java 5 introduced
improved support for configuring proxy server access through
the java.net.Proxy class.
The java.net.Proxy.Type enumeration
supports SOCKS
or HTTP
options, which can be provided to a Proxy
instance together with the socket address of a
proxy server. This approach supports the construction of
a java.net.Socket using a
specified proxy server and protocol. For proxy servers that require authentication,
the java.net.Authenticator class supports
handling requests for proxy credentials through registering a custom implementation using the
static setDefault()
method. The static registration of an Authenticator
is sufficient for applications that can share a global
implementation, but for applications that require fine-grained control of credentials without impacting the entire Java
Virtual Machine, a different approach is necessary.
Feature Summary
Socket Broker provides implementations of java.net.Socket and javax.net.SocketFactory that serve as direct replacements for standard Java components. Rather than requiring registration of a global Authenticator, the BrokeredSocketFactory accepts a configuration with optional username and password credentials.
The library runs on Java 8 or higher and has no additional runtime dependencies.
Socket Broker supports elements of the following standards:
- RFC 1928: SOCKS5 Protocol
- RFC 1929: SOCKS5 Username and Password Authentication
- RFC 7231: HTTP/1.1 Protocol
- RFC 7235: HTTP/1.1 Authentication
- RFC 7617: HTTP/1.1 Basic Authentication
Implementation classes support TCP socket connections using SOCKS5, with optional username and password authentication.
HTTP support relies on the CONNECT method as described in
RFC 7231 Section 4.3.6. Socket Broker supports username
and password authentication over HTTP using the Proxy-Authorization
header with the Basic Authentication Scheme
defined in RFC 7617.
Implementation Overview
Socket Broker supports SOCKS5 and HTTP CONNECT proxy access through composed implementations of each protocol.
Generic PacketDecoder
and PacketEncoder
interfaces abstract translation between bytes and protocol objects, supporting both low-level testing and intuitive
high-level communication operations.
The SocketBroker
interface abstracts proxy communication handling, including initial connectivity as well as subsequent request and
response processing. Implementations of java.net.Socket
delegate protocol processing to concrete instances
of SocketBroker
, providing a clear separation between standard socket operations and proxy server interaction.
SOCKS5 Implementation
The SOCKS5 protocol defines a simple standard for proxy communication using binary messages. Requests and responses begin with a single byte indicating the protocol version. The initial request and response packets negotiate supported and accepted authentication methods, allowing the server to indicate whether the client must provide authentication credentials before making subsequent requests. Following a successful authentication negotiation, the client issues a command requesting a connection to a remote address.
SOCKS5 supports both Internet Protocol Version 4 and Version 6, as well as domain names, allowing connections to addresses that the client might not be able to resolve using direct DNS lookups. In response to a connect command, the SOCKS5 server responds using a reply packet, indicating either a successful connection, or a specific failure status. Following a successful reply, the client proceeds to communicate with the remote service using standard stream handling methods. Based on this design, the SOCKS5 protocol is capable of supporting a wide range of TCP socket protocols.
The Socks5SocketBroker
class contains the implementation methods for interacting with a SOCKS5 server. In absence of username and password
credentials, Socks5SocketBroker
sends an initial message to the SOCKS5 server requesting access with no authentication
required. This approach is different from
the java.net.SocksSocketImpl
, which always indicates support for username and password authentication. The standard SocksSocketImpl
defaults to
sending the username of running Java Virtual Machine, along with a zero byte flag indicating an empty password. The
approach implemented in Socks5SocketBroker
avoids this fallback behavior and instead throws a
BrokeredAuthenticationException
when the SOCKS5 server does not respond with a supported authentication method.
RFC 1929 does not define a standard character encoding for username and password
credentials, which creates the potential for encoding mismatch between client and server. The standard
Java SocksSocketImpl
uses ISO-8859-1 for character encoding, which is
limited to Latin characters and a small number of control characters. The Socks5SocketBroker
implementation uses
UTF-8, providing support for a much larger range of characters. UTF-8 represents the
first 128 characters using the same byte codes as ISO-8859-1, which provides compatibility with historical
implementations. SOCKS5 servers must support UTF-8 encoding in order to handle characters outside the ISO-8859-1 range,
but using UTF-8 provides potential support for other languages.
HTTP Implementation
The HTTP specification defines several standard request methods, including the CONNECT method for the purpose of
connection tunneling. HTTP CONNECT requests follow the same conventions as other methods, including support for multiple
request headers. Proxy servers supporting HTTP CONNECT requests return a standard HTTP 200 response status following a
successful connection, and return a failure status code to indicate an error condition.
An HTTP 407 response status indicates that the proxy server
requires authentication, and the Proxy-Authenticate
response header contains a challenge declaration indicating a
supported authentication scheme. HTTP CONNECT requests include the remote address and port number in the first line and
should also include the same information in the Host
request header. After receiving a successful response, the client
can proceed to communicate through the tunnel using standard methods.
The HttpConnectSocketBroker
class implements HTTP CONNECT request and response handling. Without credentials configured, HttpConnectSocketBroker
sends an HTTP CONNECT request with matching Host
header to the specified proxy server. When provided with username and
password credentials, the implementation class encodes the username and password
using Base64 as defined in RFC 7617 and
passes the credentials in the initial request using the Proxy-Authorization
header. The broker class reads the
response status line, including the protocol version, status code, and optional reason phrase, then checks the status
for success. An HTTP 401 status indicates unauthorized access, and results in a BrokeredAuthenticationException
containing the status code and associated reason. An HTTP 407 produces the same exception, but also includes the
authentication challenges provided in Proxy-Authenticate
response headers. All other response statuses result in
a ConnectException
that includes the response status code for troubleshooting.
Although RFC 7617 defined support for an optional character set parameter when encoding usernames and passwords, the
standard does not require a particular default encoding. Following the same strategy as the SOCKS5 implementation,
the HttpConnectSocketBroker
uses UTF-8 as the character encoding for credentials regardless of the server parameter.
Java 8 incorporated
an HttpConnectSocketImpl
class to support HTTP CONNECT proxy server tunneling, which delegates to the internal
HttpURLConnection
class for request processing and authentication handling. The internal Java
BasicAuthentication
class supports UTF-8 encoding when the server response header includes the character set parameter, and otherwise
defaults to ISO-8859-1. The Java HttpConnectSocketImpl
implementation supports other authentication schemes in
addition to Basic Authentication, but the initial version of Socket Broker is limited to the Basic Authentication
strategy.
Integration Examples
The Socket Broker BrokeredSocketFactory
class can be integrated with network clients that support configuring a
standard javax.net.SocketFactory
for TCP connections. Proxy server configuration is specific to each network
environment, so connection information should be supplied through configurable properties.
Daytime Protocol Connection
The Daytime Protocol defined in RFC 867 provides a simple standard for demonstrating TCP socket connections through a proxy server. The National Institute of Standards and Technology maintains a list of Internet Time Servers that support the Daytime Protocol, providing a reliable option for confirming connectivity.
The Apache Commons Net library supports a number of standard network protocols, and
provides the option to configure a SocketFactory
for client connections. The Apache Commons Net
DaytimeTCPClient
can be configured to connect and retrieve the current time from a specified server using a few lines of code. The base
SocketClient class
supports a configurable SocketFactory
, so the implementation approach for the Daytime Protocol can also be applied to
other socket protocols.
Direct Connection to Daytime Server
The following example creates a client, connects to a server, and prints the current time retrieved from the server:
import org.apache.commons.net.daytime.DaytimeTCPClient;
import java.io.IOException;
public class DaytimeClient {
private static final String DAYTIME_SERVER = "time.nist.gov";
public static void main(final String[] arguments) throws IOException {
final DaytimeTCPClient client = new DaytimeTCPClient();
client.connect(DAYTIME_SERVER);
final String time = client.getTime();
System.out.println(time);
client.disconnect();
}
}
SOCKS5 Proxy Connection
Connecting through a proxy server requires creating a SocketFactory
and configuring the client to use the factory
prior to initiating the connection.
Dante is a free and open source SOCKS5 proxy server that can be used for evaluating proxy access. Several Docker Containers provide options for running Dante with minimal configuration. Various service providers also offer access through proxy servers.
The following example demonstrates connecting to a Daytime Protocol server through a SOCKS5 proxy server running on the
localhost
address, using port 1080
without requiring authentication:
import com.exceptionfactory.socketbroker.BrokeredSocketFactory;
import com.exceptionfactory.socketbroker.configuration.BrokerConfiguration;
import com.exceptionfactory.socketbroker.configuration.StandardBrokerConfiguration;
import org.apache.commons.net.daytime.DaytimeTCPClient;
import javax.net.SocketFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import static com.exceptionfactory.socketbroker.configuration.ProxyType.SOCKS5;
public class ProxyDaytimeClient {
private static final String DAYTIME_SERVER = "time.nist.gov";
private static final String PROXY_HOST = "localhost";
private static final int PROXY_PORT = 1080;
public static void main(final String[] arguments) throws IOException {
final DaytimeTCPClient client = new DaytimeTCPClient();
final SocketFactory socketFactory = getSocketFactory();
client.setSocketFactory(socketFactory);
client.connect(DAYTIME_SERVER);
final String time = client.getTime();
System.out.println(time);
client.disconnect();
}
private static SocketFactory getSocketFactory() {
final InetSocketAddress address = new InetSocketAddress(PROXY_HOST, PROXY_PORT);
final BrokerConfiguration configuration = new StandardBrokerConfiguration(SOCKS5, address);
final SocketFactory defaultSocketFactory = SocketFactory.getDefault();
return new BrokeredSocketFactory(configuration, defaultSocketFactory);
}
}
As shown in the getSocketFactory
method, the BrokeredSocketFactory
requires an instance of both
BrokerConfiguration
and SocketFactory
objects. For most integrations, the SocketFactory.getDefault()
approach is sufficient. This
parameter provides the opportunity to configure standard socket options, such as timeouts, when connecting to a proxy
server.
SOCKS5 Proxy Connection with Authentication
Connecting to a proxy server with authentication requires providing an additional argument to the
StandardBrokerConfiguration
constructor. The
StandardUsernamePasswordAuthenticationCredentials
constructor supports a username argument as a standard java.lang.String
and a password argument as an array of
character elements.
The following example extends the SOCKS5 proxy connection class with configured credentials. This example has placeholder methods for returning username and password properties. Integrations should consider the most secure method available to retrieve proxy authentication credentials.
import com.exceptionfactory.socketbroker.BrokeredSocketFactory;
import com.exceptionfactory.socketbroker.configuration.AuthenticationCredentials;
import com.exceptionfactory.socketbroker.configuration.BrokerConfiguration;
import com.exceptionfactory.socketbroker.configuration.StandardBrokerConfiguration;
import com.exceptionfactory.socketbroker.configuration.StandardUsernamePasswordAuthenticationCredentials;
import org.apache.commons.net.daytime.DaytimeTCPClient;
import javax.net.SocketFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import static com.exceptionfactory.socketbroker.configuration.ProxyType.SOCKS5;
public class AuthenticatedProxyDaytimeClient {
private static final String DAYTIME_SERVER = "time.nist.gov";
private static final String PROXY_HOST = "localhost";
private static final int PROXY_PORT = 1080;
public static void main(final String[] arguments) throws IOException {
final DaytimeTCPClient client = new DaytimeTCPClient();
final SocketFactory socketFactory = getSocketFactory();
client.setSocketFactory(socketFactory);
client.connect(DAYTIME_SERVER);
final String time = client.getTime();
System.out.println(time);
client.disconnect();
}
private static SocketFactory getSocketFactory() {
final InetSocketAddress address = new InetSocketAddress(PROXY_HOST, PROXY_PORT);
final AuthenticationCredentials credentials = getCredentials();
final BrokerConfiguration configuration = new StandardBrokerConfiguration(SOCKS5, address, credentials);
final SocketFactory defaultSocketFactory = SocketFactory.getDefault();
return new BrokeredSocketFactory(configuration, defaultSocketFactory);
}
private static AuthenticationCredentials getCredentials() {
final String username = getUsername();
final char[] password = getPassword();
return new StandardUsernamePasswordAuthenticationCredentials(username, password);
}
private static String getUsername() {
return null;
}
private static char[] getPassword() {
return null;
}
}
Connecting through an HTTP proxy server follows the same pattern, replacing the ProxyType.SOCKS5
reference with
ProxyType.HTTP_CONNECT
in the StandardBrokerConfiguration
constructor.
Conclusion
TCP socket communication is a vital part of most modern systems, and the capability to connect through a proxy server is a common requirement. Although the standard Java runtime components support access through proxy servers, the ability to configure proxy credentials without impacting the entire Java Virtual Machine is the primary differentiator for the Socket Broker library.
With strict adherence to protocol standards, implementation classes incorporate exception types and messages that can be useful when troubleshooting connection issues. Built with a comprehensive suite of unit tests and designed to integrate with standard socket components, Socket Broker provides a rigorous and straightforward approach to proxy server connectivity.