From b5fb1fcaa6845e57a164718736d4b73660ad6cb1 Mon Sep 17 00:00:00 2001 From: Jean-loup Gailly Date: Fri, 5 Mar 2010 13:58:53 +0100 Subject: [PATCH] Support redirecting stdin, stdout & stderr to sockets. --- Makefile | 2 +- network.c | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ network.h | 11 ++++ zzgo.c | 61 +++++++++++++----- 4 files changed, 274 insertions(+), 18 deletions(-) create mode 100644 network.c create mode 100644 network.h diff --git a/Makefile b/Makefile index 979363a..e9f8af2 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ unexport INCLUDES INCLUDES=-I. -OBJS=board.o gtp.o move.o ownermap.o pattern3.o pattern.o patternsp.o playout.o probdist.o random.o stone.o tactics.o timeinfo.o +OBJS=board.o gtp.o move.o ownermap.o pattern3.o pattern.o patternsp.o playout.o probdist.o random.o stone.o tactics.o timeinfo.o network.o SUBDIRS=random replay patternscan montecarlo uct uct/policy playout t-unit all: all-recursive zzgo diff --git a/network.c b/network.c new file mode 100644 index 0000000..a229099 --- /dev/null +++ b/network.c @@ -0,0 +1,218 @@ +/* Utility functions to redirect stdin, stdout, stderr to sockets. */ + +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "util.h" + +#define STDIN 0 +#define STDOUT 1 +#define STDERR 2 + +#define BSIZE 4096 + +static inline void +die(char *msg) +{ + perror(msg); + exit(42); +} + +/* Create a socket, bind to it on the given port and listen. + * This function is restricted to server mode (port has + * no hostname). Returns the socket. */ +int +port_listen(char *port, int max_connections) +{ + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) + die("socket"); + + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(atoi(port)); + server_addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) + die("bind"); + if (listen(sock, max_connections) == -1) + die("listen"); + return sock; +} + +/* Returns true if in private address range: 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 */ +static bool +is_private(struct in_addr *in) +{ + return (ntohl(in->s_addr) & 0xff000000) >> 24 == 10 + || (ntohl(in->s_addr) & 0xfff00000) >> 16 == 172 * 256 + 16 + || (ntohl(in->s_addr) & 0xffff0000) >> 16 == 192 * 256 + 168; +} + +/* Waits for a connection on the given socket, and returns the file descriptor. + * Updates the client address if it is not null. + * WARNING: the connection is not authenticated. As a weak security measure, + * the connections are limited to a private network. */ +int +open_server_connection(int socket, struct in_addr *client) +{ + assert(socket >= 0); + for (;;) { + struct sockaddr_in client_addr; + int sin_size = sizeof(struct sockaddr_in); + int fd = accept(socket, (struct sockaddr *)&client_addr, (socklen_t *)&sin_size); + if (fd == -1) { + die("accept"); + } + if (is_private(&client_addr.sin_addr)) { + if (client) + *client = client_addr.sin_addr; + return fd; + } + close(fd); + } +} + +/* Opens a new connection to the given port name, which must + * contain a host name. Returns the open file descriptor, + * or -1 if the open fails. */ +static int +open_client_connection(char *port_name) +{ + char hostname[BSIZE]; + strncpy(hostname, port_name, sizeof(hostname)); + char *port = strchr(hostname, ':'); + assert(port); + *port++ = '\0'; + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) + die("socket"); + struct hostent *host = gethostbyname(hostname); + struct sockaddr_in sin; + memcpy(&sin.sin_addr.s_addr, host->h_addr, host->h_length); + sin.sin_family = AF_INET; + sin.sin_port = htons(atoi(port)); + + if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + close(sock); + return -1; + } + return sock; +} + +/* Allow connexion queue > 1 to avoid race conditions. */ +#define MAX_CONNEXIONS 5 + +struct port_info { + int socket; + char *port; +}; + +/* Wait at most 30s between connection attempts. */ +#define MAX_WAIT 30 + +/* Open a connection on the given socket/port. + * Act as server if the port doesn't contain a hostname, + * as a client otherwise. If socket < 0 or in client mode, + * create the socket from the given port and update socket. + * Block until the connection succeeds. + * Return a file descriptor for the new connection. */ +static int +open_connection(struct port_info *info) +{ + int conn; + char *p = strchr(info->port, ':'); + if (p) { + for (int try = 1;; ) { + conn = open_client_connection(info->port); + if (conn >= 0) break; + sleep(try); + if (try < MAX_WAIT) try++; + } + info->socket = conn; + } else { + if (info->socket < 0) + info->socket = port_listen(info->port, MAX_CONNEXIONS); + conn = open_server_connection(info->socket, NULL); + } + return conn; +} + +/* Open the log connection on the given port, redirect stderr to it. */ +static void +open_log_connection(struct port_info *info) +{ + int log_conn = open_connection(info); + if (dup2(log_conn, STDERR) < 0) + die("dup2"); + if (DEBUGL(0)) + fprintf(stderr, "log connection opened\n"); +} + +/* Thread keeping the log connection open and redirecting stderr to it. + * It also echoes its input, which can be used to check if the + * program is alive. As a weak identity check, in server mode the input + * must start with "Pachi" (without the quotes). */ +static void * +log_thread(void *arg) +{ + struct port_info *info = arg; + assert(info && info->port); + for (;;) { + char buf[BSIZE]; + int size; + bool check = !strchr(info->port, ':'); + if (!check) + write(STDERR, "Pachi\n", 6); + while ((size = read(STDERR, buf, BSIZE)) > 0) { + if (check && strncasecmp(buf, "Pachi", 5)) break; + check = false; + write(STDERR, buf, size); + } + fflush(stderr); + open_log_connection(info); + } +} + +/* Open the log connection on the given port, redirect stderr to it, + * and keep reopening it if the connection is closed. */ +void +open_log_port(char *port) +{ + pthread_t thread; + static struct port_info log_info = { .socket = -1 }; + log_info.port = port; + open_log_connection(&log_info); + + /* From now on, log_info may only be modified by the single + * log_thread so static allocation is ok and there is no race. */ + pthread_create(&thread, NULL, log_thread, (void *)&log_info); +} + +/* Open the gtp connection on the given port, redirect stdin & stdout to it. */ +void +open_gtp_connection(int *socket, char *port) +{ + static struct port_info gtp_info = { .socket = -1 }; + gtp_info.port = port; + int gtp_conn = open_connection(>p_info); + for (int d = STDIN; d <= STDOUT; d++) { + if (dup2(gtp_conn, d) < 0) + die("dup2"); + } + if (DEBUGL(0)) + fprintf(stderr, "gtp connection opened\n"); +} diff --git a/network.h b/network.h new file mode 100644 index 0000000..602e3f7 --- /dev/null +++ b/network.h @@ -0,0 +1,11 @@ +#ifndef ZZGO_NETWORK_H +#define ZZGO_NETWORK_H + +#include + +int port_listen(char *port, int max_connections); +int open_server_connection(int socket, struct in_addr *client); +void open_log_port(char *port); +void open_gtp_connection(int *socket, char *port); + +#endif diff --git a/zzgo.c b/zzgo.c index 611535c..4128153 100644 --- a/zzgo.c +++ b/zzgo.c @@ -20,6 +20,7 @@ #include "timeinfo.h" #include "random.h" #include "version.h" +#include "network.h" int debug_level = 3; int seed; @@ -58,6 +59,14 @@ static void done_engine(struct engine *e) free(e); } +static void usage(char *name) +{ + fprintf(stderr, "Pachi version %s\n", PACHI_VERSION); + fprintf(stderr, "Usage: %s [-e random|replay|patternscan|montecarlo|uct]\n" + " [-d DEBUG_LEVEL] [-s RANDOM_SEED] [-t TIME_SETTINGS] [-u TEST_FILENAME]" + " [-g [HOST:]GTP_PORT] [-l [HOST:]LOG_PORT] [ENGINE_ARGS]\n", name); +} + bool engine_reset = false; @@ -66,11 +75,14 @@ int main(int argc, char *argv[]) enum engine_id engine = E_UCT; struct time_info ti_default = { .period = TT_NULL }; char *testfile = NULL; + char *gtp_port = NULL; + char *log_port = NULL; + int gtp_sock = -1; seed = time(NULL) ^ getpid(); int opt; - while ((opt = getopt(argc, argv, "e:d:s:t:u:")) != -1) { + while ((opt = getopt(argc, argv, "e:d:g:l:s:t:u:")) != -1) { switch (opt) { case 'e': if (!strcasecmp(optarg, "random")) { @@ -91,6 +103,12 @@ int main(int argc, char *argv[]) case 'd': debug_level = atoi(optarg); break; + case 'g': + gtp_port = strdup(optarg); + break; + case 'l': + log_port = strdup(optarg); + break; case 's': seed = atoi(optarg); break; @@ -114,13 +132,14 @@ int main(int argc, char *argv[]) testfile = strdup(optarg); break; default: /* '?' */ - fprintf(stderr, "Pachi version %s\n", PACHI_VERSION); - fprintf(stderr, "Usage: %s [-e random|replay|patternscan|montecarlo|uct] [-d DEBUG_LEVEL] [-s RANDOM_SEED] [-t TIME_SETTINGS] [-u TEST_FILENAME] [ENGINE_ARGS]\n", - argv[0]); + usage(argv[0]); exit(1); } } + if (log_port) + open_log_port(log_port); + fast_srandom(seed); if (DEBUGL(0)) fprintf(stderr, "Random seed: %d\n", seed); @@ -140,21 +159,29 @@ int main(int argc, char *argv[]) return 0; } - char buf[4096]; - while (fgets(buf, 4096, stdin)) { - if (DEBUGL(1)) - fprintf(stderr, "IN: %s", buf); - gtp_parse(b, e, ti, buf); - if (engine_reset) { - if (!e->keep_on_clear) { - b->es = NULL; - done_engine(e); - e = init_engine(engine, e_arg, b); - ti[S_BLACK] = ti_default; - ti[S_WHITE] = ti_default; + if (gtp_port) { + open_gtp_connection(>p_sock, gtp_port); + } + + for (;;) { + char buf[4096]; + while (fgets(buf, 4096, stdin)) { + if (DEBUGL(1)) + fprintf(stderr, "IN: %s", buf); + gtp_parse(b, e, ti, buf); + if (engine_reset) { + if (!e->keep_on_clear) { + b->es = NULL; + done_engine(e); + e = init_engine(engine, e_arg, b); + ti[S_BLACK] = ti_default; + ti[S_WHITE] = ti_default; + } + engine_reset = false; } - engine_reset = false; } + if (!gtp_port) break; + open_gtp_connection(>p_sock, gtp_port); } done_engine(e); return 0; -- 2.11.4.GIT