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