catan

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

server.c (6747B)


      1 #include "server.h"
      2 
      3 struct opts opts = {
      4 	.verbose = false,
      5 	.game_seed = 0,
      6 	.agent_threads = 4,
      7 	.agent_mem_mib = 1024,
      8 	.agent_timeout = 300,
      9 	.agents = {0},
     10 };
     11 
     12 static char const *optstr = "hvg:t:m:s:";
     13 
     14 static void
     15 usage(char **argv)
     16 {
     17 	fprintf(stderr, "Usage: %s [-hv] [-g <game-seed>] [-t <agent-thread-limit] [-m <agent-mem-limit-mib>] [-s <agent-timeout>] <uid:agent>...\n", argv[0]);
     18 	fprintf(stderr, "\t-h :\n\t\tdisplay this help message\n");
     19 	fprintf(stderr, "\t-v :\n\t\tenable verbose logging\n");
     20 	fprintf(stderr, "\t-g :\n\t\tset the seed for the generated game board\n");
     21 	fprintf(stderr, "\t-t :\n\t\tset the per-agent thread limit (default: 4)\n");
     22 	fprintf(stderr, "\t-m :\n\t\tset the per-agent memory limit in MiB (default: 1024 MiB)\n");
     23 	fprintf(stderr, "\t-s :\n\t\tset the per-agent timeout (default 300 secs)\n");
     24 	fprintf(stderr, "\n");
     25 	fprintf(stderr, "\tagent :\n\t\tadds the given agent, with the given uid (between %d and %d times)\n", GAME_AGENTS_MIN, GAME_AGENTS_MAX);
     26 	fprintf(stderr, "\n");
     27 	fprintf(stderr, "\tNOTE: agent programs must expect to receive the following commandline arguments:\n");
     28 	fprintf(stderr, "\t\t1. host :\n\t\t\tsocket host to connect to\n");
     29 	fprintf(stderr, "\t\t2. port :\n\t\t\tsocket port to connect to\n");
     30 }
     31 
     32 #include <getopt.h>
     33 
     34 static bool
     35 parse_args(int argc, char **argv)
     36 {
     37 	int opt;
     38 	while ((opt = getopt(argc, argv, optstr)) != -1) {
     39 		switch (opt) {
     40 		case 'v':
     41 			opts.verbose = true;
     42 			break;
     43 
     44 		case 'g':
     45 			if (!(opts.game_seed = strtoull(optarg, NULL, 10)))
     46 				return false;
     47 			break;
     48 
     49 		case 't':
     50 			if (!(opts.agent_threads = strtoull(optarg, NULL, 10)))
     51 				return false;
     52 			break;
     53 
     54 		case 'm':
     55 			if (!(opts.agent_mem_mib = strtoull(optarg, NULL, 10)))
     56 				return false;
     57 			break;
     58 
     59 		case 's':
     60 			if (!(opts.agent_timeout = strtoull(optarg, NULL, 10)))
     61 				return false;
     62 			break;
     63 
     64 		default:
     65 			return false;
     66 		}
     67 	}
     68 
     69 	opts.agents.ptr = argv + optind;
     70 	opts.agents.len = argc - optind;
     71 
     72 	if (opts.agents.len < GAME_AGENTS_MIN || opts.agents.len > GAME_AGENTS_MAX)
     73 		return false;
     74 
     75 	return true;
     76 }
     77 
     78 int
     79 main(int argc, char **argv)
     80 {
     81 	if (!parse_args(argc, argv)) {
     82 		usage(argv);
     83 		exit(EXIT_FAILURE);
     84 	};
     85 
     86 	fprintf(stderr, "====== Args ======\n");
     87 	fprintf(stderr, " verbose: %d\n", opts.verbose);
     88 	fprintf(stderr, " game seed: %" PRIu32 "\n", opts.game_seed);
     89 	fprintf(stderr, " agent threads: %" PRIu32 "\n", opts.agent_threads);
     90 	fprintf(stderr, " agent mem MiB: %" PRIu32 "\n", opts.agent_mem_mib);
     91 	fprintf(stderr, " agent timeout: %" PRIu32 " secs\n", opts.agent_timeout);
     92 	fprintf(stderr, " agents: %" PRIuMAX "\n", opts.agents.len);
     93 	for (size_t i = 0; i < opts.agents.len; i++)
     94 		fprintf(stderr, "\tagent %zu: %s\n", i, opts.agents.ptr[i]);
     95 
     96 	struct server_state server;
     97 	if (!server_init(&server)) {
     98 		fprintf(stderr, "Failed to initialise server\n");
     99 		exit(EXIT_FAILURE);
    100 	}
    101 
    102 	struct agent_state agents[opts.agents.len];
    103 	for (size_t i = 0; i < opts.agents.len; i++) {
    104 		char *agent = opts.agents.ptr[i];
    105 
    106 		char *saveptr;
    107 		char *raw_uid = strtok_r(agent, ":", &saveptr);
    108 		char *cmd = strtok_r(NULL, "\n", &saveptr);
    109 
    110 		if (!cmd) {
    111 			fprintf(stderr, "Bad agent string: %s\n", agent);
    112 			exit(EXIT_FAILURE);
    113 		}
    114 
    115 		uid_t uid = strtoul(raw_uid, NULL, 10);
    116 		if (!uid) {
    117 			fprintf(stderr, "Bad uid given: %s\n", raw_uid);
    118 			exit(EXIT_FAILURE);
    119 		}
    120 
    121 		if (!server_start_agent(&server, &agents[i], i, cmd, uid)) {
    122 			fprintf(stderr, "Failed to start agent %zu, '%s' as uid %d\n", i, cmd, uid);
    123 			exit(EXIT_FAILURE);
    124 		}
    125 	}
    126 
    127 	server_run(&server, agents, ARRLEN(agents));
    128 
    129 	for (size_t i = 0; i < opts.agents.len; i++) {
    130 		server_wait_agent(&agents[i]);
    131 	}
    132 
    133 	exit(EXIT_SUCCESS);
    134 }
    135 
    136 bool
    137 server_init(struct server_state *server)
    138 {
    139 	struct addrinfo hints = {
    140 		.ai_flags = AI_PASSIVE | AI_NUMERICSERV,
    141 		.ai_family = AF_UNSPEC,
    142 		.ai_socktype = SOCK_STREAM,
    143 	}, *addrinfo, *ptr;
    144 
    145 	int res;
    146 	if ((res = getaddrinfo(NULL, "0", &hints, &addrinfo))) {
    147 		fprintf(stderr, "Failed to get address info for server socket: %s\n", gai_strerror(res));
    148 		return false;
    149 	}
    150 
    151 	int fd;
    152 	for (ptr = addrinfo; ptr; ptr = ptr->ai_next) {
    153 		fd = socket(ptr->ai_family, ptr->ai_socktype | SOCK_CLOEXEC, ptr->ai_protocol);
    154 		if (fd == -1)
    155 			continue;
    156 
    157 		if (bind(fd, ptr->ai_addr, ptr->ai_addrlen) == -1) {
    158 			perror("bind");
    159 			close(fd);
    160 			continue;
    161 		}
    162 
    163 		if (listen(fd, GAME_AGENTS_MAX) == -1) {
    164 			perror("listen");
    165 			close(fd);
    166 			continue;
    167 		}
    168 
    169 		break;
    170 	}
    171 
    172 	if (ptr) {
    173 		server->sockfd = fd;
    174 
    175 		struct sockaddr_storage addr;
    176 		socklen_t addrlen = sizeof addr;
    177 		getsockname(fd, (struct sockaddr *) &addr, &addrlen);
    178 
    179 		getnameinfo((struct sockaddr *) &addr, addrlen,
    180 			    server->addr, ARRLEN(server->addr),
    181 			    server->port, ARRLEN(server->port),
    182 			    AI_NUMERICHOST | AI_NUMERICSERV);
    183 
    184 		fprintf(stderr, "Hosting server on %s:%s\n", server->addr, server->port);
    185 	}
    186 
    187 	freeaddrinfo(addrinfo);
    188 
    189 	if (!ptr) {
    190 		fprintf(stderr, "Failed to bind server socket\n");
    191 		return false;
    192 	}
    193 
    194 	// TODO: initialise board
    195 
    196 	return true;
    197 }
    198 
    199 bool
    200 server_start_agent(struct server_state *server, struct agent_state *agent,
    201 		   uint8_t id, char *cmd, uid_t uid)
    202 {
    203 	pid_t pid = fork();
    204 	if (pid == -1) {
    205 		perror("fork");
    206 		return false;
    207 	} else if (pid == 0) /* child */ {
    208 		struct rlimit nproc_rlimit = {
    209 			.rlim_cur = opts.agent_threads,
    210 			.rlim_max = opts.agent_threads,
    211 		};
    212 
    213 		prlimit(getpid(), RLIMIT_NPROC, &nproc_rlimit, NULL);
    214 
    215 		struct rlimit data_rlimit = {
    216 			.rlim_cur = opts.agent_mem_mib * MiB,
    217 			.rlim_max = opts.agent_mem_mib * MiB,
    218 		};
    219 
    220 		prlimit(getpid(), RLIMIT_DATA, &data_rlimit, NULL);
    221 
    222 		setuid(uid);
    223 
    224 		char *args[] = {
    225 			cmd,
    226 			server->addr,
    227 			server->port,
    228 			NULL,
    229 		};
    230 
    231 		char *envp[] = { NULL, };
    232 
    233 		execvpe(cmd, args, envp);
    234 
    235 		perror("execv");
    236 		exit(EXIT_FAILURE);
    237 	}
    238 
    239 	/* parent */
    240 	agent->id = id;
    241 	agent->pid = pid;
    242 
    243 	struct pollfd acceptfd = {
    244 		.fd = server->sockfd,
    245 		.events = POLLIN,
    246 	};
    247 
    248 	int res = poll(&acceptfd, 1, AGENT_ACCEPT_TIMEOUT_MS);
    249 	if (res <= 0) {
    250 		fprintf(stderr, "Failed to accept agent: %s, timeout\n", cmd);
    251 		return false;
    252 	}
    253 
    254 	agent->sockfd = accept4(server->sockfd, NULL, NULL, SOCK_CLOEXEC);
    255 	if (agent->sockfd < 0) {
    256 		fprintf(stderr, "Failed to accept agent: %s\n", cmd);
    257 		return false;
    258 	}
    259 
    260 	return true;
    261 }
    262 
    263 void
    264 server_wait_agent(struct agent_state *agent)
    265 {
    266 	kill(agent->pid, SIGKILL);
    267 
    268 	int stats;
    269 	if (waitpid(agent->pid, &stats, WNOHANG) == -1) {
    270 		kill(agent->pid, SIGTERM);
    271 
    272 		waitpid(agent->pid, &stats, 0);
    273 	}
    274 }
    275 
    276 void
    277 server_run(struct server_state *server, struct agent_state *agents, size_t len)
    278 {
    279 	(void) server;
    280 	(void) agents;
    281 	(void) len;
    282 
    283 	// TODO: send begin message
    284 	// TODO: send setup message
    285 	// TODO: start turns
    286 	// TODO: play until an agent wins
    287 	// TODO: send end message
    288 }