socsvn commit: r289926 - in soc2015/roam: . ayiya_listen ayiya_resp ng_ayiya

roam at FreeBSD.org roam at FreeBSD.org
Wed Aug 19 15:56:19 UTC 2015


Author: roam
Date: Wed Aug 19 15:56:16 2015
New Revision: 289926
URL: http://svnweb.FreeBSD.org/socsvn/?view=rev&rev=289926

Log:
  Add ayiya_listen for server-side ng_ayiya use.
  
  Add a daemon that listens for UDP packets coming in for port 5072,
  figures out which of the pre-configured tunnels the packet is for,
  then sets up the ng_ayiya/ng_ksocket/ng_iface set and starts
  an instance of ayiya_resp to inject the packet and then handle any
  incoming AYIYA queries.
  
  ayiya_resp:
  - add NO_WCAST_ALIGN to the Makefile; I do believe that most of
    the casts should be safe, even though the compiler does not
    realize that
  - add the -i inputfd command-line option to specify a fd for
    packets that have arrived before the ng_ayiya node was set up
  - inject these packets using ng_ayiya's new "inject from AYIYA"
    control message
  
  ng_ayiya:
  - add the "inject from AYIYA" control message with a data packet
    that must be processed as if it was received from the "ayiya"
    hook

Added:
  soc2015/roam/ayiya_listen/
  soc2015/roam/ayiya_listen/Makefile
     - copied, changed from r289925, soc2015/roam/ayiya_resp/Makefile
  soc2015/roam/ayiya_listen/main.c
Modified:
  soc2015/roam/Makefile
  soc2015/roam/ayiya_resp/Makefile
  soc2015/roam/ayiya_resp/main.c
  soc2015/roam/ng_ayiya/ng_ayiya.c
  soc2015/roam/ng_ayiya/ng_ayiya.h

Modified: soc2015/roam/Makefile
==============================================================================
--- soc2015/roam/Makefile	Wed Aug 19 15:56:09 2015	(r289925)
+++ soc2015/roam/Makefile	Wed Aug 19 15:56:16 2015	(r289926)
@@ -22,12 +22,12 @@
 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 # SUCH DAMAGE.
 
-SUBDIR=		ayiya_resp ng_ayiya
+SUBDIR=		ayiya_listen ayiya_resp ng_ayiya
 
 .include <bsd.subdir.mk>
 
 NAME=		ng_ayiya
-VERSION=	0.1.0.dev210
+VERSION=	0.1.0.dev225
 
 DISTNAME=	${NAME}-${VERSION}
 DISTDIR=	${DISTNAME}

Copied and modified: soc2015/roam/ayiya_listen/Makefile (from r289925, soc2015/roam/ayiya_resp/Makefile)
==============================================================================
--- soc2015/roam/ayiya_resp/Makefile	Wed Aug 19 15:56:09 2015	(r289925, copy source)
+++ soc2015/roam/ayiya_listen/Makefile	Wed Aug 19 15:56:16 2015	(r289926)
@@ -22,12 +22,13 @@
 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 # SUCH DAMAGE.
 
-PROG=	ayiya_resp
+PROG=	ayiya_listen
 SRCS=	main.c
 NO_MAN=	yes
 WARNS?=	9
-DPADD=	${LIBNETGRAPH}
-LDADD=	-lnetgraph
+NO_WCAST_ALIGN=	1
+DPADD=	${LIBMD} ${LIBNETGRAPH}
+LDADD=	-lmd -lnetgraph
 
 CFLAGS+=	-I${.CURDIR}/..
 

Added: soc2015/roam/ayiya_listen/main.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ soc2015/roam/ayiya_listen/main.c	Wed Aug 19 15:56:16 2015	(r289926)
@@ -0,0 +1,880 @@
+/*-
+ * Copyright (c) 2015  Peter Pentchev
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define _WITH_GETLINE
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <regex.h>
+#include <sha.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <netgraph.h>
+#include <netgraph/ng_iface.h>
+#include <netgraph/ng_ksocket.h>
+#include <ng_ayiya/ng_ayiya.h>
+
+#define MAX_TUNNELS	4
+#define MAX_RESPAWNS	3
+#define INET_ADDRPORTSTRLEN	(INET_ADDRSTRLEN + 1 + 5)
+
+struct tunnel {
+	char		id[16 + 1];
+	struct in6_addr	local_addr, remote_addr;
+	char		password[20];
+	unsigned	prefix_len;
+	unsigned mtu;
+
+	char		remote_outer[INET_ADDRPORTSTRLEN];
+	int		comm[2];
+	pid_t		pid;
+	ng_ID_t		node_id;
+	unsigned	respawned;
+};
+
+static struct tunnel	tunnels[MAX_TUNNELS];
+static size_t		tunnels_count;
+
+static struct sockaddr_in	local;
+static int		ng_cs, ng_ds;
+static const char	*resppath;
+
+static int		quiet;
+static int		verbose;
+
+static void		usage(int _ferr);
+static void		version(void);
+static void		debug(const char *fmt, ...) __printflike(1, 2);
+
+static void		parse_tunnels(const char *);
+static int		setup_listener(const char *);
+static void		accept_connection(int);
+static void		handle_tunnel(struct tunnel *, const char *,
+			const struct sockaddr_in *, const char *, size_t);
+static void		stop_tunnel(struct tunnel *, int);
+static void		start_tunnel(struct tunnel *,
+			const struct sockaddr_in *, const char *);
+static void		check_wait_result(pid_t res, int stat, pid_t expected,
+			const char *progname);
+static void		spawn_child(const struct tunnel *) __dead2;
+static int		inet_addrport(const struct sockaddr_in *,
+			char *, size_t);
+static void		get_ayiya_node(char *, size_t, const struct tunnel *,
+			const char *);
+static void		sigchld_handler(int);
+
+static void		report_regerror(int, const regex_t * restrict);
+
+int
+main(int argc, char * const argv[])
+{
+	int ch, hflag, Vflag;
+	const char *addr, *tunnelsfile;
+
+	hflag = Vflag = 0;
+	addr = tunnelsfile = NULL;
+	while (ch = getopt(argc, argv, "a:hqr:t:Vv"), ch != -1)
+		switch (ch) {
+			case 'a':
+				if (addr != NULL)
+					errx(1, "Duplicate address specified");
+				addr = optarg;
+				break;
+
+			case 'h':
+				hflag = 1;
+				break;
+
+			case 'q':
+				quiet = 1;
+				break;
+
+			case 'r':
+				resppath = optarg;
+				break;
+
+			case 't':
+				tunnelsfile = optarg;
+				break;
+
+			case 'V':
+				Vflag = 1;
+				break;
+
+			case 'v':
+				verbose++;
+				break;
+
+			default:
+				usage(1);
+				/* NOTREACHED */
+		}
+	if (Vflag)
+		version();
+	if (hflag)
+		usage(0);
+	if (Vflag || hflag)
+		return (0);
+
+	if (addr == NULL) {
+		warnx("No address (-a) specified");
+		usage(1);
+	} else if (tunnelsfile == NULL) {
+		warnx("No tunnels file (-t) specified");
+		usage(1);
+	} else if (resppath == NULL) {
+		warnx("No ayiya_resp path (-r) specified");
+		usage(1);
+	}
+
+	argc -= optind;
+	argv += optind;
+	if (argc > 0) {
+		warnx("No positional arguments expected");
+		usage(1);
+	}
+
+	parse_tunnels(tunnelsfile);
+
+	signal(SIGPIPE, SIG_IGN);
+	const struct sigaction sa = {
+		.sa_handler = sigchld_handler,
+		.sa_flags = SA_NOCLDSTOP | SA_RESETHAND,
+	};
+	sigaction(SIGCHLD, &sa, NULL);
+
+	const int fd = setup_listener(addr);
+	while (1)
+		accept_connection(fd);
+	/* NOTREACHED */
+}
+
+void
+usage(const int _ferr)
+{
+	const char * const s =
+	    "Usage:\tayiya_listen [-v] -a address -r resppath -t tunnelsfile config\n"
+	    "\tayiya_listen [-qv] loop\n"
+	    "\tayiya_listen -V | -h\n"
+	    "\n"
+	    "\t-a\tspecify the IPv4 address to listen on\n"
+	    "\t-h\tdisplay program usage information and exit\n"
+	    "\t-n\tspecify the name of the AYIYA node to accept the connection\n"
+	    "\t-q\tquiet mode; only respond, do not originate AYIYA packets\n"
+	    "\t-r\tspecify the path to the ayiya_resp executable\n"
+	    "\t-t\tspecify the path to the tunnel information file\n"
+	    "\t-V\tdisplay program version information and exit\n"
+	    "\t-v\tverbose operation; display diagnostic output\n";
+
+	fprintf(_ferr? stderr: stdout, "%s", s);
+	if (_ferr)
+		exit(1);
+}
+
+void
+version(void)
+{
+	puts("ayiya_resp 0.1.0.dev225");
+}
+
+void
+debug(const char * const fmt, ...)
+{
+	va_list v;
+
+	va_start(v, fmt);
+	if (verbose)
+		vfprintf(stderr, fmt, v);
+	va_end(v);
+}
+
+int
+setup_listener(const char * const addr)
+{
+	assert(addr != NULL);
+
+	if (NgMkSockNode("ayiya_listen", &ng_cs, &ng_ds) == -1)
+		err(1, "Could not create the Netgraph socket node");
+
+	struct addrinfo hints;
+	bzero(&hints, sizeof(hints));
+	hints.ai_family = AF_INET;
+	hints.ai_socktype = SOCK_DGRAM;
+	hints.ai_protocol = IPPROTO_UDP;
+	hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_PASSIVE;
+	struct addrinfo *res;
+	const int ares = getaddrinfo(addr, "5072", &hints, &res);
+	if (ares != 0)
+		errx(1, "Could not parse the '%s' address",
+		    gai_strerror(ares));
+	if (res == NULL)
+		errx(1, "Invalid address '%s' specified", addr);
+
+	for (; res != NULL; res = res->ai_next) {
+		const int fd = socket(res->ai_family, res->ai_socktype,
+		    res->ai_protocol);
+		if (fd == -1) {
+			warn("Could not create a %d/%d/%d socket",
+			    res->ai_family, res->ai_socktype,
+			    res->ai_protocol);
+			continue;
+		}
+		const int optval = 1;
+		if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT,
+		    &optval, sizeof(optval)) == -1) {
+			warn("Could not set the 'reuse port' socket option");
+			close(fd);
+			continue;
+		}
+		if (bind(fd, res->ai_addr, res->ai_addrlen) == -1) {
+			warn("Could not bind to %s", addr);
+			close(fd);
+			continue;
+		}
+
+		debug("Looks like we've set up the socket\n");
+		bcopy(res->ai_addr, &local, sizeof(local));
+		return fd;
+	}
+	errx(1, "Could not set up a listener on %s", addr);
+}
+
+void
+accept_connection(const int fd)
+{
+	static char buf[32768];
+	struct sockaddr_in sin;
+	socklen_t sin_len;
+retry:
+	sin_len = sizeof(sin);
+	ssize_t len = recvfrom(fd, buf, sizeof(buf), 0,
+	    (struct sockaddr *)&sin, &sin_len);
+	if (len == -1) {
+		if (errno == EINTR) {
+			debug("Hm, was a SIGCHLD received?\n");
+			while (1) {
+				int stat;
+				const pid_t pid = waitpid(-1, &stat, WNOHANG);
+				if (pid == -1) {
+					if (errno != ECHILD)
+						err(1, "waitpid() failed");
+					/**
+					 * recvfrom() was not interrupted by
+					 * a SIGCHLD?  Ah well, never mind :)
+					 */
+					break;
+				} else if (pid == 0) {
+					break;
+				}
+				for (size_t idx = 0; idx < tunnels_count; idx++) {
+					struct tunnel * const t =
+					    tunnels + idx;
+					if (t->pid == pid) {
+						check_wait_result(pid, stat,
+						    t->pid, "AYIYA responder");
+						stop_tunnel(t, 1);
+						break;
+					}
+				}
+			}
+			goto retry;
+		}
+		err(1, "Could not receive an incoming message");
+	} else if (len < 1) {
+		errx(1, "Short read on an incoming message");
+	}
+
+	char sender[INET_ADDRPORTSTRLEN];
+	if (!inet_addrport(&sin, sender, sizeof(sender)))
+		return;
+	debug("Received a message from %s\n", sender);
+
+	const struct ng_ayiya_header * const ay =
+	    (const struct ng_ayiya_header *)buf;
+	if (ayiya_length_id(ay) != 16) {
+		warnx("Invalid AYIYA identity header in the message");
+		return;
+	}
+	const struct in6_addr * const addr =
+	    (const struct in6_addr *)(buf + ayiya_offset_id(ay));
+	char id[INET6_ADDRSTRLEN];
+	if (inet_ntop(AF_INET6, addr, id, sizeof(id)) == NULL) {
+		warn("Could not convert the AYIYA identity to an IPv6 address string");
+		return;
+	}
+	debug("- AYIYA sender identity: %s\n", id);
+
+	struct tunnel *t;
+	for (t = tunnels; t < tunnels + tunnels_count; t++)
+		if (bcmp(addr, &t->remote_addr, sizeof(addr)) == 0) {
+			handle_tunnel(t, sender, &sin, buf, len);
+			return;
+		}
+	debug("- unknown sender, ignoring the packet\n");
+}
+
+void
+handle_tunnel(struct tunnel * const t, const char * const sender,
+	const struct sockaddr_in * const sin,
+	const char * const buf, const size_t len)
+{
+	/* Do we need to kill a tunnel first? */
+	if (strcmp(t->remote_outer, sender) != 0) {
+		stop_tunnel(t, 0);
+		snprintf(t->remote_outer, sizeof(t->remote_outer), "%s", sender);
+	}
+
+	if (t->pid == 0) {
+		/* Silently ignore the packet if too many respanws. */
+		if (t->respawned > MAX_RESPAWNS)
+			return;
+
+		debug("Starting a new tunnel instance for %s\n", t->id);
+		start_tunnel(t, sin, sender);
+	}
+
+	debug("Sending a packet of size %zu to process %jd for %s\n",
+	    len, (intmax_t)t->pid, t->id);
+	const uint16_t slen = len;
+	size_t written = 0;
+	while (written < sizeof(slen) + len) {
+		ssize_t n;
+		if (written < sizeof(slen))
+			n = write(t->comm[1], ((const char *)&slen) + written,
+			    sizeof(slen) - written);
+		else
+			n = write(t->comm[1], buf + written - sizeof(slen),
+			    len - written + sizeof(slen));
+		if (n < 1) {
+			/* Oof, kill and respawn the child process. */
+			debug("Could not send the packet to child process %jd, respawning the child\n",
+			    (intmax_t)t->pid);
+			bzero(&t->remote_outer, sizeof(t->remote_outer));
+			t->respawned++;
+			handle_tunnel(t, sender, sin, buf, len);
+			return;
+		}
+
+		written += n;
+	}
+	debug("Packet sent successfully\n");
+}
+
+void
+stop_tunnel(struct tunnel * const t, const int waited)
+{
+	if (t->pid != 0) {
+		close(t->comm[1]);
+		if (!waited) {
+			kill(t->pid, SIGTERM);
+			int stat;
+			const pid_t wpid = waitpid(t->pid, &stat, 0);
+			check_wait_result(wpid, stat, t->pid, "AYIYA responder");
+		}
+		t->pid = 0;
+	}
+
+	/**
+	 * Always try to shut the Netgraph nodes down, no matter if
+	 * we created them a while ago or they were there before
+	 * we started.
+	 */
+	char path[NG_PATHSIZ];
+	get_ayiya_node(path, sizeof(path), t, ":inet6");
+	debug("Shutting down the ng_iface node %s\n", path);
+	if (NgSendMsg(ng_cs, path, NGM_GENERIC_COOKIE,
+	    NGM_SHUTDOWN, NULL, 0) == -1 && errno != ENOENT)
+		err(1, "Could not shut down the ng_iface node %s",
+		    path);
+
+	get_ayiya_node(path, sizeof(path), t, ":");
+	debug("Shutting down the ng_ayiya node %s\n", path);
+	if (NgSendMsg(ng_cs, path, NGM_GENERIC_COOKIE,
+	    NGM_SHUTDOWN, NULL, 0) == -1 && errno != ENOENT)
+		err(1, "Could not shut down the AYIYA node %s",
+		    path);
+	t->node_id = 0;
+
+	t->respawned = 0;
+}
+
+void
+start_tunnel(struct tunnel * const t, const struct sockaddr_in * const sin,
+		const char * const sender)
+{
+	debug("Creating a new node for %s\n", t->id);
+	assert(t->node_id == 0);
+	struct ngm_mkpeer pa = {
+		.type = "ayiya",
+		.ourhook = "a",
+		.peerhook = "control",
+	};
+	if (NgSendMsg(ng_cs, ".", NGM_GENERIC_COOKIE, NGM_MKPEER,
+	    &pa, sizeof(pa)) == -1)
+		err(1, "Could not create an AYIYA node");
+	char node_name[NG_NODESIZ];
+	get_ayiya_node(node_name, sizeof(node_name), t, "");
+	if (NgNameNode(ng_cs, "a", node_name, t->id) == -1) {
+		warn("Could not set the AYIYA node's name");
+		goto ng_error;
+	}
+
+	static union {
+		struct ng_mesg msg;
+		char buf[2000];
+	} ng_buf;
+	char ng_path[NG_PATHSIZ];
+	if (NgSendMsg(ng_cs, "a", NGM_GENERIC_COOKIE, NGM_NODEINFO,
+	    NULL, 0) == -1) {
+		warn("Could not request the AYIYA node's ID");
+		goto ng_error;
+	}
+	if (NgRecvMsg(ng_cs, &ng_buf.msg, sizeof(ng_buf.buf), ng_path) == -1) {
+		warn("Could not receive the AYIYA node's ID");
+		goto ng_error;
+	}
+	const struct nodeinfo * const info =
+	    (const struct nodeinfo *)ng_buf.msg.data;
+	debug("Created the ng_ayiya node %s [%jx], it seems\n",
+	    node_name, (intmax_t)info->id);
+	t->node_id = info->id;
+
+	struct ngm_mkpeer pk = {
+		.type = "ksocket",
+		.ourhook = "ayiya",
+		.peerhook = "inet/dgram/udp",
+	};
+	if (NgSendMsg(ng_cs, "a", NGM_GENERIC_COOKIE, NGM_MKPEER,
+	    &pk, sizeof(pk)) == -1) {
+		warn("Could not create a ksocket node");
+		goto ng_error;
+	}
+	get_ayiya_node(node_name, sizeof(node_name), t, "_conn");
+	if (NgNameNode(ng_cs, "a.ayiya", node_name, t->id) == -1) {
+		warn("Could not set the ksocket node's name");
+		goto ng_error;
+	}
+	debug("Created the %s ksocket node\n", node_name);
+
+	union {
+		struct ng_ksocket_sockopt ks;
+		char buf[sizeof(struct ng_ksocket_sockopt) + sizeof(uint32_t)];
+	} opt;
+	opt.ks.level = SOL_SOCKET,
+	opt.ks.name = SO_REUSEPORT,
+	*(uint32_t *)&opt.ks.value[0] = 1;
+	if (NgSendMsg(ng_cs, "a.ayiya", NGM_KSOCKET_COOKIE, NGM_KSOCKET_SETOPT,
+	    &opt, sizeof(opt)) == -1) {
+		warn("Could not set the SO_REUSEPORT option on the ksocket node");
+		goto ng_error;
+	}
+
+	if (NgSendMsg(ng_cs, "a.ayiya", NGM_KSOCKET_COOKIE, NGM_KSOCKET_BIND,
+	    &local, sizeof(local)) == -1) {
+		warn("Could not bind the ksocket node to %s:%d",
+		    inet_ntoa(local.sin_addr), htons(local.sin_port));
+		goto ng_error;
+	}
+	if (NgSendMsg(ng_cs, "a.ayiya", NGM_KSOCKET_COOKIE, NGM_KSOCKET_CONNECT,
+	    sin, sizeof(*sin)) == -1) {
+		warn("Could not connect the ksocket node to %s:%d",
+		    inet_ntoa(sin->sin_addr), htons(sin->sin_port));
+		goto ng_error;
+	}
+
+	struct ngm_mkpeer pi = {
+		.type = "iface",
+		.ourhook = "inet6",
+		.peerhook = "inet6",
+	};
+	if (NgSendMsg(ng_cs, "a", NGM_GENERIC_COOKIE, NGM_MKPEER,
+	    &pi, sizeof(pi)) == -1) {
+		warn("Could not create the iface node");
+		goto ng_error;
+	}
+	if (NgSendMsg(ng_cs, "a.inet6", NGM_IFACE_COOKIE, NGM_IFACE_GET_IFNAME,
+	    NULL, 0) == -1) {
+		warn("Could not request the iface node's name");
+		goto ng_error;
+	}
+	if (NgRecvMsg(ng_cs, &ng_buf.msg, sizeof(ng_buf.buf), ng_path) == -1) {
+		warn("Could not receive the iface node's name");
+		goto ng_error;
+	}
+	const char * const ifname = ng_buf.msg.data;
+	debug("Created the %s ng_iface node\n", ifname);
+
+	char local6[INET6_ADDRSTRLEN], remote6[INET6_ADDRSTRLEN];
+	inet_ntop(AF_INET6, &t->local_addr, local6, sizeof(local6));
+	inet_ntop(AF_INET6, &t->remote_addr, remote6, sizeof(remote6));
+	static char cmdbuf[1024];
+	snprintf(cmdbuf, sizeof(cmdbuf),
+	    "ifconfig %s inet6 %s %s prefixlen 128 alias mtu %u",
+	    ifname, local6, remote6, t->mtu);
+	debug("Running %s\n", cmdbuf);
+	const int res = system(cmdbuf);
+	if (res == -1) {
+		warn("Could not run ifconfig for the %s tunnel", t->id);
+		goto ng_error;
+	} else if (!WIFEXITED(res) || WEXITSTATUS(res) != 0) {
+		warnx("Could not configure the %s tunnel using '%s'",
+		    t->id, cmdbuf);
+		goto ng_error;
+	}
+
+	debug("Sending the 'configure' message to the ng_ayiya node\n");
+	if (NgSendMsg(ng_cs, "a", NGM_AYIYA_COOKIE, NGM_AYIYA_SECRETHASH,
+	    t->password, sizeof(t->password)) == -1) {
+		warn("Could not set the AYIYA node's secret hash");
+		goto ng_error;
+	}
+	if (NgSendMsg(ng_cs, "a", NGM_AYIYA_COOKIE, NGM_AYIYA_CONFIGURE,
+	    NULL, 0) == -1) {
+		warn("Could not request the AYIYA node to start up");
+		goto ng_error;
+	}
+	if (NgRecvMsg(ng_cs, &ng_buf.msg, sizeof(ng_buf.buf), ng_path) == -1) {
+		warn("Could not get the AYIYA node's configure response");
+		goto ng_error;
+	}
+	const uint32_t presp = *(const uint32_t *)ng_buf.msg.data;
+	if (presp != 0) {
+		warnc(presp, "The AYIYA node failed to start up");
+		goto ng_error;
+	}
+	debug("Well, well, it looks like we have a running ng_ayiya node!\n");
+
+	struct ngm_rmhook rm = {
+		.ourhook = "a",
+	};
+	if (NgSendMsg(ng_cs, ".", NGM_GENERIC_COOKIE, NGM_RMHOOK,
+	    &rm, sizeof(rm)) == -1) {
+		warn("Could not remove the hook to the AYIYA node");
+		goto ng_error;
+	}
+
+	debug("Spawning a new process for %s\n", t->id);
+	if (pipe(t->comm) == -1)
+		err(1, "Could not create a communications pipe");
+
+	const pid_t pid = fork();
+	if (pid == -1) {
+		err(1, "Could not fork for a %s connection from %s",
+		    t->id, sender);
+	} else if (pid == 0) {
+		close(t->comm[1]);
+		spawn_child(t);
+		/* NOTREACHED */
+	} else {
+		close(t->comm[0]);
+		t->pid = pid;
+	}
+	return;
+
+ng_error:
+	NgSendMsg(ng_cs, "a.inet6", NGM_GENERIC_COOKIE, NGM_SHUTDOWN, NULL, 0);
+	NgSendMsg(ng_cs, "a", NGM_GENERIC_COOKIE, NGM_SHUTDOWN, NULL, 0);
+	t->node_id = 0;
+	exit(1);
+}
+
+void
+check_wait_result(const pid_t pid, const int stat, const pid_t expected,
+	const char * const progname)
+{
+	if (pid != expected) {
+		errx(1, "Waiting for %s: expected pid %ld, got %ld", progname, (long)expected, (long)pid);
+	} else if (WIFEXITED(stat)) {
+		if (WEXITSTATUS(stat) != 0)
+			errx(1, "Child %s (pid %ld) exited with code %d", progname, (long)pid, WEXITSTATUS(stat));
+	} else if (WIFSIGNALED(stat)) {
+		if (WTERMSIG(stat) != SIGTERM)
+			errx(1, "Child %s (pid %ld) was killed by signal %d", progname, (long)pid, WTERMSIG(stat));
+	} else if (WIFSTOPPED(stat)) {
+		errx(1, "Child %s (pid %ld) was stopped by signal %d", progname, (long)pid, WSTOPSIG(stat));
+	} else {
+		errx(1, "Child %s (pid %ld) neither exited nor was killed or stopped; what in the world does wait(2) status %d mean?!", progname, (long)pid, stat);
+	}
+}
+
+void
+spawn_child(const struct tunnel * const t)
+{
+	debug("Starting ayiya_resp for tunnel %s, source %s\n",
+	    t->id, t->remote_outer);
+	assert(resppath != NULL);
+
+	char node_name[NG_NODESIZ];
+	get_ayiya_node(node_name, sizeof(node_name), t, "");
+	char input[10];
+	snprintf(input, sizeof(input), "%d", t->comm[0]);
+	execl(resppath, "ayiya_resp", (verbose? "-vn": "-n"), node_name,
+	    "-i", input, "loop", NULL);
+	err(1, "Could not execute the AYIYA responder %s for %s",
+	    resppath, t->id);
+}
+
+int
+inet_addrport(const struct sockaddr_in * const sin, char * const buf,
+		const size_t size)
+{
+	char addr[INET_ADDRSTRLEN];
+	char port[6];
+	const int ares = getnameinfo((const struct sockaddr *)sin,
+	    sizeof(*sin), addr, sizeof(addr), port, sizeof(port),
+	    NI_NUMERICHOST | NI_NUMERICSERV);
+	if (ares != 0) {
+		warnx("Could not convert the message source to an IPv4 address:port string: %s",
+		    gai_strerror(ares));
+		return 0;
+	}
+
+	const ssize_t len = snprintf(buf, size, "%s:%s", addr, port);
+	if (len < 0) {
+		warn("Message source snprintf() failed");
+		return 0;
+	} else if ((size_t)len >= size) {
+		warnx("Could not convert the message source to an IPv4 "
+		    "address:port string, tried to store %zd bytes in "
+		    "a %zu byte buffer", len, size);
+		return 0;
+	}
+	return 1;
+}
+
+void
+parse_tunnels(const char * const fname)
+{
+	FILE * const fp = fopen(fname, "r");
+	if (fp == NULL)
+		err(1, "Could not open the tunnels file %s", fname);
+
+	regex_t re_tunnel, re_line;
+	int rerr = regcomp(&re_tunnel, "^T[0-9]+$", REG_EXTENDED);
+	if (rerr != 0)
+		report_regerror(rerr, &re_tunnel);
+	rerr = regcomp(&re_line, "^[[:space:]]+([^:]+):[[:space:]]*(.*)$",
+	    REG_EXTENDED);
+	if (rerr != 0)
+		report_regerror(rerr, &re_line);
+
+	char *line = NULL;
+	size_t cap = 0;
+	int in = 0;
+	struct tunnel *t = tunnels;
+	debug("Parsing the tunnels file %s\n", fname);
+	unsigned flags;
+	while (1) {
+		ssize_t len = getline(&line, &cap, fp);
+		if (len == -1) {
+			if (ferror(fp))
+				err(1, "Could not read the tunnels file %s",
+				    fname);
+			else
+				break;
+		}
+		while (len > 0 && strchr(" \t\r\n", line[len - 1]) != NULL)
+			line[--len] = '\0';
+		if (len == 0)
+			continue;
+
+		regmatch_t match[3];
+		rerr = regexec(&re_tunnel, line, 1, match, 0);
+		if (rerr == 0) {
+			assert(match[0].rm_so == 0);
+			assert(match[0].rm_eo == len);
+
+			if (in) {
+				if (flags != (1 << 5) - 1)
+					errx(1,
+					    "Error in the tunnels file %s: "
+					    "incomplete specification for "
+					    "tunnel %s", fname, t->id);
+
+				debug("- tunnel %s complete, moving on\n",
+				    t->id);
+				t++;
+				tunnels_count++;
+			}
+
+			if ((size_t)len + 1 >= sizeof(t->id))
+				errx(1,
+				    "Error in the tunnels file %s: "
+				    "tunnel name '%s' too long, must be "
+				    "at most %zu characters long",
+				    fname, line, sizeof(t->id) - 1);
+			snprintf(t->id, sizeof(t->id), "%s", line);
+			/* Let's hope that the rest is pre-zeroed */
+			debug("- parsing tunnel %s\n", line);
+
+			flags = 0;
+			in = 1;
+			continue;
+		} else if (rerr != REG_NOMATCH) {
+			report_regerror(rerr, &re_tunnel);
+		}
+
+		rerr = regexec(&re_line, line, 3, match, 0);
+		if (rerr == 0) {
+			if (!in)
+				errx(1, "Error in the tunnels file %s: "
+				    "data before the tunnel name", fname);
+			assert(match[1].rm_so != -1);
+			const char * const var = line + match[1].rm_so;
+			line[match[1].rm_eo] = '\0';
+			assert(match[2].rm_so != -1);
+			const char * const value = line + match[2].rm_so;
+			line[match[2].rm_eo] = '\0';
+
+			if (strcmp(var, "IPv6 Endpoint") == 0) {
+				debug("  - local address %s\n", value);
+				const int res = inet_pton(AF_INET6, value,
+				    &t->local_addr);
+				if (res == 0)
+					errx(1,
+					    "Error in the tunnels file %s: "
+					    "invalid IPv6 endpoint '%s' for "
+					    "tunnel %s", fname, value, t->id);
+				else if (res == -1)
+					errx(1, "Could not parse the IPv6 "
+					    "endpoint '%s' for tunnel %s",
+					    value, t->id);
+				flags |= 1 << 0;
+			} else if (strcmp(var, "IPv6 POP") == 0) {
+				debug("  - remote address %s\n", value);
+				const int res = inet_pton(AF_INET6, value,
+				    &t->remote_addr);
+				if (res == 0)
+					errx(1,
+					    "Error in the tunnels file %s: "
+					    "invalid IPv6 client '%s' for "
+					    "tunnel %s", fname, value, t->id);
+				else if (res == -1)
+					errx(1, "Could not parse the IPv6 "
+					    "client '%s' for tunnel %s",
+					    value, t->id);
+				flags |= 1 << 1;
+			} else if (strcmp(var, "IPv6 PrefixLength") == 0) {
+				debug("  - prefix length %s\n", value);
+				char *end;
+				const unsigned long v =
+				    strtoul(value, &end, 10);
+				if (v > 127 || *end != '\0')
+					errx(1,
+					    "Error in the tunnels file %s: "
+					    "invalid prefix length '%s' for "
+					    "tunnel %s", fname, value, t->id);
+				t->prefix_len = v;
+				flags |= 1 << 2;
+			} else if (strcmp(var, "Password") == 0) {
+				debug("  - tunnel password %s\n", value);
+				SHA_CTX ctx;
+				SHA1_Init(&ctx);
+				SHA1_Update(&ctx, value,
+				    match[2].rm_eo - match[2].rm_so);
+				SHA1_Final(t->password, &ctx);
+				flags |= 1 << 3;
+			} else if (strcmp(var, "Tunnel MTU") == 0) {
+				debug("  - MTU %s\n", value);
+				char *end;
+				const unsigned long v =
+				    strtoul(value, &end, 10);
+				if (v > 100000 || *end != '\0')
+					errx(1,
+					    "Error in the tunnels file %s: "
+					    "invalid MTU '%s' for "
+					    "tunnel %s", fname, value, t->id);
+				t->mtu = v;
+				flags |= 1 << 4;
+			}
+		} else if (rerr != REG_NOMATCH) {
+			report_regerror(rerr, &re_tunnel);
+		} else {
+			errx(1, "Error in the tunnels file %s: "
+			    "unrecognized line '%s'", fname, line);
+		}
+	}
+
+	if (flags != (1 << 5) - 1)
+		errx(1, "Error in the tunnels file %s: "
+		    "incomplete specification for tunnel %s", fname, t->id);
+	tunnels_count++;
+	debug("- tunnel %s complete\n", t->id);
+}
+
+void
+report_regerror(const int code, const regex_t * restrict const preg)
+{
+	char *buf = NULL;
+	size_t sz = 0;
+
+	while (1) {
+		const size_t newsz = regerror(code, preg, buf, sz);
+		if (newsz <= sz)
+			break;
+
+		char * const nbuf = realloc(buf, newsz);
+		if (nbuf == NULL)
+			err(1, "No memory for an regerror buffer");
+		buf = nbuf;
+		sz = newsz;
+	}
+	errx(1, "Internal error compiling a regular expression: %s", buf);
+}
+
+void
+get_ayiya_node(char * const buf, const size_t sz,
+		const struct tunnel * const t,const char * const suffix)
+{
+	snprintf(buf, sz, "ayiya_%s%s", t->id, suffix);
+}
+
+void
+sigchld_handler(int sig)
+{
+	/**
+	 * The only purpose of this signal handler is to make sure
+	 * that accept_connection()'s recvfrom() is interrupted.
+	 */
+	(void)sig;
+}

Modified: soc2015/roam/ayiya_resp/Makefile
==============================================================================
--- soc2015/roam/ayiya_resp/Makefile	Wed Aug 19 15:56:09 2015	(r289925)
+++ soc2015/roam/ayiya_resp/Makefile	Wed Aug 19 15:56:16 2015	(r289926)
@@ -26,6 +26,7 @@
 SRCS=	main.c
 NO_MAN=	yes
 WARNS?=	9
+NO_WCAST_ALIGN=	1
 DPADD=	${LIBNETGRAPH}
 LDADD=	-lnetgraph
 

Modified: soc2015/roam/ayiya_resp/main.c
==============================================================================
--- soc2015/roam/ayiya_resp/main.c	Wed Aug 19 15:56:09 2015	(r289925)
+++ soc2015/roam/ayiya_resp/main.c	Wed Aug 19 15:56:16 2015	(r289926)
@@ -25,10 +25,13 @@
  */
 
 #include <sys/types.h>
+#include <sys/queue.h>
 
 #include <assert.h>
 #include <err.h>
+#include <errno.h>
 #include <fcntl.h>
+#include <limits.h>
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -47,6 +50,7 @@
 #define A_SEL_RD_DATA	0x0001
 #define A_SEL_WR_DATA	0x0002
 #define A_SEL_EXC	0x0004
+#define A_SEL_RD_INPUT	0x0008
 
 #define MOTD		\
 	"1435504634\n" \
@@ -54,6 +58,15 @@
 	"Welcome to the system, here's the situation;\n" \
 	"It's a bit confusing, welcome to the maze!\n"
 
+struct ayqueue {
+	size_t		sz;
+	STAILQ_ENTRY(ayqueue)	next;
+	char		buf[];
+};
+
+static int		inputfd = -1;
+static STAILQ_HEAD(ayqhead, ayqueue)	injectq;
+
 static int		quiet;
 static int		verbose;
 
@@ -69,6 +82,10 @@
 static unsigned		ayiya_select(int, bool, bool);
 static void		send_packet(int, ayiya_opcode, const char *, size_t);
 static void		send_empty_packet(int, ayiya_opcode);
+static void		inject_packets(int);
+
+static size_t		check_ayiya_packet(const char *, size_t);
+static void		inject_ayiya_packet(const char *, size_t);
 
 static struct {
 	const char *name;
@@ -79,7 +96,7 @@
 };
 #define COMMANDS	(sizeof(commands) / sizeof(commands[0]))
 
-#define AYIYA_ND	"sc_ayiya"
+static const char *node_name = "sc_ayiya";
 
 static union {
 	struct ng_mesg msg;
@@ -93,12 +110,32 @@
 	int ch, hflag, Vflag;
 
 	hflag = Vflag = 0;
-	while (ch = getopt(argc, argv, "hqVv"), ch != -1)
+	while (ch = getopt(argc, argv, "hi:n:qVv"), ch != -1)
 		switch (ch) {
 			case 'h':
 				hflag = 1;
 				break;
 

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-soc-all mailing list