openclonk/masterserver/web/server/include/C4HostTest.php

125 lines
5.1 KiB
PHP

<?php
// If not in Include mode: Perform test!
if(!$C4HostTestIncludeMode)
if(performHostTest($_GET['ip'],$_GET['tcp'],$_GET['udp'],$_GET['time']))
echo "Reached=Yes\n";
else
echo "Reached=No\n";
/**
* Parse configuration and call actual host testing.
* May open a http connection
*
* @param string $req Request Data
*/
function testHostConn(&$req) {
global $config;
$mode = ParseINI::parseValue('hosttest_mode', $config);
if($mode == 0)
return true; // Say it works if we didn't test.
$timeout = ParseINI::parseValue('hosttest_timeout', $config);
$addr = ParseINI::parseValue('Address', $req);
preg_match('/TCP:0.0.0.0:([0-9]+),|$/',$addr, $addr_tcp);
$tcpport = count($addr_tcp)==2 ? ((int) $addr_tcp[1]) : 0; // Be sure that this is injection safe: this machine might have elevated privilegues on the host test provider based on ip
preg_match('/UDP:0.0.0.0:([0-9]+),|$/',$addr, $addr_udp);
$udpport = count($addr_udp)==2 ? ((int) $addr_udp[1]) : 0;
if(!$udpport && !$tcpport)
return false; // That might be a false false, but it's weird anyway.
unset($addr, $addr_tcp, $addr_udp);
if($mode == 1) { // Perform the check from local host
return performHostTest($_SERVER['REMOTE_ADDR'],$tcpport,$udpport,$timeout);
} else { // Call hostchecker on remote service
$url = ParseINI::parseValue('hosttest_url', $config);
$remotetimeout = ParseINI::parseValue('hosttest_remote_timeout', $config);
if(!preg_match('#^(([a-z0-9.]+\.[a-z0-9]+)|\[([0-9a-f:]+)\]):([0-9]+)(/.*)$#', $url, $url_split)) {
trigger_error('Unable to parse hosttest_url from config.', E_USER_WARNING);
return true; // As earlier
}
$remote_start = microtime(true);
$fp = fsockopen($url_split[1], $url_split[4], $errno, $errstr, $remotetimeout); //fsockopen want's ipv6 with [], http's Host-field however does not!
if(!$fp) {
trigger_error('Unable to connect to remote C4HostTest '.$url_split[1].':'.$url_split[4]." in $remotetimeout secons.", E_USER_WARNING);
return true;
}
$req_str = $url_split[5] . '?remotecall&tcp='.$tcpport.'&udp='.$udpport.'&ip='.$_SERVER['REMOTE_ADDR'].'&time='.$timeout;
$remotetimeout += $timeout;
fwrite($fp, "GET ".$req_str." HTTP/1.0\r\nHost: ".($url_split[2]?$url_split[2]:$urlsplit[3])."\r\nUser-Agent: C4Masterserver\r\n\r\n"); // Injection warning!
$reply = '';
do {
stream_set_timeout($fp, $remotetimeout-microtime(true)+$remote_start);
$reply .= fread($fp, 8192);
} while (!feof($fp) && $remotetimeout > (microtime(true)-$remote_start));
if(!preg_match('#^HTTP/1.[01] 200#', $reply)) {
trigger_error('Unable to process response from C4HostTest. Wrong address in hosttest_url? No redirects allowed!', E_USER_WARNING);
return true;
}
if(!preg_match('/\r\n\r\n(.*\n)?Reached=[Yy]es\r?\n/s', $reply))
return false;
return true;
}
}
/**
* Perform host test on $host with $tcpport and $udpport, maximally take $timeout time.
*
*/
function performHostTest ($host,$tcpport,$udpport,$timeout) {
global $C4HostTestIncludeMode;
if($tcpport < 1024)
$tcpport = false;
if($udpport < 1024)
$udpport = false;
if($timeout < 0.001) // 0 check
return false;
if($timeout > 60)
$timeout = 60; // Actually, something larger than 30 seconds doesn't make much sense
$udpreqhex = array( // Magic UDP data stolen from a capture.
0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00, 0x02, 0x00, 0x2b, 0x73, 0x3e, 0xb4, 0x6c,
0xec, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00 );
$udpreq = '';
for($i=0; $i<count($udpreqhex); ++$i) {
$udpreq .= chr($udpreqhex[$i]);
}
$timeend = microtime(true) + $timeout;
$udpreply = false;
while(($cyclestart = microtime(true)) < $timeend) {
if(!$udp && $udpport)
if($udp = @fsockopen('udp://'.$host, $udpport, $udperrno, $udperrstr, 0.1)) // Timeout 1 sec, should theretically not take 'time' at all
stream_set_blocking($udp, false);
if($udp && !$udpreply) {
if(microtime(true) - $lastudppack > $timeout/5) // Send 5 Paks a max, usually because tcp waits for half timeout.
fwrite($udp, $udpreq);
$lastudppack = microtime(true);
$udpreply = fread($udp,1500);
}
if(!$tcp && $tcpport)
$tcp = @fsockopen($host, $tcpport, $tcperrno, $tcperrstr, min($timeout/2 - 0.1, $timeend-microtime(true)));
usleep(max(0,min($timeout*1000000/20,microtime(true)-$cylcestart))); // That means 20 cycles limit, even if $stuff goes wrong
if ($tcp || $udpreply)
break;
}
if(!$C4HostTestIncludeMode)
if($tcperrno && !$tcp)
echo "TCP=Error $tcperrno - $tcperrstr\n";
else if($tcp)
echo "TCP=Reached\n";
else
echo "TCP=Unknown\n"; //Somewhat unbeliegable that UDP replied that quick.
if(!$C4HostTestIncludeMode)
if($udperrno && !$udp)
echo "UDP=Error $udperrno - $udperrstr\n";
if(!$udpreply && $udp)
$udpreply = fread($udp,1500); // Last chance udp!
if(!$C4HostTestIncludeMode)
if(!$tcp && !$udpreply) // we can't say if tcp was just faster, so: ignore
echo "UDP=Timeout\n";
else if($tcp && !$udpreply)
echo "UDP=Unknown\n";
else if($udpreply)
echo "UDP=Reached\n";
return ($tcp || $udpreply); // Reachable if a tcp connection could be made, udp replied
}
?>