Edit

IABSD.fr/src/usr.sbin/switchd/switchd.c

Branch :

  • Show log

    Commit

  • Author : schwarze
    Date : 2019-07-23 00:31:31
    Hash : 7dc362a4
    Message : delete a stray blank from the usage() message; reported by Jesper Wallin

  • usr.sbin/switchd/switchd.c
  • /*	$OpenBSD: switchd.c,v 1.18 2019/07/23 00:31:31 schwarze Exp $	*/
    
    /*
     * Copyright (c) 2013-2016 Reyk Floeter <reyk@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/uio.h>
    #include <sys/un.h>
    #include <sys/queue.h>
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <netinet/tcp.h>
    
    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <signal.h>
    #include <syslog.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <pwd.h>
    #include <event.h>
    
    #include "switchd.h"
    
    void	 parent_shutdown(struct switchd *);
    void	 parent_sig_handler(int, short, void *);
    int	 parent_dispatch_ofp(int, struct privsep_proc *, struct imsg *);
    int	 parent_dispatch_control(int, struct privsep_proc *, struct imsg *);
    int	 parent_configure(struct switchd *);
    int	 parent_reload(struct switchd *);
    void	 parent_connect(struct privsep *, struct switch_client *);
    void	 parent_connected(int, short, void *);
    void	 parent_disconnect(struct privsep *, struct switch_client *);
    
    __dead void	usage(void);
    
    static struct privsep_proc procs[] = {
    	{ "ofp",	PROC_OFP,	NULL, ofp },
    	{ "control",	PROC_CONTROL,	parent_dispatch_control, control },
    	{ "ofcconn",	PROC_OFCCONN,	NULL, ofcconn }
    };
    
    __dead void
    usage(void)
    {
    	extern const char	*__progname;
    	fprintf(stderr, "usage: %s [-dnv] [-c cachesize] [-D macro=value] "
    	    "[-f file] [-t timeout]\n",
    	    __progname);
    	exit(1);
    }
    
    int
    main(int argc, char *argv[])
    {
    	struct switchd		*sc = NULL;
    	struct privsep		*ps = NULL;
    	struct switch_server	*srv;
    	const char		*errstr = NULL;
    	int			 c;
    	int			 debug = 0, verbose = 0;
    	uint32_t		 opts = 0;
    	unsigned int		 cache = SWITCHD_CACHE_MAX;
    	unsigned int		 timeout = SWITCHD_CACHE_TIMEOUT;
    	const char		*conffile = SWITCHD_CONFIG;
    	const char		*errp, *title = NULL;
    	enum privsep_procid	 proc_id = PROC_PARENT;
    	int			 argc0 = argc, proc_instance = 0;
    
    	log_init(1, LOG_DAEMON);
    
    	while ((c = getopt(argc, argv, "c:dD:f:hI:nP:t:v")) != -1) {
    		switch (c) {
    		case 'c':
    			cache = strtonum(optarg, 1, UINT32_MAX, &errstr);
    			if (errstr != NULL) {
    				log_warn("max cache size: %s", errstr);
    				usage();
    			}
    			break;
    		case 'd':
    			debug++;
    			break;
    		case 'D':
    			if (cmdline_symset(optarg) < 0)
    				log_warnx("could not parse macro definition %s",
    				    optarg);
    			break;
    		case 'f':
    			conffile = optarg;
    			break;
    		case 'I':
    			proc_instance = strtonum(optarg, 0,
    			    PROC_MAX_INSTANCES, &errp);
    			if (errp)
    				fatalx("invalid process instance");
    			break;
    		case 'n':
    			opts |= SWITCHD_OPT_NOACTION;
    			break;
    		case 'P':
    			title = optarg;
    			proc_id = proc_getid(procs, nitems(procs), title);
    			if (proc_id == PROC_MAX)
    				fatalx("invalid process name");
    			break;
    		case 't':
    			timeout = strtonum(optarg, 0, UINT32_MAX, &errstr);
    			if (errstr != NULL) {
    				log_warn("cache timeout: %s", errstr);
    				usage();
    			}
    			break;
    		case 'v':
    			verbose++;
    			opts |= SWITCHD_OPT_VERBOSE;
    			break;
    		default:
    			usage();
    		}
    	}
    
    	if ((sc = calloc(1, sizeof(*sc))) == NULL)
    		fatal("calloc");
    
    	if (strlcpy(sc->sc_conffile, conffile, PATH_MAX) >= PATH_MAX)
    		fatal("config file exceeds PATH_MAX");
    
    	sc->sc_cache_max = cache;
    	sc->sc_cache_timeout = timeout;
    	sc->sc_opts = opts;
    
    	srv = &sc->sc_server;
    	srv->srv_sc = sc;
    
    	ps = &sc->sc_ps;
    	ps->ps_env = sc;
    	TAILQ_INIT(&ps->ps_rcsocks);
    	TAILQ_INIT(&sc->sc_clients);
    
    	if (parse_config(sc->sc_conffile, sc) == -1) {
    		proc_kill(&sc->sc_ps);
    		exit(1);
    	}
    
    	if (opts & SWITCHD_OPT_NOACTION) {
    		fprintf(stderr, "configuration OK\n");
    		proc_kill(&sc->sc_ps);
    		exit(0);
    	}
    
    	/* check for root privileges */
    	if (geteuid())
    		fatalx("need root privileges");
    
    	if ((ps->ps_pw =  getpwnam(SWITCHD_USER)) == NULL)
    		fatalx("unknown user " SWITCHD_USER);
    
    	log_init(debug, LOG_DAEMON);
    	log_setverbose(verbose);
    
    	/* Configure the control socket */
    	ps->ps_csock.cs_name = SWITCHD_SOCKET;
    	ps->ps_instance = proc_instance;
    	if (title)
    		ps->ps_title[proc_id] = title;
    
    	/* Only the parent returns. */
    	proc_init(ps, procs, nitems(procs), debug, argc0, argv, proc_id);
    
    	if (!debug && daemon(0, 0) == -1)
    		fatal("failed to daemonize");
    
    	log_procinit("parent");
    
    	if (unveil("/", "r") == -1)
    		fatal("unveil");
    	if (unveil("/dev", "rw") == -1)
    		fatal("unveil");
    	/*
    	 * pledge in the parent process:
    	 * stdio - for malloc and basic I/O including events.
    	 * rpath - for reload to open and read the configuration files.
    	 * wpath - for accessing the /dev/switch device.
    	 * inet - for opening OpenFlow and device sockets.
    	 * dns - for resolving host in the configuration files.
    	 * sendfd - send sockets to child processes on reload.
    	 */
    	if (pledge("stdio rpath wpath inet dns sendfd", NULL) == -1)
    		fatal("pledge");
    
    	event_init();
    
    	signal_set(&ps->ps_evsigint, SIGINT, parent_sig_handler, ps);
    	signal_set(&ps->ps_evsigterm, SIGTERM, parent_sig_handler, ps);
    	signal_set(&ps->ps_evsighup, SIGHUP, parent_sig_handler, ps);
    	signal_set(&ps->ps_evsigpipe, SIGPIPE, parent_sig_handler, ps);
    	signal_set(&ps->ps_evsigusr1, SIGUSR1, parent_sig_handler, ps);
    
    	signal_add(&ps->ps_evsigint, NULL);
    	signal_add(&ps->ps_evsigterm, NULL);
    	signal_add(&ps->ps_evsighup, NULL);
    	signal_add(&ps->ps_evsigpipe, NULL);
    	signal_add(&ps->ps_evsigusr1, NULL);
    
    	proc_connect(ps);
    
    	if (parent_configure(sc) == -1)
    		fatalx("configuration failed");
    
    	event_dispatch();
    
    	log_debug("%d parent exiting", getpid());
    
    	return (0);
    }
    
    int
    switchd_socket(struct sockaddr *sock, int reuseport)
    {
    	int s = -1, val;
    	struct linger lng;
    
    	if ((s = socket(sock->sa_family, SOCK_STREAM | SOCK_NONBLOCK,
    	    IPPROTO_TCP)) == -1)
    		goto bad;
    
    	/*
    	 * Socket options
    	 */
    	bzero(&lng, sizeof(lng));
    	if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
    		goto bad;
    	if (reuseport) {
    		val = 1;
    		if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val,
    		    sizeof(int)) == -1)
    			goto bad;
    	}
    
    	/*
    	 * TCP options
    	 */
    	val = 1;
    	if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
    	    &val, sizeof(val)) == -1)
    		goto bad;
    
    	return (s);
    
     bad:
    	if (s != -1)
    		close(s);
    	return (-1);
    }
    
    int
    switchd_listen(struct sockaddr *sock)
    {
    	int s;
    
    	if ((s = switchd_socket(sock, 1)) == -1)
    		return (-1);
    
    	if (bind(s, sock, sock->sa_len) == -1)
    		goto bad;
    	if (listen(s, 10) == -1)
    		goto bad;
    
    	return (s);
    
     bad:
    	close(s);
    	return (-1);
    }
    
    int
    switchd_tap(void)
    {
    	char	 path[PATH_MAX];
    	int	 i, fd;
    
    	for (i = 0; i < SWITCHD_MAX_TAP; i++) {
    		snprintf(path, PATH_MAX, "/dev/tap%d", i);
    		fd = open(path, O_RDWR | O_NONBLOCK);
    		if (fd != -1)
    			return (fd);
    	}
    
    	return (-1);
    }
    
    struct switch_connection *
    switchd_connbyid(struct switchd *sc, unsigned int id, unsigned int instance)
    {
    	struct switch_connection	*con;
    
    	TAILQ_FOREACH(con, &sc->sc_conns, con_entry) {
    		if (con->con_id == id && con->con_instance == instance)
    			return (con);
    	}
    
    	return (NULL);
    }
    
    struct switch_connection *
    switchd_connbyaddr(struct switchd *sc, struct sockaddr *sa)
    {
    	struct switch_connection	*con;
    
    	TAILQ_FOREACH(con, &sc->sc_conns, con_entry) {
    		if (sockaddr_cmp((struct sockaddr *)
    		    &con->con_peer, sa, -1) == 0)
    			return (con);
    	}
    
    	return (NULL);
    }
    
    void
    parent_sig_handler(int sig, short event, void *arg)
    {
    	struct privsep	*ps = arg;
    
    	switch (sig) {
    	case SIGHUP:
    		log_info("%s: reload requested with SIGHUP", __func__);
    
    		/*
    		 * This is safe because libevent uses async signal handlers
    		 * that run in the event loop and not in signal context.
    		 */
    		parent_reload(ps->ps_env);
    		break;
    	case SIGPIPE:
    		log_info("%s: ignoring SIGPIPE", __func__);
    		break;
    	case SIGUSR1:
    		log_info("%s: ignoring SIGUSR1", __func__);
    		break;
    	case SIGTERM:
    	case SIGINT:
    		parent_shutdown(ps->ps_env);
    		break;
    	default:
    		fatalx("unexpected signal");
    	}
    }
    
    int
    parent_configure(struct switchd *sc)
    {
    	struct switch_client	*swc, *swcn;
    	int			 fd;
    
    	if ((fd = switchd_tap()) == -1)
    		fatal("%s: tap", __func__);
    	proc_compose_imsg(&sc->sc_ps, PROC_OFP, -1,
    	    IMSG_TAPFD, -1, fd, NULL, 0);
    
    	TAILQ_FOREACH_SAFE(swc, &sc->sc_clients, swc_next, swcn) {
    		parent_connect(&sc->sc_ps, swc);
    	}
    
    	return (0);
    }
    
    int
    parent_reload(struct switchd *sc)
    {
    	struct switchd			 newconf;
    	struct switch_client		*swc, *oswc, *swcn;
    
    	memset(&newconf, 0, sizeof(newconf));
    	TAILQ_INIT(&newconf.sc_clients);
    	TAILQ_INIT(&newconf.sc_conns);
    
    	if (parse_config(sc->sc_conffile, &newconf) != -1) {
    		TAILQ_FOREACH_SAFE(swc, &sc->sc_clients, swc_next, swcn) {
    			TAILQ_FOREACH(oswc, &newconf.sc_clients, swc_next) {
    				if (sockaddr_cmp((struct sockaddr *)
    				    &oswc->swc_addr.swa_addr,
    				    (struct sockaddr *)
    				    &swc->swc_addr.swa_addr, -1) == 0) {
    					TAILQ_REMOVE(&newconf.sc_clients,
    					    oswc, swc_next);
    					break;
    				}
    			}
    			if (oswc == NULL) {
    				/* Removed */
    				parent_disconnect(&sc->sc_ps, swc);
    			} else {
    				/* Keep the existing one */
    				TAILQ_REMOVE(&newconf.sc_clients,
    				    oswc, swc_next);
    				free(oswc);
    			}
    		}
    		TAILQ_FOREACH_SAFE(swc, &newconf.sc_clients, swc_next, swcn) {
    			TAILQ_REMOVE(&newconf.sc_clients, swc, swc_next);
    			TAILQ_INSERT_TAIL(&sc->sc_clients, swc, swc_next);
    
    			parent_connect(&sc->sc_ps, swc);
    		}
    	}
    
    	return (0);
    }
    
    int
    parent_dispatch_control(int fd, struct privsep_proc *p, struct imsg *imsg)
    {
    	struct switch_client	*swc, *oswc;
    	struct privsep		*ps = p->p_ps;
    	struct switchd		*sc = ps->ps_env;
    
    	switch (imsg->hdr.type) {
    	case IMSG_CTL_CONNECT:
    	case IMSG_CTL_DISCONNECT:
    		IMSG_SIZE_CHECK(imsg, swc);
    
    		/* Need to allocate it in case it is reused */
    		if ((swc = calloc(1, sizeof(*swc))) == NULL) {
    			log_warnx("%s: calloc", __func__);
    			return (0);
    		}
    		memcpy(swc, imsg->data, sizeof(*swc));
    		memset(&swc->swc_ev, 0, sizeof(swc->swc_ev));
    
    		if (imsg->hdr.type == IMSG_CTL_CONNECT) {
    			TAILQ_INSERT_TAIL(&sc->sc_clients, swc, swc_next);
    			parent_connect(p->p_ps, swc);
    		} else {
    			TAILQ_FOREACH(oswc, &sc->sc_clients, swc_next) {
    				if (sockaddr_cmp((struct sockaddr *)
    				    &oswc->swc_addr.swa_addr,
    				    (struct sockaddr *)
    				    &swc->swc_addr.swa_addr, -1) == 0) {
    					parent_disconnect(ps, oswc);
    					break;
    				}
    			}
    			if (oswc == NULL)
    				log_warnx("client %s is not connected",
    				    print_host(&swc->swc_addr.swa_addr,
    				    NULL, 0));
    			free(swc);
    		}
    		return (0);
    	default:
    		break;
    	}
    
    	return (-1);
    }
    
    void
    parent_shutdown(struct switchd *sc)
    {
    	proc_kill(&sc->sc_ps);
    
    	free(sc);
    
    	log_warnx("parent terminating");
    	exit(0);
    }
    
    void
    parent_connect(struct privsep *ps, struct switch_client *swc)
    {
    	struct switchd		*sc = ps->ps_env;
    	struct sockaddr_storage	*ss;
    	struct sockaddr_un	*un;
    	struct sockaddr_in	*sin4;
    	struct sockaddr_in6	*sin6;
    	int			 fd = -1;
    	struct timeval		 tv;
    
    	ss = &swc->swc_addr.swa_addr;
    
    	if (ss->ss_len == 0) {
    		log_warnx("%s: invalid address", __func__);
    		goto fail;
    	}
    	swc->swc_arg = ps;
    	memset(&swc->swc_ev, 0, sizeof(swc->swc_ev));
    
    	switch (ss->ss_family) {
    	case AF_LOCAL:
    		un = (struct sockaddr_un *)ss;
    
    		/* restrict the opening path to /dev/switch* */
    		if (strncmp(un->sun_path, "/dev/switch",
    		    strlen("/dev/switch")) != 0) {
    			log_warnx("%s: device path is wrong: %s", __func__,
    			    un->sun_path);
    			goto fail;
    		}
    
    		if ((fd = open(un->sun_path, O_RDWR | O_NONBLOCK)) == -1) {
    			log_warn("%s: failed to open %s",
    			    __func__, un->sun_path);
    			goto fail;
    		}
    		break;
    	case AF_INET:
    	case AF_INET6:
    		if (ss->ss_family == AF_INET) {
    			sin4 = (struct sockaddr_in *)ss;
    			if (sin4->sin_port == 0)
    				sin4->sin_port = htons(SWITCHD_CTLR_PORT);
    		} else if (ss->ss_family == AF_INET6) {
    			sin6 = (struct sockaddr_in6 *)ss;
    			if (sin6->sin6_port == 0)
    				sin6->sin6_port = htons(SWITCHD_CTLR_PORT);
    		}
    
    		if ((fd = switchd_socket((struct sockaddr *)ss, 0)) == -1) {
    			log_debug("%s: failed to get socket for %s", __func__,
    			    print_host(ss, NULL, 0));
    			goto fail;
    		}
    
     retry:
    		if (connect(fd, (struct sockaddr *)ss, ss->ss_len) == -1) {
    			if (errno == EINTR)
    				goto retry;
    			if (errno == EINPROGRESS) {
    				tv.tv_sec = SWITCHD_CONNECT_TIMEOUT;
    				tv.tv_usec = 0;
    				event_set(&swc->swc_ev, fd, EV_WRITE|EV_TIMEOUT,
    				    parent_connected, swc);
    				event_add(&swc->swc_ev, &tv);
    				return;
    			}
    
    			log_warn("%s: failed to connect to %s, fd %d", __func__,
    			    print_host(ss, NULL, 0), fd);
    			goto fail;
    		}
    
    		break;
    	}
    
    	parent_connected(fd, 0, swc);
    	return;
    
     fail:
    	TAILQ_REMOVE(&sc->sc_clients, swc, swc_next);
    	free(swc);
    }
    
    void
    parent_connected(int fd, short event, void *arg)
    {
    	struct switch_client	*swc = arg;
    	struct privsep		*ps = swc->swc_arg;
    	struct switchd		*sc = ps->ps_env;
    
    	if (event & EV_TIMEOUT) {
    		log_debug("%s: failed to connect to %s", __func__,
    		    print_host(&swc->swc_addr.swa_addr, NULL, 0));
    		TAILQ_REMOVE(&sc->sc_clients, swc, swc_next);
    		free(swc);
    		return;
    	}
    
    	switch (swc->swc_target.swa_type) {
    	case SWITCH_CONN_LOCAL:
    		proc_compose_imsg(ps, PROC_OFP, -1, IMSG_CTL_CONNECT,
    		    -1, fd, swc, sizeof(*swc));
    		break;
    	case SWITCH_CONN_TLS:
    	case SWITCH_CONN_TCP:
    		proc_compose_imsg(ps, PROC_OFCCONN, -1, IMSG_CTL_CONNECT,
    		    -1, fd, swc, sizeof(*swc));
    		break;
    	default:
    		fatalx("not implemented");
    	}
    }
    
    void
    parent_disconnect(struct privsep *ps, struct switch_client *swc)
    {
    	struct switchd		*sc = ps->ps_env;
    	enum privsep_procid	 target;
    
    	TAILQ_REMOVE(&sc->sc_clients, swc, swc_next);
    
    	target = swc->swc_target.swa_type == SWITCH_CONN_LOCAL ?
    	    PROC_OFP : PROC_OFCCONN;
    	proc_compose(ps, target, IMSG_CTL_DISCONNECT, swc, sizeof(*swc));
    
    	free(swc);
    }