Rob
Rob

Reputation: 8101

Check if SOCKS4 or 5 in PHP?

Basically, I have a list of proxies. I'm wanting to separate them into SOCKS4 and SOCKS5. I'd like to code up a small PHP script to do this for me. How would I go about detecting which type it is in PHP?

Upvotes: 3

Views: 5762

Answers (3)

hakre
hakre

Reputation: 198219

You need to write yourself some little code that tries to connect with any of your proxies and inspect the socks version. Connection protocol for the different versions and error codes are documented on the wikipedia page about SOCKS.

Taking that into account, the rest is more or less standard socket connection with PHP.

Example:

$proxies = array( '66.135.131.74:1681', '172.52.61.244:48943',
                  '75.101.237.217:1080', '76.68.128.165:39879',);

foreach ($proxies as $index => $proxy)
{
    $type = SOCKSVersion::getType($proxy);
    $typeName = SOCKSVersion::getTypeName($type);
    printf("Proxy #%d: %s\n", $index, $typeName);
}

Output:

Proxy #0: SOCKS4
Proxy #1: SOCKS4
Proxy #2: Unknown
Proxy #3: SOCKS4

This exemplary implementation does only check for SOCKS4 so, but it could be easily extended to test as well for SOCK4a and SOCKS5 by adding methods similar to isSocks4():

/**
 * SOCKS server identifiation class.
 */
class SOCKSVersion
{
    const TYPE_UNKNOWN = 0;
    const TYPE_SOCKS4 = 1;
    const TYPE_SOCKS4a = 2;
    const TYPE_SOCKS5 = 3;

    /**
     * @var string[]
     */
    private static $typeNames = array(
        self::TYPE_UNKNOWN => 'Unknown',
        self::TYPE_SOCKS4 => 'SOCKS4',
        self::TYPE_SOCKS4a => 'SOCKS4a',
        self::TYPE_SOCKS5 => 'SOCKS5',
    );

    /**
     * @var int
     */
    private $timeout = 30;

    /**
     * @var int
     */
    private $host, $port;

    /**
     * @var string[]
     */
    private $errors;

    /**
     * @var string[]
     */
    private $socks4Errors = array(
        91 => "Request rejected or failed",
        92 => "Request failed because client is not running identd (or not reachable from the server)",
        93 => "Request failed because client's identd could not confirm the user ID string in the request",
    );

    public function __construct($endpoint)
    {
        $this->setEndpoint($endpoint);
    }

    /**
     * @static
     * @param string $proxy
     * @return int any of the TYPE_* constants
     */
    public static function getType($proxy)
    {
        $socks = new self($proxy);
        return $socks->getSocksVersion();
    }

    /**
     * @static
     * @param int $type
     * @return string
     */
    public static function getTypeName($type)
    {
        $typeNames = self::$typeNames;
        if (isset($typeNames[$type])) {
            return $typeNames[$type];
        }
        return $typeNames[self::TYPE_UNKNOWN];
    }

    public function setEndpoint($endpoint)
    {
        if (!$parts = parse_url('http://' . $endpoint)) {
            throw new InvalidArgumentException(sprintf('Unable to parse endpoint "%s".', $endpoint));
        }
        if (empty($parts['host'])) {
            throw new InvalidArgumentException('No host given.');
        }
        if (empty($parts['port'])) {
            throw new InvalidArgumentException('No port given.');
        }
        $this->host = $parts['host'];
        $this->port = $parts['port'];
    }

    /**
     * @return int any of the TYPE_* constants
     */
    public function getSocksVersion()
    {
        try {
            if ($this->isSocks4()) {
                return self::TYPE_SOCKS4;
            }
        } catch (BadFunctionCallException $e) {
            $this->errors[] = sprintf("SOCKS4 Test: ", $this->host, $e->getMessage());
        }
        return self::TYPE_UNKNOWN;
    }

    public function isSocks4()
    {
        $socket = stream_socket_client("tcp://" . $this->host . ":" . $this->port, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT);
        if (!$socket) {
            throw new BadFunctionCallException(sprintf('Socket-Error #%d: %s', $errno, $errstr));
        }

        // SOCKS4; @link <http://en.wikipedia.org/wiki/SOCKS#Protocol>
        $userId = "";
        $packet = "\x04\x01" . pack("n", $this->port) . pack("H*", dechex(ip2long($this->host))) . $userId . "\0";
        fwrite($socket, $packet, strlen($packet));
        $response = fread($socket, 9);
        if (strlen($response) == 8 && (ord($response[0]) == 0 || ord($response[0]) == 4)) {
            $status = ord($response[1]);
            if ($status != 90) {
                throw new BadFunctionCallException(sprintf("Error from SOCKS4 server: %s.", $this->socks4Errors[$status]));
            }
        } else {
            throw new BadFunctionCallException("The SOCKS server returned an invalid response");
        }
        fclose($socket);

        return TRUE;
    }
}

Hope this is helpful. If you introduce multiple versions, you should improve the error handling and don't connect more than once to the same host if the connection failed in a previous test.

Upvotes: 4

stan
stan

Reputation: 4995

I think the best you can do is to first try to establish a CURL connection by trying the highest version - 5.

  curl_setopt($curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);

This will give you the answer either way. Check curl_error after you execute it. If there is no error, you are using SOCKS5, else you are using SOCKS4.

Upvotes: 0

Wouter Dorgelo
Wouter Dorgelo

Reputation: 11998

According to RFC1928, for establishing a SOCKS connection you start by sending these bytes to the server:

1 byte      SOCKS version
1 byte      Number of authentication methods (n)
n bytes     List of method identifiers

And server responds with

1 byte      SOCKS version
1 byte      Accepted method

This is common between both 4th and 5th versions of SOCKS. So you can start by one version (5, for example) and fall back to another version if server doesn't respond accordingly.

Upvotes: -1

Related Questions