1 |
#!/usr/bin/perl |
2 |
# $Id: metaserver.pl.in,v 1.3 2005/07/08 23:48:59 mwedel Exp $ |
3 |
|
4 |
# Copyright 2000 by Mark Wedel. |
5 |
# This script follows the same license as crossfire (GPL). |
6 |
|
7 |
use Socket; |
8 |
use English; |
9 |
|
10 |
# We periodically generate a nice HTML file that people can't put their |
11 |
# web browser at. This is the location of that file. |
12 |
$HTML_FILE="/var/apache/htdocs/metaserver.html"; |
13 |
|
14 |
# Cache file to keep data we ahve collected. This is used so that if |
15 |
# the metaserver program crashes/dies, it still has some old data. |
16 |
# You may want to set this to a location that will survive across system |
17 |
# reboots. |
18 |
$CACHE_FILE="/var/tmp/meta_xfire.cache"; |
19 |
|
20 |
# We remove a server after $REMOVE_SERVER number of seconds of no updates. |
21 |
# 600 is 10 minutes - if we haven't gotten an update that fast, the server |
22 |
# is almost certainly gone/not available. This reduces congestion when |
23 |
# someone on a dhcp connection keeps running a server and it fills |
24 |
# up a bunch of the slots. |
25 |
$REMOVE_SERVER=600; |
26 |
|
27 |
# UPDATE_SYNC determines how often we update the HTML_FILE and CACHE_FILE. |
28 |
$UPDATE_SYNC=300; |
29 |
|
30 |
# IP_INTERVAL is how often (in seconds) same IP can request metaserver |
31 |
# info. This is to prevent DOS attacks. |
32 |
|
33 |
$IP_INTERVAL=5; |
34 |
|
35 |
# For gathering some anonymous statistics. You probably want to use |
36 |
# MRTG/RRDTOOL for generating statistics from the file. |
37 |
# -- Heikki Hokkanen, 2005-03-26 |
38 |
my $STATS_FILE="/var/tmp/meta_xfire.stats"; |
39 |
my $stats_updatehits = 0; # COUNTER |
40 |
my $stats_requesthits = 0; # COUNTER |
41 |
my $stats_totalplayers = 0; # GAUGE |
42 |
my $stats_totalservers = 0; # GAUGE |
43 |
|
44 |
socket(SOCKET, PF_INET, SOCK_DGRAM, getprotobyname("udp")) || |
45 |
die("$0: can not open udp socket: $OS_ERROR\n"); |
46 |
bind(SOCKET, sockaddr_in(13326, INADDR_ANY)) || |
47 |
die("$0: Can not bind to socket: $OS_ERROR\n"); |
48 |
|
49 |
# Socket1 is used for incoming requests - if we get a connection on this, |
50 |
# we dump out our data to that socket in an easily parsible form. |
51 |
socket(SOCKET1, PF_INET, SOCK_STREAM, getprotobyname("tcp")) || |
52 |
die("$0: can not open tcp socket: $OS_ERROR\n"); |
53 |
|
54 |
# errors on this not that critical |
55 |
setsockopt(SOCKET1, SOL_SOCKET, SO_REUSEADDR, 1); |
56 |
|
57 |
bind(SOCKET1, sockaddr_in(13326, INADDR_ANY)) || |
58 |
die("$0: Can not bind to socket: $OS_ERROR\n"); |
59 |
listen(SOCKET1, 10) || |
60 |
die("$0: Can not listen on socket: $OS_ERROR\n"); |
61 |
|
62 |
vec($rin, fileno(SOCKET), 1)=1; |
63 |
vec($rin, fileno(SOCKET1), 1)=1; |
64 |
|
65 |
if (open(CACHE,"<$CACHE_FILE")) { |
66 |
while (<CACHE>) { |
67 |
chomp; |
68 |
($ip, $rest) = split /\|/, $_, 2; |
69 |
$data{$ip} = $_; |
70 |
} |
71 |
} |
72 |
close(CACHE); |
73 |
|
74 |
$last_sync=time; |
75 |
|
76 |
while (1) { |
77 |
$nfound=select($rout=$rin, undef, undef, 60); |
78 |
$cur_time=time; |
79 |
if ($nfound) { |
80 |
if (vec($rout, fileno(SOCKET),1)) { |
81 |
$ipaddr = recv(SOCKET, $data, 256, 0) || |
82 |
print STDERR "$0: error on recv call: $OS_ERROR\n"; |
83 |
($port, $ip) = sockaddr_in($ipaddr); |
84 |
$host = inet_ntoa $ip; |
85 |
($name, $rest) = split /\|/, $data; |
86 |
if ($name ne "put.your.hostname.here") { |
87 |
$data{$host} = "$host|$cur_time|$data"; |
88 |
$stats_updatehits++; |
89 |
} |
90 |
} |
91 |
if (vec($rout, fileno(SOCKET1),1)) { |
92 |
# This is overly basic - if there are enough servers |
93 |
# where the output won't fit in the outgoing socket, |
94 |
# this will block. However, if we fork, we open |
95 |
# ourselves up to someone intentionally designing something |
96 |
# that causes these to block, and then have us fork a huge |
97 |
# number of process potentially filling up our proc table. |
98 |
if ($ipaddr=accept NEWSOCKET, SOCKET1) { |
99 |
($port, $ip ) = sockaddr_in( $ipaddr ); |
100 |
$dq = join('.',unpack('C4', $ip)); |
101 |
if ($ip_times{$dq} > time) { |
102 |
close(NEWSOCKET); |
103 |
} else { |
104 |
$ip_times{$dq} = time + $IP_INTERVAL; |
105 |
foreach $i (keys %data) { |
106 |
# Report idle time to client, and not last update |
107 |
# as we received in seconds since epoch. |
108 |
($ip, $time, $rest) = split /\|/, $data{$i}, 3; |
109 |
$newtime = $cur_time - $time; |
110 |
print NEWSOCKET "$ip|$newtime|$rest\n"; |
111 |
} |
112 |
close(NEWSOCKET); |
113 |
$stats_requesthits++; |
114 |
} |
115 |
} |
116 |
} |
117 |
} |
118 |
|
119 |
# Need to generate some files. This is also where we remove outdated |
120 |
# hosts. |
121 |
if ($last_sync+$UPDATE_SYNC < $cur_time) { |
122 |
$last_sync = $cur_time; |
123 |
open(CACHE,">$CACHE_FILE"); |
124 |
open(HTML,">$HTML_FILE"); |
125 |
|
126 |
print HTML |
127 |
'<title>Crossfire Server List</title> |
128 |
<h1 align=center>Crossfire Server List</h1><p> |
129 |
<table border=1 align=center cellpadding=5> |
130 |
<tr> |
131 |
<th>IP Address</th><th>Last Update Date/Time</th><th>Last Update Minutes Elapsed</th> |
132 |
<th>Hostname</th><th>Number of Players</th><th>Version</th><th>Comment</th> |
133 |
</tr> |
134 |
'; |
135 |
|
136 |
$stats_totalplayers = 0; |
137 |
$stats_totalservers = 0; |
138 |
foreach $i (keys %data) { |
139 |
$stats_totalservers++; |
140 |
($ip, $time, @rest) = split /\|/, $data{$i}; |
141 |
if ($time+$REMOVE_SERVER<$cur_time) { |
142 |
delete $data{$i}; |
143 |
} else { |
144 |
print CACHE "$data{$i}\n"; |
145 |
$elapsed = int(($cur_time - $time)/60); |
146 |
$gmtime = gmtime($time); |
147 |
print HTML "<tr><td>$i</td><td>$gmtime</td><td>$elapsed</td>"; |
148 |
print HTML "<td>$rest[0]</td><td>$rest[1]</td><td>$rest[2]</td><td>$rest[3]</td></tr>\n"; |
149 |
$stats_totalplayers += int($rest[2]); |
150 |
} |
151 |
} |
152 |
$gmtime = gmtime($cur_time); |
153 |
print HTML " |
154 |
</table><p> |
155 |
The server name is reported by the server, while the ip address is determined by |
156 |
the incoming data packet. These values may not resolve to the same thing in the |
157 |
case of multi homed hosts or multi ip hosts.<p> |
158 |
|
159 |
All times are in GMT.<p> |
160 |
|
161 |
<font size=-2>Last Updated: $gmtime<font size=+2><p>"; |
162 |
close(HTML); |
163 |
close(CACHE); |
164 |
|
165 |
open(STATS,">$STATS_FILE"); |
166 |
print STATS "$stats_updatehits:$stats_requesthits:$stats_totalservers:$stats_totalplayers\n"; |
167 |
close(STATS); |
168 |
|
169 |
} |
170 |
} |