Add a way to test hosts for their hosting capabilities to the masterserver.

Julius Michaelis 2012-01-05 23:30:11 +01:00
parent 2b33a55cfb
commit 9f855c6067
3 changed files with 143 additions and 1 deletions

View File

@ -0,0 +1,124 @@
<?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-z.]+\.[a-z]+):([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[2], $errno, $errstr, $remotetimeout);
if(!$fp) {
trigger_error('Unable to connect to remote C4HostTest '.$url_split[1].':'.$url_split[2]." in $removetimeout secons.", E_USER_WARNING);
return true;
}
$req_str = $url_split[3] . '?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[1]."\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
}
?>

View File

@ -46,3 +46,15 @@ oc_update_url=http://www.example.com/
;secret key for the HAMC-updating system (using sha256) to verify files
oc_update_secret=
;host port testing: 0 - off (default), 1 - perfrom from local host, 2 - perform from from hosttest_url
hosttest_mode=0
;time for the client to reply - keep in mind that if that test fails, a client will have to wait at least that long.
hosttest_timeout=6
;remote address of host testing script, supply in format of masterserver address in oc config
hosttest_url=
;hosttest provider answer time. Additional time that may be consumed by establishing the http connection, etc.
hosttest_remote_timeout=1.5

View File

@ -14,6 +14,8 @@ require_once('include/C4Masterserver.php');
require_once('include/C4Network.php');
require_once('include/FloodProtection.php');
require_once('include/ParseINI.php');
$C4HostTestIncludeMode = true;
require_once('include/C4HostTest.php');
$config = file_get_contents('include/config.ini');
$link = mysql_connect(
@ -85,7 +87,11 @@ if ($link && $db) {
} else {
$csid = $server->addReference($reference);
if ($csid) {
C4Network::sendAnswer(C4Network::createAnswer(array('Status' => 'Success', 'CSID' => $csid)));
$answer = array('Status' => 'Success', 'CSID' => $csid);
if(!testHostConn($input))
$answer['Message'] = 'Your network failed to pass certain tests. It is unlikely that are you able to host for the public.|To fix that, you need port forwarding in your router.';
C4Network::sendAnswer(C4Network::createAnswer($answer));
unset($answer);
} else {
C4Network::sendAnswer(C4Network::createError('Round signup failed. (To many tries?)'));
}