hex

hex.git
git clone git://git.lenczewski.org/hex.git
Log | Files | Refs

server.c (12233B)


      1 #include "hex_server.h"
      2 
      3 bool
      4 server_init(struct server_state *state)
      5 {
      6 	assert(state);
      7 
      8 	struct addrinfo hints = {
      9 		.ai_family = AF_UNSPEC,
     10 		.ai_socktype = SOCK_STREAM,
     11 		.ai_flags = AI_PASSIVE,
     12 	}, *addrinfo, *ptr;
     13 
     14 	int res;
     15 	if ((res = getaddrinfo("localhost", "0", &hints, &addrinfo))) {
     16 		errlog("[server] Failed to get address information: %s\n", gai_strerror(res));
     17 		goto error_without_socket;
     18 	}
     19 
     20 	for (ptr = addrinfo; ptr; ptr = ptr->ai_next) {
     21 		state->servfd = socket(ptr->ai_family, ptr->ai_socktype | SOCK_CLOEXEC, ptr->ai_protocol);
     22 		if (state->servfd == -1) continue;
     23 		if (bind(state->servfd, ptr->ai_addr, ptr->ai_addrlen) != -1) break;
     24 		close(state->servfd);
     25 	}
     26 
     27 	freeaddrinfo(addrinfo);
     28 
     29 	if (!ptr) {
     30 		errlog("[server] Failed to bind server socket\n");
     31 		goto error_without_socket;
     32 	}
     33 
     34 	state->serv_addrlen = sizeof state->serv_addr;
     35 	if (getsockname(state->servfd, (struct sockaddr *) &state->serv_addr, &state->serv_addrlen)) {
     36 		errlog("[server] Failed to get server socket addr\n");
     37 		goto error;
     38 	}
     39 
     40 	if ((res = getnameinfo((struct sockaddr *) &state->serv_addr, state->serv_addrlen,
     41 				state->serv_host, sizeof state->serv_host,
     42 				state->serv_port, sizeof state->serv_port,
     43 				NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
     44 		errlog("[server] Failed to get bound socket addr host and port\n");
     45 		goto error;
     46 	}
     47 
     48 	listen(state->servfd, 2);
     49 
     50 	dbglog("[server] Server socket is listening on %s:%s\n", state->serv_host, state->serv_port);
     51 
     52 	return true;
     53 
     54 error:
     55 	close(state->servfd);
     56 
     57 error_without_socket:
     58 	return false;
     59 }
     60 
     61 void
     62 server_free(struct server_state *state)
     63 {
     64 	assert(state);
     65 
     66 	close(state->servfd);
     67 }
     68 
     69 bool
     70 server_spawn_agent(struct server_state *state, struct agent_state *agent_state)
     71 {
     72 	assert(state);
     73 	assert(agent_state);
     74 
     75 	int fd;
     76 	if ((fd = mkstemp(agent_state->logfile)) != -1) {
     77 		fchmod(fd, HEX_AGENT_LOGFILE_MODE);
     78 
     79 		dbglog("[server] Created logfile '%s' for agent: '%s'\n",
     80 			agent_state->logfile, agent_state->agent);
     81 	} else {
     82 		dbglog("[server] Failed to create logfile '%s' for agent: '%s'\n",
     83 			agent_state->logfile, agent_state->agent);
     84 
     85 		strcpy(agent_state->logfile, "/dev/null");
     86 	}
     87 
     88 	pid_t child_pid = fork();
     89 
     90 	if (child_pid == 0) { /* child process, exec() agent */
     91 		pid_t pid = getpid();
     92 
     93 		dbglog("[server] Child process '%" PRIi32 "', setting uid\n", pid);
     94 
     95 		if (setuid(agent_state->agent_uid) == -1) {
     96 			perror("setuid");
     97 			exit(EXIT_FAILURE); /* fork()-d process can die without issue */
     98 		}
     99 
    100 		dbglog("[server] Child process '%" PRIi32 "', setting resource limits\n", pid);
    101 
    102 		struct rlimit limit;
    103 
    104 		limit.rlim_cur = limit.rlim_max = opts.agent_threads;
    105 		prlimit(pid, RLIMIT_NPROC, &limit, NULL);
    106 
    107 		limit.rlim_cur = limit.rlim_max = opts.agent_mem_mib * 1024 * 1024;
    108 		prlimit(pid, RLIMIT_DATA, &limit, NULL);
    109 
    110 		char *args[] = {
    111 			agent_state->agent,
    112 			state->serv_host,
    113 			state->serv_port,
    114 			NULL,
    115 		};
    116 
    117 		char *env[] = {
    118 			NULL,
    119 		};
    120 
    121 		dbglog("[server] Child process '%" PRIi32 "', exec()-ing agent: '%s'\n",
    122 			pid, agent_state->agent);
    123 
    124 		if (!freopen("/dev/null", "rb", stdin)) {
    125 			perror("freopen(stdin)");
    126 			exit(EXIT_FAILURE); /* fork()-d process can die without issue */
    127 		}
    128 
    129 		if (!freopen(agent_state->logfile, "wb", stdout)) {
    130 			perror("freopen(stdout)");
    131 			exit(EXIT_FAILURE); /* fork()-d process can die without issue */
    132 		}
    133 
    134 		if (!freopen(agent_state->logfile, "wb", stderr)) {
    135 			perror("freopen(stderr)");
    136 			exit(EXIT_FAILURE); /* fork()-d process can die without issue */
    137 		}
    138 
    139 		if (execve(agent_state->agent, args, env)) {
    140 			perror("execve");
    141 			exit(EXIT_FAILURE); /* fork()-d process can die without issue */
    142 		}
    143 	} else if (child_pid == -1) { /* parent process, fork() error */
    144 		perror("fork");
    145 		errlog("[server] Failed to fork() to agent process: '%s'\n", agent_state->agent);
    146 		goto error;
    147 	}
    148 
    149 	/* parent process, fork() success */
    150 
    151 	/* accept() the agent socket */
    152 	struct pollfd pollfds[] = {
    153 		{ .fd = state->servfd, .events = POLLIN, },
    154 	};
    155 
    156 	int ready = poll(pollfds, 1, HEX_AGENT_ACCEPT_TIMEOUT_MS);
    157 
    158 	if (ready == -1) {
    159 		perror("poll");
    160 		goto error;
    161 	} else if (ready == 0) {
    162 		errlog("[server] %s (%s) timed out during accept() period, assuming forfeit\n",
    163 			hexplayerstr(agent_state->player), agent_state->agent);
    164 		goto error;
    165 	}
    166 
    167 	int sockflags = SOCK_CLOEXEC;
    168 	agent_state->sockfd = accept4(state->servfd,
    169 				      (struct sockaddr *) &agent_state->sock_addr,
    170 				      &agent_state->sock_addrlen,
    171 				      sockflags);
    172 
    173 	if (agent_state->sockfd == -1) {
    174 		perror("accept4");
    175 		goto error;
    176 	}
    177 
    178 	return true;
    179 
    180 error:
    181 	kill(0, SIGKILL);
    182 
    183 	int wpid, wstatus;
    184 	while ((wpid = wait(&wstatus)) > 0); /* wait for all children to die */
    185 
    186 	return false;
    187 }
    188 
    189 void
    190 server_wait_all_agents(struct server_state *state)
    191 {
    192 	assert(state);
    193 
    194 	int wpid, wstatus;
    195 	while ((wpid = wait(&wstatus)) > 0) {
    196 		dbglog("[server] Child process '%" PRIi32 "' returned code: %d\n",
    197 			wpid, WEXITSTATUS(wstatus));
    198 	}
    199 }
    200 
    201 static enum hex_error
    202 send_msg(struct agent_state *agent, struct hex_msg *msg, b32 force);
    203 
    204 static enum hex_error
    205 recv_msg(struct agent_state *agent, struct hex_msg *out, enum hex_msg_type *expected, size_t len);
    206 
    207 static enum hex_error
    208 play_round(struct server_state *state, size_t turn, enum hex_player *winner);
    209 
    210 void
    211 server_run(struct server_state *state, struct statistics *statistics)
    212 {
    213 	assert(state);
    214 
    215 	enum hex_error err;
    216 
    217 	enum hex_player winner;
    218 
    219 	/* setup common statistics */
    220 	statistics->agent_1 = state->black_agent.agent;
    221 	statistics->agent_2 = state->white_agent.agent;
    222 
    223 	/* send a start message to both agents, including all game parameters
    224 	 */
    225 	struct hex_msg msg;
    226 	msg.type = HEX_MSG_START;
    227 	msg.data.start.board_size = opts.board_size;
    228 	msg.data.start.game_secs = opts.game_secs;
    229 	msg.data.start.thread_limit = opts.agent_threads;
    230 	msg.data.start.mem_limit_mib = opts.agent_mem_mib;
    231 
    232 	msg.data.start.player = HEX_PLAYER_BLACK;
    233 	if ((err = send_msg(&state->black_agent, &msg, true))) goto forfeit_black;
    234 
    235 	msg.data.start.player = HEX_PLAYER_WHITE;
    236 	if ((err = send_msg(&state->white_agent, &msg, true))) goto forfeit_white;
    237 
    238 	size_t round = 0;
    239 	while ((err = play_round(state, round++, &winner)) == HEX_ERROR_OK);
    240 
    241 	msg.type = HEX_MSG_END;
    242 	msg.data.end.winner = winner;
    243 
    244 	send_msg(&state->black_agent, &msg, true);
    245 	send_msg(&state->white_agent, &msg, true);
    246 
    247 	/* calculate game statistics
    248 	 */
    249 	statistics->agent_1_won = state->black_agent.player == winner;
    250 	statistics->agent_2_won = state->white_agent.player == winner;
    251 
    252 	statistics->agent_1_rounds = (round + 1) / 2;
    253 	statistics->agent_2_rounds = round / 2;
    254 
    255 	statistics->agent_1_secs = state->black_agent.timer.tv_sec
    256 				 + state->black_agent.timer.tv_nsec / (f32) NANOSECS;
    257 	statistics->agent_2_secs = state->white_agent.timer.tv_sec
    258 				 + state->white_agent.timer.tv_nsec / (f32) NANOSECS;
    259 
    260 	if (winner == HEX_PLAYER_BLACK) {
    261 		statistics->agent_1_err = HEX_ERROR_OK;
    262 		statistics->agent_2_err = err;
    263 	} else {
    264 		statistics->agent_1_err = err;
    265 		statistics->agent_2_err = HEX_ERROR_OK;
    266 	}
    267 
    268 	return;
    269 
    270 forfeit_black:
    271 	statistics->agent_1_won = false;
    272 	statistics->agent_2_won = true;
    273 
    274 	statistics->agent_1_rounds = statistics->agent_2_rounds = 0;
    275 
    276 	statistics->agent_1_secs = state->black_agent.timer.tv_sec
    277 				 + state->black_agent.timer.tv_nsec / (f32) NANOSECS;
    278 	statistics->agent_2_secs = state->white_agent.timer.tv_sec
    279 				 + state->white_agent.timer.tv_nsec / (f32) NANOSECS;
    280 
    281 	return;
    282 
    283 forfeit_white:
    284 	statistics->agent_1_won = true;
    285 	statistics->agent_2_won = false;
    286 
    287 	statistics->agent_1_rounds = statistics->agent_2_rounds = 0;
    288 
    289 	statistics->agent_1_secs = state->black_agent.timer.tv_sec
    290 				 + state->black_agent.timer.tv_nsec / (f32) NANOSECS;
    291 	statistics->agent_2_secs = state->white_agent.timer.tv_sec
    292 				 + state->white_agent.timer.tv_nsec / (f32) NANOSECS;
    293 
    294 	return;
    295 }
    296 
    297 static enum hex_error
    298 send_msg(struct agent_state *agent, struct hex_msg *msg, b32 force)
    299 {
    300 	assert(agent);
    301 	assert(msg);
    302 
    303 	size_t nbytes_sent = 0;
    304 
    305 	u8 buf[HEX_MSG_SZ];
    306 	if (!hex_msg_try_serialise(msg, buf)) return HEX_ERROR_BAD_MSG;
    307 
    308 	struct pollfd pollfd = { .fd = agent->sockfd, .events = POLLOUT, };
    309 
    310 	struct timespec start, end, diff, temp;
    311 	if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) {
    312 		perror("clock_gettime");
    313 		return HEX_ERROR_SERVER;
    314 	}
    315 
    316 	int res;
    317 	while (nbytes_sent < ARRLEN(buf) && (res = ppoll(&pollfd, 1, force ? NULL : &agent->timer, NULL)) > 0) {
    318 		ssize_t curr = send(pollfd.fd, buf + nbytes_sent, ARRLEN(buf) - nbytes_sent, 0);
    319 
    320 		if (curr <= 0) /* connection closed or error */
    321 			return HEX_ERROR_DISCONNECT;
    322 
    323 		if (clock_gettime(CLOCK_MONOTONIC, &end) < 0) {
    324 			perror("clock_gettime");
    325 			return HEX_ERROR_SERVER;
    326 		}
    327 
    328 		difftimespec(&end, &start, &diff);
    329 		difftimespec(&agent->timer, &diff, &temp);
    330 
    331 		start = end;
    332 		agent->timer = temp;
    333 
    334 		nbytes_sent += curr;
    335 	}
    336 
    337 	if (res == 0) { /* timeout */
    338 		dbglog("[server] Timeout when sending message to %s\n",
    339 			hexplayerstr(agent->player));
    340 		return HEX_ERROR_TIMEOUT;
    341 	}
    342 
    343 	if (res == -1) {
    344 		perror("ppoll");
    345 		return HEX_ERROR_SERVER;
    346 	}
    347 
    348 	return HEX_ERROR_OK;
    349 }
    350 
    351 static enum hex_error
    352 recv_msg(struct agent_state *agent, struct hex_msg *out, enum hex_msg_type *expected, size_t len)
    353 {
    354 	assert(agent);
    355 	assert(out);
    356 	assert(expected);
    357 
    358 	size_t nbytes_received = 0;
    359 
    360 	u8 buf[HEX_MSG_SZ];
    361 
    362 	struct pollfd pollfd = { .fd = agent->sockfd, .events = POLLIN, };
    363 
    364 	struct timespec start, end, diff, temp;
    365 	if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) {
    366 		perror("clock_gettime");
    367 		return HEX_ERROR_SERVER;
    368 	}
    369 
    370 	int res;
    371 	while (nbytes_received < ARRLEN(buf) && (res = ppoll(&pollfd, 1, &agent->timer, NULL)) > 0) {
    372 		ssize_t curr = recv(pollfd.fd, buf + nbytes_received, ARRLEN(buf) - nbytes_received, 0);
    373 
    374 		if (curr <= 0) /* connection closed or error */
    375 			return HEX_ERROR_DISCONNECT;
    376 
    377 		if (clock_gettime(CLOCK_MONOTONIC, &end) < 0) {
    378 			perror("clock_gettime");
    379 			return HEX_ERROR_SERVER;
    380 		}
    381 
    382 		difftimespec(&end, &start, &diff);
    383 		difftimespec(&agent->timer, &diff, &temp);
    384 
    385 		start = end;
    386 		agent->timer = temp;
    387 
    388 		nbytes_received += curr;
    389 	}
    390 
    391 	if (res == 0) { /* timeout */
    392 		dbglog("[server] Timeout while receiving message from %s\n",
    393 			hexplayerstr(agent->player));
    394 		return HEX_ERROR_TIMEOUT;
    395 	}
    396 
    397 	if (res == -1) {
    398 		perror("ppoll");
    399 		return HEX_ERROR_SERVER;
    400 	}
    401 
    402 	if (!hex_msg_try_deserialise(buf, out)) return HEX_ERROR_BAD_MSG;
    403 
    404 	for (size_t i = 0; i < len; i++) {
    405 		if (out->type == expected[i]) return HEX_ERROR_OK;
    406 	}
    407 
    408 	return HEX_ERROR_BAD_MSG;
    409 }
    410 
    411 static enum hex_error
    412 play_round(struct server_state *state, size_t turn, enum hex_player *winner)
    413 {
    414 	assert(state);
    415 	assert(winner);
    416 
    417 	enum hex_error err;
    418 
    419 	struct agent_state *agents[] = {
    420 		[HEX_PLAYER_BLACK] = &state->black_agent,
    421 		[HEX_PLAYER_WHITE] = &state->white_agent,
    422 	};
    423 
    424 	struct agent_state *player = agents[turn % 2];
    425 	struct agent_state *opponent = agents[(turn + 1) % 2];
    426 
    427 	dbglog("[server] round %zu, to-play: %s, opponent: %s\n",
    428 		turn, hexplayerstr(player->player), hexplayerstr(opponent->player));
    429 
    430 	/* on the first turn for white (i.e. turn 1 when 0-addressed), white
    431 	 * can respond with either a MSG_MOVE, or a MSG_SWAP, but for all
    432 	 * other turns (for both black and white), only a MSG_MOVE can be
    433 	 * played, thus implementing the swap rule.
    434 	 */
    435 	enum hex_msg_type expected_msg_types[] = { HEX_MSG_MOVE, HEX_MSG_SWAP, };
    436 	size_t expected_msg_types_len = (turn == 1) ? 2 : 1;
    437 
    438 	struct hex_msg msg;
    439 
    440 	if ((err = recv_msg(player, &msg, expected_msg_types, expected_msg_types_len))) {
    441 		*winner = opponent->player;
    442 		return err;
    443 	}
    444 
    445 	switch (msg.type) {
    446 	case HEX_MSG_MOVE:
    447 		dbglog("[server] %s made move (%u,%u)\n",
    448 			hexplayerstr(player->player), msg.data.move.board_x, msg.data.move.board_y);
    449 
    450 		if (!board_play(state->board, player->player, msg.data.move.board_x, msg.data.move.board_y)) {
    451 			*winner = opponent->player;
    452 			return HEX_ERROR_BAD_MOVE;
    453 		}
    454 
    455 		if (board_completed(state->board, winner)) {
    456 			board_print(state->board);
    457 			return HEX_ERROR_GAME_OVER;
    458 		}
    459 		break;
    460 
    461 	case HEX_MSG_SWAP:
    462 		dbglog("[server] %s swapped board\n", hexplayerstr(player->player));
    463 
    464 		board_swap(state->board);
    465 		break;
    466 	}
    467 
    468 	if ((err = send_msg(opponent, &msg, false))) {
    469 		*winner = player->player;
    470 		return err;
    471 	}
    472 
    473 	board_print(state->board);
    474 
    475 	return HEX_ERROR_OK;
    476 }