ExceptionFactory

Producing content that a reasonable developer might want to read

Introducing Socket Broker

Socket Proxy SOCKS HTTP

2022-01-24 • 10 minute read • David Handermann

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:

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.