521 Server Is Down
Origin server refuse the connection
522 Connection Timed Out
Could not negotiate a TCP handshake with the origin server.
523 Origin Is Unreachable
Could not reach the origin server; for example, if the DNS records for the
origin server are incorrect, or No route to host
git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5441 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
331 lines
10 KiB
PHP
331 lines
10 KiB
PHP
<?php
|
|
|
|
/** TCP Client
|
|
* Allow to create TCP connections to a server.
|
|
* If both IPv6 and IPv4 are allowed by the server, try in IPv6 then back in
|
|
* IPv4 if it doesn't works.
|
|
* If the name of the server is provided instead of the IP, look for all the
|
|
* IP address, manage the CNAME aliases.
|
|
* If multiple addresses are available in IPv6 or IPv4, randomize them to allow
|
|
* round-robin connections to the server.
|
|
* Manage the timeout, send a command, receive a max number of bytes,
|
|
* allow SSL trafic with CA verification
|
|
*/
|
|
class tcpclient
|
|
{
|
|
// PROPERTIES //
|
|
// {{{
|
|
/** The IPv6 allowed for the server
|
|
*/
|
|
private $ipv6 = array ();
|
|
|
|
/** The IPv4 allowed for the server
|
|
*/
|
|
private $ipv4 = array ();
|
|
|
|
/** The ipOrName parameter
|
|
*/
|
|
private $ipOrName = null;
|
|
|
|
/** The port to connect to the server
|
|
*/
|
|
private $port = null;
|
|
|
|
/** Prefer the IPv4 connection either the IPv6 is valid
|
|
*/
|
|
private $preferIPv4 = false;
|
|
|
|
/** The internal socket connected to the server
|
|
*/
|
|
private $socket = null;
|
|
|
|
/** Read in Text mode or in Binary mode
|
|
*/
|
|
private $readMode = "text";
|
|
|
|
/** The timeout before aborting the connection
|
|
* 30s by default
|
|
*/
|
|
private $timeout = 30;
|
|
// }}}
|
|
|
|
/** Initialize the object, by setting the name or the IP of the server
|
|
* @param string $ipOrName The IP or the name of the server
|
|
* @param integer $port The port of the server to connect
|
|
*/
|
|
public function __construct ($ipOrName, $port)
|
|
// {{{
|
|
{
|
|
$providedIpOrName = $ipOrName;
|
|
if (filter_var ($ipOrName, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
|
$this->ipv4 = array ($ipOrName);
|
|
elseif (filter_var ($ipOrName, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
|
$this->ipv6 = array ($ipOrName);
|
|
else
|
|
{
|
|
$i = 0;
|
|
while (empty ($this->ipv4) && empty ($this->ipv6) && $i < 10)
|
|
{
|
|
$nsRecords = @dns_get_record ($ipOrName, DNS_A + DNS_AAAA);
|
|
if ($nsRecords === false || $nsRecords == array ())
|
|
{
|
|
// There is some problems with CNAME if they are not defined.
|
|
// So enter in this case only if there is no other solution
|
|
$nsRecords = @dns_get_record ($ipOrName, DNS_CNAME);
|
|
if ($nsRecords === false || $nsRecords == array ())
|
|
throw new \Exception ("Can not find the IP for $ipOrName : ".
|
|
"DNS Error (No A, AAAA, CNAME entries)", 523);
|
|
}
|
|
foreach ($nsRecords as $val)
|
|
{
|
|
if ($val["type"] === "CNAME")
|
|
$ipOrName = $val["target"];
|
|
elseif ($val["type"] === "A")
|
|
$this->ipv4[] = $val["ip"];
|
|
elseif ($val["type"] === "AAAA")
|
|
$this->ipv6[] = $val["ipv6"];
|
|
}
|
|
$i++;
|
|
}
|
|
if ($i >= 10)
|
|
throw new \Exception ("Can not find the IP for $ipOrName : ".
|
|
"CNAME loop", 404);
|
|
if (empty ($this->ipv4) && empty ($this->ipv6))
|
|
throw new \Exception ("Can not find the IP for $ipOrName : ".
|
|
"No A or AAAA record", 404);
|
|
}
|
|
$port = intval ($port);
|
|
if ($port < 0 || $port > 65535)
|
|
throw new \Exception ("Invalid port provided to connection to server",
|
|
500);
|
|
$this->ipOrName = $providedIpOrName;
|
|
$this->port = $port;
|
|
shuffle ($this->ipv6);
|
|
shuffle ($this->ipv4);
|
|
}
|
|
// }}}
|
|
|
|
/** Set/get the preferIPv4 property
|
|
* @param boolean|null $preferIPv4 The preferIPv4 property to set (or to get
|
|
* if null)
|
|
*/
|
|
public function preferIPv4 ($preferIPv4 = null)
|
|
// {{{
|
|
{
|
|
if ($preferIPv4 === null)
|
|
return $this->preferIPv4;
|
|
if ($this->socket !== null)
|
|
throw new \Exception ("Can not connect in IPv4 prefered to server ".
|
|
" $this->ipOrName : The server is already connected", 500);
|
|
$this->preferIPv4 = !!$preferIPv4;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Set/get the read mode : text or binary
|
|
* @param string|null $readMode The mode to set (or get if null)
|
|
*/
|
|
public function readMode ($readMode = null)
|
|
// {{{
|
|
{
|
|
if ($readMode === null)
|
|
return $this->readMode;
|
|
if ($readMode !== "text" && $readMode !== "binary")
|
|
throw new \Exception ("Invalid readMode provided (nor text nor binary)",
|
|
500);
|
|
$this->readMode = $readMode;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Set/get the timeout
|
|
* @param integer|null $timeout The timeout in seconds
|
|
*/
|
|
public function timeout ($timeout = null)
|
|
// {{{
|
|
{
|
|
if ($timeout === null)
|
|
return $this->timeout;
|
|
$this->timeout = intval ($timeout);
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Initialize the connection to the server
|
|
* Return the socket
|
|
*/
|
|
public function connect ()
|
|
// {{{
|
|
{
|
|
if ($this->preferIPv4)
|
|
$ips = array_merge ($this->ipv4, $this->ipv6);
|
|
else
|
|
$ips = array_merge ($this->ipv6, $this->ipv4);
|
|
foreach ($ips as $ip)
|
|
{
|
|
if (filter_var ($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
|
$ip = "[$ip]";
|
|
$socket = @stream_socket_client ("tcp://$ip:$this->port", $errno, $errstr,
|
|
$this->timeout);
|
|
if ($socket === false)
|
|
continue;
|
|
$this->socket = $socket;
|
|
return $this->socket;
|
|
}
|
|
if ($errno === 110)
|
|
throw new \Exception ("Can not connect to server $this->ipOrName : ".
|
|
"Connection timed out", 522);
|
|
if ($errno === 111)
|
|
throw new \Exception ("Can not connect to server $this->ipOrName : ".
|
|
"Connection refused", 521);
|
|
if ($errno === 113)
|
|
throw new \Exception ("Can not connect to server $this->ipOrName : ".
|
|
"No route to host", 523);
|
|
throw new \Exception ("Can not connect to server $this->ipOrName : ".
|
|
$errstr, 500);
|
|
}
|
|
// }}}
|
|
|
|
/** Activate the SSL connection.
|
|
* Put the socket in blocking mode, as it is mandatory to have SSL connection
|
|
* @param boolean $val True to activate, false to disable SSL
|
|
* @param integer|null $cryptoMethod The cryptoMethod allowed
|
|
* @param array|null $options Can overload the SSL options if needed
|
|
* @return false if the client can not found a encryption method with the
|
|
* server
|
|
*/
|
|
public function cryptoEnable ($val, $cryptoMethod = null, $options = array ())
|
|
// {{{
|
|
{
|
|
if ($this->socket === null)
|
|
throw new \Exception ("Can not send to server $this->ipOrName : ".
|
|
"The server is not connected", 500);
|
|
if ($cryptoMethod === null)
|
|
$cryptoMethod = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT|
|
|
STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT|
|
|
STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
|
|
$optionsBase = array ("ssl" => array (
|
|
"peer_name" => $this->ipOrName,
|
|
"verify_peer" => true,
|
|
"verify_peer_name" => true,
|
|
"capture_peer_cert" => true,
|
|
"capture_peer_cert_chain" => true,
|
|
"SNI_enabled" => true,
|
|
));
|
|
$optionsMerged["ssl"] = array_merge ($optionsBase["ssl"], $options);
|
|
stream_set_blocking ($this->socket, true);
|
|
stream_context_set_option ($this->socket, $optionsMerged);
|
|
ini_set ("track_errors", 1);
|
|
$rc = @stream_socket_enable_crypto ($this->socket, !!$val, $cryptoMethod);
|
|
ini_set ("track_errors", 0);
|
|
if ($rc === false)
|
|
throw new \Exception ("Can not enable crypto to '$this->ipOrName' : ".
|
|
substr (strrchr ($php_errormsg, ":"), 1), 500);
|
|
return $rc;
|
|
}
|
|
// }}}
|
|
|
|
/** Send a data to the server.
|
|
* The connection must be established
|
|
* @param mixed $data The data to send
|
|
*/
|
|
public function send ($data)
|
|
// {{{
|
|
{
|
|
if ($this->socket === null)
|
|
throw new \Exception ("Can not send to server $this->ipOrName : ".
|
|
"The server is not connected", 500);
|
|
stream_set_timeout ($this->socket, $this->timeout);
|
|
$length = strlen ($data);
|
|
$sentLen = @fwrite ($this->socket, $data);
|
|
if ($sentLen < $length)
|
|
throw new \Exception ("Can not send to server $this->ipOrName", 500);
|
|
return $sentLen;
|
|
}
|
|
// }}}
|
|
|
|
/** Read the data from the server.
|
|
* The connection must be established
|
|
* Use the readMode in text or binary (text by default)
|
|
* In text mode, the read return when found the first \r\n, and doesn't
|
|
* returns the \r\n.
|
|
* @param integer $maxLength Limit the length of the data from the server
|
|
* @return The content
|
|
*/
|
|
public function read ($maxLength = 1024)
|
|
// {{{
|
|
{
|
|
if ($this->socket === null)
|
|
throw new \Exception ("Can not read from server $this->ipOrName : ".
|
|
"The server is not connected", 500);
|
|
stream_set_timeout ($this->socket, $this->timeout);
|
|
if ($this->readMode === "text")
|
|
{
|
|
$read = stream_get_line ($this->socket, $maxLength, "\r\n");
|
|
if ($read === false)
|
|
throw new \Exception ("Can not read from server in text", 500);
|
|
}
|
|
else
|
|
{
|
|
$read = fread ($this->socket, $maxLength);
|
|
if ($read === false)
|
|
throw new \Exception ("Can not read from server in binary" , 500);
|
|
}
|
|
return $read;
|
|
}
|
|
// }}}
|
|
|
|
/** Disconnect the socket
|
|
*/
|
|
public function disconnect ()
|
|
// {{{
|
|
{
|
|
if ($this->socket === null)
|
|
throw new \Exception ("Can not disconnect server $this->ipOrName : ".
|
|
"The server is not connected", 500);
|
|
@stream_socket_shutdown ($this->socket, STREAM_SHUT_RDWR);
|
|
$this->socket = null;
|
|
}
|
|
// }}}
|
|
|
|
/** Get meta data, like the timeout state, the crypto protocol and ciphers...
|
|
*/
|
|
public function getMeta ()
|
|
// {{{
|
|
{
|
|
return stream_get_meta_data ($this->socket);
|
|
}
|
|
// }}}
|
|
|
|
/** Get the connection peer address, peer port and localaddress and localport
|
|
* @return array
|
|
*/
|
|
public function getInfo ()
|
|
// {{{
|
|
{
|
|
if ($this->socket === null)
|
|
throw new \Exception ("Can not getInfo for server $this->ipOrName : ".
|
|
"The server is not connected", 500);
|
|
$name = stream_socket_get_name ($this->socket, false);
|
|
$pos = strrpos ($name, ":");
|
|
$localPort = substr ($name, $pos+1);
|
|
$localAddress = substr ($name, 0, $pos);
|
|
$name = stream_socket_get_name ($this->socket, true);
|
|
$pos = strrpos ($name, ":");
|
|
$port = substr ($name, $pos+1);
|
|
$address = substr ($name, 0, $pos);
|
|
return array ($address, $port, $localAddress, $localPort);
|
|
}
|
|
// }}}
|
|
|
|
/** Get the socket to direct access
|
|
* @return resource The socket with the client
|
|
*/
|
|
public function getSock ()
|
|
// {{{
|
|
{
|
|
return $this->socket;
|
|
}
|
|
// }}}
|
|
}
|