simple-socks

repository·main·Indexed 43 days ago

https://github.com/brozeph/simple-socks

A SOCKS5 server implementation for Node.js (v3.6.0) that provides an easy-to-use API for proxying requests. It supports RFC 1929 username/password authentication, RFC 1961 GSSAPI/Negotiate authentication, and connection filtering. The server exposes detailed events to track proxy connections, data flow, and errors via the `socks5.createServer()` method.

Tokens
6.4K
Snippets
14
Records
18
Agent score
5%

What's inside simple-socks

  1. Test a SOCKS5 server with curl

    main

    Once your server is running, you can verify it using curl.

    No Authentication:

    curl http://www.google.com --socks5 127.0.0.1:1080

    Username/Password Authentication:

    curl http://www.google.com --socks5 127.0.0.1:1080 --proxy-user foo:bar
  2. Configure GSSAPI/Negotiate (Kerberos) authentication

    main

    To support GSSAPI/Negotiate authentication, you must install the kerberos native dependency and ensure a service principal (e.g., rcmd/<hostname>@REALM) and a keytab are available to the process.

    Server Example: Run node examples/createServerWithGssapi to start a server using a Kerberos provider.

    Client Test:

    curl --socks5 127.0.0.1:1080 \
         --socks5-gssapi \
         --socks5-gssapi-service rcmd \
         https://example.com
  3. Bind to multiple network interfaces

    main

    If you need to run multiple SOCKS5 servers on the same port but bound to different IP addresses, create a separate server instance for each interface.

    When doing this, use the object form of .listen() and set exclusive: true. This prevents the servers from conflicting and ensures each server binds strictly to its assigned host.

    Note: If you call .listen(port) without a host, Node.js binds to 0.0.0.0, which accepts connections on all interfaces.

    import socks5 from "simple-socks";
    
    const hosts = ["10.0.0.1", "10.0.0.2", "10.0.0.3"];
    const port = 1080;
    
    hosts.forEach((host) => {
      const server = socks5.createServer();
    
      server.on("listening", () => {
        const addr = server.address();
        console.log("listening on %s:%d", addr.address, addr.port);
      });
    
      // Force an interface-specific, exclusive bind
      server.listen({ port, host, exclusive: true });
    });
  4. Troubleshoot macOS SOCKS5 authentication issues

    main

    The built-in macOS SOCKS client may fail to authenticate using standard RFC 1929 username/password methods because it might not advertise BASIC (0x02) support or may send zero-length passwords.

    Key constraints:

    • The server only uses BASIC if the client explicitly advertises it. If the client doesn't, the server will close the connection.
    • compatAuth.allowEmptyPassword and compatAuth.allowEmptyUsername can help with zero-length credentials, but only after BASIC has been selected.
    • compatAuth cannot fix clients that fail to advertise BASIC during the initial negotiation.

    Solutions:

    • Use a client that fully supports RFC 1929 (e.g., curl --socks5 --proxy-user, or modern browsers/extensions).
    • Use GSSAPI/Negotiate (though this library does not currently implement it).
  5. Create and use a basic SOCKS5 server

    main

    You can create a SOCKS5 server using socks5.createServer() and bind it to a port using .listen(port). The server emits several events to track proxy connections, data flow, and errors.

    import socks5 from "simple-socks";
    
    const server = socks5.createServer().listen(1080);
    
    // When a request arrives for a remote destination
    server.on("proxyConnect", (info, destination) => {
      console.log("connected to remote server at %s:%d", info.address, info.port);
    
      destination.on("data", (data) => {
        console.log(data.length);
      });
    });
    
    // When data arrives from the remote connection
    server.on("proxyData", (data) => {
      console.log(data.length);
    });
    
    // When an error occurs connecting to remote destination
    server.on("proxyError", (err) => {
      console.error("unable to connect to remote server");
      console.error(err);
    });
    
    // When a request for a remote destination ends
    server.on("proxyDisconnect", (originInfo, destinationInfo, hadError) => {
      console.log(
        "client %s:%d request has disconnected from remote server at %s:%d with %serror",
        originInfo.address,
        originInfo.port,
        destinationInfo.address,
        destinationInfo.port,
        hadError ? "" : "no ",
      );
    });
    
    // When a proxy connection ends
    server.on("proxyEnd", (response, args) => {
      console.log("socket closed with code %d", response);
      console.log(args);
    });
  6. Configure GSSAPI/Negotiate (RFC 1961)

    main

    You can enable GSSAPI authentication by providing a gssapi configuration object in createServer options. The server will only select GSSAPI if enabled: true is set and the client advertises method 0x01 during negotiation.

    Configuration Object:

    • enabled: Boolean to activate GSSAPI support.
    • provider: An object implementing the authenticate method.

    Provider Signature: authenticate(socket, firstChunk, callback)

    • socket: The client connection socket.
    • firstChunk: The first chunk of data from the client.
    • callback: Call with (err, principal) on success, where principal is the authenticated principal string. Call with an error on failure.

    Limitations:

    • This only performs authentication; it does not enable integrity or confidentiality wrapping for subsequent SOCKS traffic.
    import socks5 from "simple-socks";
    
    const server = socks5.createServer({
      gssapi: {
        enabled: true,
        provider: {
          authenticate(socket, firstChunk, callback) {
            // Implement RFC 1961 token exchange here.
            // Use system GSS libraries via your integration and call callback(err, principal)
            // on success with the authenticated principal string.
            callback(new Error("GSSAPI provider not implemented"));
          },
        },
      },
    });
    
    server.listen(1080);
  7. Manage connection filtering and proxy connectivity

    main

    Monitor and control the flow of proxy traffic using connection filters and proxy lifecycle events.

    • connectionFilter: Emitted when a destination is denied by your options.connectionFilter callback. Callback provides destination (address/port), origin (address/port), and the err returned by the filter.
    • proxyConnect: Emitted when a successful connection to a remote destination is established. Callback provides info (address/port) and the destination net.Socket.
    • proxyError: Emitted if a network error occurs while attempting to connect to the remote destination.
    • proxyDisconnect: Emitted when a successful connection to a remote destination ends. Callback provides originInfo, destinationInfo, and hadError (boolean).
    // Handle denied connections
    server.on("connectionFilter", function (destination, origin, err) {
      console.log("connection to %s:%s has been denied", destination.address, destination.port);
      console.error(err);
    });
    
    // Handle successful remote connections
    server.on("proxyConnect", function (info, destination) {
      console.log("connected to remote server at %s:%d", info.address, info.port);
    });
    
    // Handle connection errors to remote destinations
    server.on("proxyError", function (err) {
      console.error("unable to connect to remote server", err);
    });
    
    // Handle remote connection termination
    server.on("proxyDisconnect", function (originInfo, destinationInfo, hadError) {
      console.log("client %s:%d disconnected from %s:%d (error: %s)", 
        originInfo.address, originInfo.port, 
        destinationInfo.address, destinationInfo.port, 
        hadError ? "yes" : "no"
      );
    });
  8. Implement username/password authentication

    main

    To require credentials, provide an authenticate callback in the createServer options. The callback is triggered during the SOCKS5 handshake.

    Callback Signature: authenticate(username, password, socket, callback)

    • username: The username provided by the client.
    • password: The password provided by the client.
    • socket: The client connection socket.
    • callback: Call with no arguments on success, or an Error on failure.

    Note on compatAuth: By default, the server is strict about RFC 1929. If you need to allow empty usernames or passwords (e.g., for older clients), use the compatAuth option:

    • allowEmptyUsername (default false)
    • allowEmptyPassword (default false)
    • strictMethodNegotiation (default true - do not change this)
    import socks5 from "simple-socks";
    
    const options = {
      authenticate: function (username, password, socket, callback) {
        if (username === "foo" && password === "bar") {
          return setImmediate(callback);
        }
    
        return setImmediate(callback, new Error("incorrect username and password"));
      },
    };
    
    const server = socks5.createServer(options);
    server.listen(1080);
  9. Customize outbound connections with connectionOptions

    main

    The connectionOptions callback allows you to modify the parameters passed to Node's net.createConnection. This is useful for rewriting destinations, choosing local bind addresses, or routing traffic through a local TCP adapter (like an SSH tunnel).

    Callback Signature: connectionOptions(destination, origin, defaults, callback)

    • destination: Object with address and port of the requested remote server.
    • origin: Object with address and port of the client.
    • defaults: The standard options object used by the server (contains host, port, localAddress, localPort).
    • callback: Call with (err, options) where options is the final object passed to net.createConnection.

    Important: This must return valid TCP connection options; it cannot be used to return an arbitrary stream directly. To use a stream (like an SSH channel), you must bridge it via a local TCP listener.

    import socks5 from "simple-socks";
    
    const server = socks5.createServer({
      connectionOptions(destination, origin, defaults, callback) {
        return callback(null, {
          ...defaults,
          host: "127.0.0.1",
          port: destination.port === 443 ? 8443 : destination.port,
        });
      },
    });
  10. Handle SOCKS5 client handshakes and authentication

    main

    Use the handshake and authentication events to monitor new client connections and user logins.

    • handshake: Emitted when a new SOCKS5 client begins negotiation. Callback provides the originating net.Socket.
    • authenticate: Emitted when username/password authentication succeeds. Callback provides the username.
    • authenticateError: Emitted when authentication fails. Callback provides the username and the err returned by the options.authenticate callback.
    // Handle new client connections
    server.on("handshake", function (socket) {
      console.log("new socks5 client from %s:%d", socket.remoteAddress, socket.remotePort);
    });
    
    // Handle successful authentication
    server.on("authenticate", function (username) {
      console.log("user %s successfully authenticated!", username);
    });
    
    // Handle authentication failures
    server.on("authenticateError", function (username, err) {
      console.log("user %s failed to authenticate...", username);
      console.error(err);
    });
  11. Create a SOCKS5 proxy server with createServer()

    main

    Use socks5.createServer(options) to instantiate a new SOCKS5 proxy server. You can then call .listen() to start accepting connections.

    Available Options

    OptionDescription
    authenticationA callback for user/password authentication.
    connectionFilterA callback to allow or deny connections based on origin or destination.
    connectionOptionsA callback to customize outbound TCP connection options (e.g., rewriting host/port).
    connectTimeoutMilliseconds to wait for the destination connect phase before failing (default: 0, which is disabled).
    idleTimeoutMilliseconds of inactivity before destroying client/destination sockets (default: 0, which is disabled).
    destinationIdleTimeoutMilliseconds of inactivity before destroying destination sockets; falls back to idleTimeout if not set.
    compatAuthConfiguration for RFC 1929 compatibility (handling empty credentials).
    gssapiConfiguration for GSSAPI/Negotiate (RFC 1961) authentication.
    import socks5 from "simple-socks";
    
    const server = socks5.createServer();
    
    server.listen(1080, "0.0.0.0", function () {
      console.log("SOCKS5 proxy server started on 0.0.0.0:1080");
    });