1 /* libh2.c: masterserver plugin for Heretic2 servers. */
2 /* Copyright (C) 2003 Andre' Schulz
3 * This file is part of masterserver.
4 *
5 * masterserver is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * masterserver is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with masterserver; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 * The author can be contacted at chickenman@exhale.de
20 */
21 /*
22 * vim:sw=4:ts=4
23 */
24
25 #include <pthread.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30 #include <sys/socket.h> // for socket() etc.
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33
34 #include "../masterserver.h"
35
36 #define HEARTBEAT_TIMEOUT 300
37
38 #undef LOG_SUBNAME
39 #define LOG_SUBNAME "libh2" // logging subcategory description
40
41 const char h2_pkt_header[] = "\xff\xff\xff\xff";
42 const int h2_pkt_header_len = 4;
43 // we just check for the keyword
44 const char h2_pkt_heartbeat[] = "heartbeat";
45 const int h2_pkt_heartbeat_len= 9;
46 const char h2_pkt_query[] = "query";
47 const int h2_pkt_query_len = 5;
48 const char h2_pkt_servers[] = "servers ";
49 const int h2_pkt_servers_len = 8;
50 const char h2_pkt_shutdown[] = "shutdown";
51 const int h2_pkt_shutdown_len = 8;
52 const char h2_pkt_ping[] = "ping";
53 const int h2_pkt_ping_len = 4;
54 const char h2_pkt_ack[] = "ack";
55 const int h2_pkt_ack_len = 3;
56
57 const char h2m_plugin_version[] = "0.4";
58 static port_t h2m_ports[] = { { IPPROTO_UDP, 28900 } };
59
60 static void info(void); // print information about plugin
61 static int process(char *, int); // process packet and return a value
62 static int process_heartbeat(char *);
63 static int process_shutdown(char *);
64 static int process_ping(char *);
65 static int process_query(char *);
66 void init_plugin(void) __attribute__ ((constructor));
67
68 static
69 struct masterserver_plugin h2m
70 = { "h2m",
71 h2m_plugin_version,
72 masterserver_version,
73 h2m_ports,
74 1,
75 HEARTBEAT_TIMEOUT,
76 &info,
77 &process,
78 NULL, // free_privdata()
79 NULL // cleanup()
80 };
81
82 static void
info(void)83 info(void)
84 {
85 INFO("heretic2 masterserver plugin v%s\n", h2m_plugin_version);
86 INFO(" compiled for masterserver v%s\n", masterserver_version);
87 }
88
89 static int
process_heartbeat(char * packet)90 process_heartbeat(char *packet)
91 {
92 int i, server_dup = 0, time_diff; // temp vars
93
94 // first, check if server is already in our list
95 for (i = 0; i < h2m.num_servers; i++) {
96 if ((h2m.list[i].ip.s_addr == h2m.client.sin_addr.s_addr)
97 && (h2m.list[i].port == h2m.client.sin_port)) {
98 DEBUG("duplicate server detected! (%s:%d)\n",
99 inet_ntoa(h2m.client.sin_addr), ntohs(h2m.client.sin_port));
100 server_dup = 1;
101 break;
102 }
103 }
104
105 INFO("heartbeat from %s:%d\n",
106 inet_ntoa(h2m.client.sin_addr), ntohs(h2m.client.sin_port));
107 // if not, then add it to the list
108 if (!server_dup) {
109 h2m.list[h2m.num_servers].ip = h2m.client.sin_addr;
110 h2m.list[h2m.num_servers].port = h2m.client.sin_port;
111 h2m.list[h2m.num_servers].lastheartbeat = time(NULL);
112 h2m.num_servers++;
113
114 DEBUG("reallocating server list (old size: %d -> new size: %d)\n",
115 h2m.num_servers * sizeof(serverlist_t),
116 (h2m.num_servers+1) * sizeof(serverlist_t));
117
118 h2m.list = realloc(h2m.list, ((h2m.num_servers + 1) * sizeof(serverlist_t)));
119 if (h2m.list == NULL) {
120 ERRORV("realloc() failed trying to get %d bytes!\n",
121 (h2m.num_servers+1)*sizeof(serverlist_t));
122 pthread_exit((void *) -1);
123 } else DEBUG("reallocation successful\n");
124 } else {
125 time_diff = time(NULL) - h2m.list[i].lastheartbeat;
126 // server is already in our list so we just update the timestamp
127 h2m.list[i].lastheartbeat = time(NULL);
128 server_dup = 0;
129 }
130 return 0; // server added to list
131 } // process_heartbeat()
132
133 static int
process_shutdown(char * packet)134 process_shutdown(char *packet)
135 {
136 int i, time_diff, server_dup = 0;
137
138 // sanity check num_servers
139 for (i = 0; i < h2m.num_servers; i++) {
140 if ((h2m.list[i].ip.s_addr == h2m.client.sin_addr.s_addr)
141 && (h2m.list[i].port == h2m.client.sin_port)) {
142 server_dup = 1;
143 break;
144 }
145 }
146
147 if (server_dup) {
148 time_diff = time(NULL) - h2m.list[i].lastheartbeat;
149 INFO("server %s:%u is shutting down (time_diff %d)\n",
150 inet_ntoa(h2m.list[i].ip), ntohs(h2m.list[i].port), time_diff);
151 delete_server(&h2m, i);
152 return 2; // return "server shutdown" code
153 } else return -1; // invalid packet
154 } // process_shutdown()
155
156 static int
process_ping(char * packet)157 process_ping(char *packet)
158 {
159 INFO("ping from %s:%d\n", inet_ntoa(h2m.client.sin_addr), ntohs(h2m.client.sin_port));
160 // allocate memory for msg_out_length array
161 h2m.msg_out_length = calloc(1, sizeof(int));
162 if (h2m.msg_out_length == NULL) {
163 ERRORV("calloc() failed trying to get %d bytes!\n",
164 sizeof(int));
165 return -2;
166 }
167
168 // write length of packet
169 h2m.msg_out_length[0] = h2_pkt_header_len + h2_pkt_ack_len;
170
171 // allocate memory for packet pointers (we only send 1)
172 h2m.msg_out = calloc(1, sizeof(char *));
173 if (h2m.msg_out == NULL) {
174 ERRORV("calloc() failed trying to get %d bytes!\n",
175 sizeof(char *));
176 return -2;
177 }
178
179 // allocate memory for the packet itself
180 h2m.msg_out[0] = calloc(h2m.msg_out_length[0]+1, 1);
181 if (h2m.msg_out[0] == NULL) {
182 ERRORV("calloc() failed trying to get %d bytes!\n",
183 h2m.msg_out_length[0]+1);
184 return -2;
185 }
186
187 // write packet contents
188 memcpy(h2m.msg_out[0], h2_pkt_header, h2_pkt_header_len);
189 memcpy(h2m.msg_out[0]+h2_pkt_header_len, h2_pkt_ack, h2_pkt_ack_len);
190
191 h2m.num_msgs = 1;
192
193 return 1; // tell masterserver to send it out
194 } // process_ping()
195
196 static int
process_query(char * packet)197 process_query(char *packet)
198 {
199 int i; // temp var
200 int msg_out_offset; // temp var for keeping track of where were writing the outgoing packet
201
202 INFO("query from %s:%d\n",
203 inet_ntoa(h2m.client.sin_addr), ntohs(h2m.client.sin_port));
204
205 /*
206 * the following char array will be our outgoing packet.
207 * first, we'll calculate the length.
208 * the length consists of the following values:
209 * - length of header -> h2_pkt_header_len
210 * - length of command -> h2_pkt_servers_len
211 * - delimiter between command and list
212 * - number of servers in list
213 * -> h2m.num_servers * 6
214 */
215
216 // allocate memory for msg_out_length array
217 // XXX: I don't know if there's any restriction in the h2m protocol
218 // regarding the # of servers or bytes sent to the client.
219 // I'll just leave it like this for now.
220 h2m.msg_out_length = calloc(1, sizeof(int));
221 if (h2m.msg_out_length == NULL) {
222 ERRORV("calloc() failed trying to get %d bytes!\n",
223 sizeof(int));
224 return -2; // TODO: define retval for errors
225 }
226
227 h2m.msg_out_length[0] = h2_pkt_header_len
228 + h2_pkt_servers_len
229 + (h2m.num_servers * 6);
230 DEBUG("%d + %d + (%d * 6) = %d\n",
231 h2_pkt_header_len, h2_pkt_servers_len,
232 h2m.num_servers, h2m.msg_out_length[0]);
233
234 // allocate memory for packet pointers
235 h2m.msg_out = calloc(1, sizeof(char *));
236 if (h2m.msg_out == NULL) {
237 ERRORV("calloc() failed trying to get %d bytes!\n",
238 sizeof(char *));
239 return -2; // TODO: define retval for errors
240 }
241
242 // allocate memory for the packet itself
243 h2m.msg_out[0] = calloc(h2m.msg_out_length[0]+1, 1);
244 if (h2m.msg_out[0] == NULL) {
245 ERRORV("calloc() failed trying to get %d bytes!\n",
246 h2m.msg_out_length[0]+1);
247 return -2; // TODO: define retval for errors
248 }
249
250 // copy h2_pkt_header into the packet
251 memcpy(h2m.msg_out[0], h2_pkt_header, h2_pkt_header_len);
252 memcpy(h2m.msg_out[0]+h2_pkt_header_len, h2_pkt_servers, h2_pkt_servers_len);
253 msg_out_offset = h2_pkt_header_len + h2_pkt_servers_len;
254
255 // create the packet
256 for (i = 0; i < h2m.num_servers; i++) {
257 // append ip and port to msg_out
258 memcpy(h2m.msg_out[0]+msg_out_offset, &h2m.list[i].ip, 4);
259 msg_out_offset += 4;
260 memcpy(h2m.msg_out[0]+msg_out_offset, &h2m.list[i].port, 2);
261 msg_out_offset += 2;
262 }
263
264 h2m.num_msgs = 1;
265
266 // packet with server list is ready
267 return 1;
268 } // process_query()
269
270 static int
process(char * packet,int packetlen)271 process(char *packet, int packetlen)
272 {
273
274 // check if packet is h2 related
275 if (strncmp(packet, h2_pkt_header, h2_pkt_header_len) == 0) {
276 DEBUG("H2 protocol marker detected!\n");
277 // which packet did we receive?
278 if (strncmp(packet+h2_pkt_header_len, h2_pkt_heartbeat, h2_pkt_heartbeat_len) == 0) {
279 return process_heartbeat(packet);
280 } else if (strncmp(packet+h2_pkt_header_len, h2_pkt_shutdown, h2_pkt_shutdown_len) == 0) {
281 return process_shutdown(packet);
282 } else if (strncmp(packet+h2_pkt_header_len, h2_pkt_ping, h2_pkt_ping_len) == 0) {
283 return process_ping(packet);
284 }
285 } else if (strncmp(packet, h2_pkt_query, h2_pkt_query_len) == 0) {
286 return process_query(packet);
287 }
288 return -1; // invalid packet
289 }
290
291 void
init_plugin(void)292 init_plugin(void)
293 {
294 register_plugin(&h2m);
295 }
296
297