171 lines
5.4 KiB
Perl
Executable File
171 lines
5.4 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
# $Id: metaserver.pl.in 6717 2007-06-27 20:07:36Z akirschbaum $
|
|
|
|
# Copyright 2000 by Mark Wedel.
|
|
# This script follows the same license as crossfire (GPL).
|
|
|
|
use Socket;
|
|
use English;
|
|
|
|
# We periodically generate a nice HTML file that people can't put their
|
|
# web browser at. This is the location of that file.
|
|
$HTML_FILE="/var/apache/htdocs/metaserver.html";
|
|
|
|
# Cache file to keep data we ahve collected. This is used so that if
|
|
# the metaserver program crashes/dies, it still has some old data.
|
|
# You may want to set this to a location that will survive across system
|
|
# reboots.
|
|
$CACHE_FILE="/var/tmp/meta_xfire.cache";
|
|
|
|
# We remove a server after $REMOVE_SERVER number of seconds of no updates.
|
|
# 600 is 10 minutes - if we haven't gotten an update that fast, the server
|
|
# is almost certainly gone/not available. This reduces congestion when
|
|
# someone on a dhcp connection keeps running a server and it fills
|
|
# up a bunch of the slots.
|
|
$REMOVE_SERVER=600;
|
|
|
|
# UPDATE_SYNC determines how often we update the HTML_FILE and CACHE_FILE.
|
|
$UPDATE_SYNC=300;
|
|
|
|
# IP_INTERVAL is how often (in seconds) same IP can request metaserver
|
|
# info. This is to prevent DOS attacks.
|
|
|
|
$IP_INTERVAL=5;
|
|
|
|
# For gathering some anonymous statistics. You probably want to use
|
|
# MRTG/RRDTOOL for generating statistics from the file.
|
|
# -- Heikki Hokkanen, 2005-03-26
|
|
my $STATS_FILE="/var/tmp/meta_xfire.stats";
|
|
my $stats_updatehits = 0; # COUNTER
|
|
my $stats_requesthits = 0; # COUNTER
|
|
my $stats_totalplayers = 0; # GAUGE
|
|
my $stats_totalservers = 0; # GAUGE
|
|
|
|
socket(SOCKET, PF_INET, SOCK_DGRAM, getprotobyname("udp")) ||
|
|
die("$0: can not open udp socket: $OS_ERROR\n");
|
|
bind(SOCKET, sockaddr_in(13326, INADDR_ANY)) ||
|
|
die("$0: Can not bind to socket: $OS_ERROR\n");
|
|
|
|
# Socket1 is used for incoming requests - if we get a connection on this,
|
|
# we dump out our data to that socket in an easily parsible form.
|
|
socket(SOCKET1, PF_INET, SOCK_STREAM, getprotobyname("tcp")) ||
|
|
die("$0: can not open tcp socket: $OS_ERROR\n");
|
|
|
|
# errors on this not that critical
|
|
setsockopt(SOCKET1, SOL_SOCKET, SO_REUSEADDR, 1);
|
|
|
|
bind(SOCKET1, sockaddr_in(13326, INADDR_ANY)) ||
|
|
die("$0: Can not bind to socket: $OS_ERROR\n");
|
|
listen(SOCKET1, 10) ||
|
|
die("$0: Can not listen on socket: $OS_ERROR\n");
|
|
|
|
vec($rin, fileno(SOCKET), 1)=1;
|
|
vec($rin, fileno(SOCKET1), 1)=1;
|
|
|
|
if (open(CACHE,"<$CACHE_FILE")) {
|
|
while (<CACHE>) {
|
|
chomp;
|
|
($ip, $rest) = split /\|/, $_, 2;
|
|
$data{$ip} = $_;
|
|
}
|
|
}
|
|
close(CACHE);
|
|
|
|
$last_sync=time;
|
|
|
|
while (1) {
|
|
$nfound=select($rout=$rin, undef, undef, 60);
|
|
$cur_time=time;
|
|
if ($nfound) {
|
|
if (vec($rout, fileno(SOCKET),1)) {
|
|
$ipaddr = recv(SOCKET, $data, 256, 0) ||
|
|
print STDERR "$0: error on recv call: $OS_ERROR\n";
|
|
($port, $ip) = sockaddr_in($ipaddr);
|
|
$host = inet_ntoa $ip;
|
|
($name, $rest) = split /\|/, $data;
|
|
if ($name ne "put.your.hostname.here") {
|
|
$data{$host} = "$host|$cur_time|$data";
|
|
$stats_updatehits++;
|
|
}
|
|
}
|
|
if (vec($rout, fileno(SOCKET1),1)) {
|
|
# This is overly basic - if there are enough servers
|
|
# where the output won't fit in the outgoing socket,
|
|
# this will block. However, if we fork, we open
|
|
# ourselves up to someone intentionally designing something
|
|
# that causes these to block, and then have us fork a huge
|
|
# number of process potentially filling up our proc table.
|
|
if ($ipaddr=accept NEWSOCKET, SOCKET1) {
|
|
($port, $ip ) = sockaddr_in( $ipaddr );
|
|
$dq = join('.',unpack('C4', $ip));
|
|
if ($ip_times{$dq} > time) {
|
|
close(NEWSOCKET);
|
|
} else {
|
|
$ip_times{$dq} = time + $IP_INTERVAL;
|
|
foreach $i (keys %data) {
|
|
# Report idle time to client, and not last update
|
|
# as we received in seconds since epoch.
|
|
($ip, $time, $rest) = split /\|/, $data{$i}, 3;
|
|
$newtime = $cur_time - $time;
|
|
print NEWSOCKET "$ip|$newtime|$rest\n";
|
|
}
|
|
close(NEWSOCKET);
|
|
$stats_requesthits++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Need to generate some files. This is also where we remove outdated
|
|
# hosts.
|
|
if ($last_sync+$UPDATE_SYNC < $cur_time) {
|
|
$last_sync = $cur_time;
|
|
open(CACHE,">$CACHE_FILE");
|
|
open(HTML,">$HTML_FILE");
|
|
|
|
print HTML
|
|
'<title>Crossfire Server List</title>
|
|
<h1 align=center>Crossfire Server List</h1><p>
|
|
<table border=1 align=center cellpadding=5>
|
|
<tr>
|
|
<th>IP Address</th><th>Last Update Date/Time</th><th>Last Update Minutes Elapsed</th>
|
|
<th>Hostname</th><th>Number of Players</th><th>Version</th><th>Comment</th>
|
|
</tr>
|
|
';
|
|
|
|
$stats_totalplayers = 0;
|
|
$stats_totalservers = 0;
|
|
foreach $i (keys %data) {
|
|
$stats_totalservers++;
|
|
($ip, $time, @rest) = split /\|/, $data{$i};
|
|
if ($time+$REMOVE_SERVER<$cur_time) {
|
|
delete $data{$i};
|
|
} else {
|
|
print CACHE "$data{$i}\n";
|
|
$elapsed = int(($cur_time - $time)/60);
|
|
$gmtime = gmtime($time);
|
|
print HTML "<tr><td>$i</td><td>$gmtime</td><td>$elapsed</td>";
|
|
print HTML "<td>$rest[0]</td><td>$rest[1]</td><td>$rest[2]</td><td>$rest[3]</td></tr>\n";
|
|
$stats_totalplayers += int($rest[2]);
|
|
}
|
|
}
|
|
$gmtime = gmtime($cur_time);
|
|
print HTML "
|
|
</table><p>
|
|
The server name is reported by the server, while the ip address is determined by
|
|
the incoming data packet. These values may not resolve to the same thing in the
|
|
case of multi homed hosts or multi ip hosts.<p>
|
|
|
|
All times are in GMT.<p>
|
|
|
|
<font size=-2>Last Updated: $gmtime<font size=+2><p>";
|
|
close(HTML);
|
|
close(CACHE);
|
|
|
|
open(STATS,">$STATS_FILE");
|
|
print STATS "$stats_updatehits:$stats_requesthits:$stats_totalservers:$stats_totalplayers\n";
|
|
close(STATS);
|
|
|
|
}
|
|
}
|