From: reyk Date: Tue, 19 Jul 2016 16:54:26 +0000 (+0000) Subject: Import switchd(8), a basic WIP OpenFlow implementation for OpenBSD. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=d24c1ddb59f93e30ff64fdd3703cf88de9c8e584;p=openbsd Import switchd(8), a basic WIP OpenFlow implementation for OpenBSD. switchd consists of two parts: 1. switchd(8) and switchctl(8), an OpenFlow controller or "vswitch". 2. switch(4), an OpenFlow-aware kernel "bridge". This the 1st part, the driver will be imported later. The code will remain disabled for a while, but it helps development to have it in the tree. switchd currently supports partial OpenFlow 1.0, but the goal is to use OpenFlow 1.3.5 instead (switch(4) already does 1.3.5). For more background information see: http://www.openbsd.org/papers/bsdcan2016-switchd.pdf https://youtu.be/Cuo0qT-lqig With help from yasuoka@ goda@ Import discussed with deraadt@ --- diff --git a/usr.sbin/switchctl/Makefile b/usr.sbin/switchctl/Makefile new file mode 100644 index 00000000000..e894ddd044f --- /dev/null +++ b/usr.sbin/switchctl/Makefile @@ -0,0 +1,21 @@ +# $OpenBSD: Makefile,v 1.1 2016/07/19 16:54:26 reyk Exp $ + +NAME= switch + +.PATH: ${.CURDIR}/../${NAME}d + +PROG= ${NAME}ctl +MAN= ${NAME}ctl.8 +SRCS= log.c ${NAME}ctl.c parser.c util.c + +LDADD= -lutil +DPADD= ${LIBUTIL} + +CFLAGS+= -DOFD_NAME=\"${NAME}\" +CFLAGS+= -Wall -I${.CURDIR} -I${.CURDIR}/../${NAME}d +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare + +.include diff --git a/usr.sbin/switchctl/parser.c b/usr.sbin/switchctl/parser.c new file mode 100644 index 00000000000..ee671c8adb6 --- /dev/null +++ b/usr.sbin/switchctl/parser.c @@ -0,0 +1,284 @@ +/* $OpenBSD: parser.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2010-2013 Reyk Floeter + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "switchd.h" +#include "parser.h" + +enum token_type { + NOTOKEN, + ENDTOKEN, + KEYWORD, + PATH, + ADDRESS, + FQDN, + DEVICE, + URI +}; + +struct token { + enum token_type type; + const char *keyword; + int value; + const struct token *next; +}; + +static const struct token t_main[]; +static const struct token t_reset[]; +static const struct token t_log[]; +static const struct token t_load[]; +static const struct token t_show[]; +static const struct token t_device[]; +static const struct token t_device_add[]; +static const struct token t_device_remove[]; +static const struct token t_connect_to[]; +static const struct token t_uri[]; + +static const struct token t_main[] = { + { KEYWORD, "device", NONE, t_device }, + { KEYWORD, "load", LOAD, t_load }, + { KEYWORD, "log", NONE, t_log }, + { KEYWORD, "monitor", MONITOR, NULL }, + { KEYWORD, "reload", RELOAD, NULL }, + { KEYWORD, "reset", NONE, t_reset }, + { KEYWORD, "show", NONE, t_show }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_log[] = { + { KEYWORD, "verbose", LOG_VERBOSE, NULL }, + { KEYWORD, "brief", LOG_BRIEF, NULL }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_reset[] = { + { KEYWORD, "all", RESETALL, NULL }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_load[] = { + { PATH, "", NONE, NULL }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_opt_path[] = { + { NOTOKEN, "", NONE, NULL }, + { PATH, "", NONE, NULL }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_show[] = { + { KEYWORD, "summary", SHOW_SUM, NULL }, + { KEYWORD, "switches", SHOW_SWITCHES, NULL }, + { KEYWORD, "macs", SHOW_MACS, NULL }, + { ENDTOKEN, "", NONE, NULL } +}; +static const struct token t_device[] = { + { KEYWORD, "add", ADD_DEVICE, t_device_add }, + { KEYWORD, "remove", REMOVE_DEVICE, t_device_remove }, + { ENDTOKEN, "", NONE, NULL } +}; +static const struct token t_device_add[] = { + { DEVICE, "", NONE, t_connect_to }, + { ENDTOKEN, "", NONE, NULL } +}; +static const struct token t_device_remove[] = { + { DEVICE, "", NONE, NULL }, + { ENDTOKEN, "", NONE, NULL } +}; +static const struct token t_connect_to[] = { + { KEYWORD, "connect-to", NONE, t_uri }, + { ENDTOKEN, "", NONE, NULL } +}; +static const struct token t_uri[] = { + { URI, "", NONE, NULL }, + { ENDTOKEN, "", NONE, NULL } +}; + +static struct parse_result res; + +const struct token *match_token(char *, const struct token []); +void show_valid_args(const struct token []); +int parse_addr(const char *); + +struct parse_result * +parse(int argc, char *argv[]) +{ + const struct token *table = t_main; + const struct token *match; + + bzero(&res, sizeof(res)); + + while (argc >= 0) { + if ((match = match_token(argv[0], table)) == NULL) { + fprintf(stderr, "valid commands/args:\n"); + show_valid_args(table); + return (NULL); + } + + argc--; + argv++; + + if (match->type == NOTOKEN || match->next == NULL) + break; + + table = match->next; + } + + if (argc > 0) { + fprintf(stderr, "superfluous argument: %s\n", argv[0]); + return (NULL); + } + + return (&res); +} + +int +parse_addr(const char *word) +{ + struct addrinfo hints, *r; + + bzero(&hints, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; /* dummy */ + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(word, "0", &hints, &r) == 0) { + return (0); + } + + return (1); +} + + +const struct token * +match_token(char *word, const struct token table[]) +{ + unsigned int i, match = 0; + const struct token *t = NULL; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + if (word == NULL || strlen(word) == 0) { + match++; + t = &table[i]; + } + break; + case KEYWORD: + if (word != NULL && strncmp(word, table[i].keyword, + strlen(word)) == 0) { + match++; + t = &table[i]; + if (t->value) + res.action = t->value; + } + break; + case DEVICE: + case PATH: + if (!match && word != NULL && strlen(word) > 0) { + res.path = strdup(word); + match++; + t = &table[i]; + } + break; + case ADDRESS: + case FQDN: + if (!match && word != NULL && strlen(word) > 0) { + parse_addr(word); + res.host = strdup(word); + if (parse_addr(word) == 0) + res.htype = HOST_IPADDR; + else + res.htype = HOST_FQDN; + match++; + t = &table[i]; + } + break; + case URI: + if (!match && word != NULL && strlen(word) > 0) { + res.uri = strdup(word); + match++; + t = &table[i]; + } + break; + case ENDTOKEN: + break; + } + } + + if (match != 1) { + if (word == NULL) + fprintf(stderr, "missing argument:\n"); + else if (match > 1) + fprintf(stderr, "ambiguous argument: %s\n", word); + else if (match < 1) + fprintf(stderr, "unknown argument: %s\n", word); + return (NULL); + } + + return (t); +} + +void +show_valid_args(const struct token table[]) +{ + int i; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + fprintf(stderr, " \n"); + break; + case KEYWORD: + fprintf(stderr, " %s\n", table[i].keyword); + break; + case PATH: + fprintf(stderr, " \n"); + break; + case ADDRESS: + fprintf(stderr, " \n"); + break; + case FQDN: + fprintf(stderr, " \n"); + break; + case DEVICE: + fprintf(stderr, " \n"); + break; + case URI: + fprintf(stderr, " \n"); + break; + case ENDTOKEN: + break; + } + } +} diff --git a/usr.sbin/switchctl/parser.h b/usr.sbin/switchctl/parser.h new file mode 100644 index 00000000000..8b885886b8e --- /dev/null +++ b/usr.sbin/switchctl/parser.h @@ -0,0 +1,55 @@ +/* $OpenBSD: parser.h,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2007-2015 Reyk Floeter + * + * 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. + */ + +#ifndef _SWITCHCTL_PARSER_H +#define _SWITCHCTL_PARSER_H + +enum actions { + NONE, + SHOW_SUM, + SHOW_SWITCHES, + SHOW_MACS, + LOAD, + RELOAD, + MONITOR, + LOG_VERBOSE, + LOG_BRIEF, + RESETALL, + ADD_DEVICE, + REMOVE_DEVICE +}; + +struct parse_result { + enum actions action; + struct imsgbuf *ibuf; + char *path; + char *caname; + char *pass; + char *host; + char *peer; + char *uri; + int htype; + int quiet; +}; + +#define HOST_IPADDR 1 +#define HOST_FQDN 2 + +struct parse_result *parse(int, char *[]); + +#endif /* _SWITCHCTL_PARSER_H */ diff --git a/usr.sbin/switchctl/switchctl.8 b/usr.sbin/switchctl/switchctl.8 new file mode 100644 index 00000000000..d4c1ecefa35 --- /dev/null +++ b/usr.sbin/switchctl/switchctl.8 @@ -0,0 +1,100 @@ +.\" $OpenBSD: switchctl.8,v 1.1 2016/07/19 16:54:26 reyk Exp $ +.\" +.\" Copyright (c) 2007-2015 Reyk Floeter +.\" +.\" 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. +.\" +.Dd $Mdocdate: July 19 2016 $ +.Dt SWITCHCTL 8 +.Os +.Sh NAME +.Nm switchctl +.Nd control the SDN flow controller +.Sh SYNOPSIS +.Nm +.Op Fl q +.Op Fl s Ar socket +.Ar command +.Op Ar arg ... +.Sh DESCRIPTION +The +.Nm +program controls the +.Xr switchd 8 +daemon. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl q +Don't ask for confirmation of any default options. +.It Fl s Ar socket +Use +.Ar socket +instead of the default +.Pa /var/run/switchd.sock +to communicate with +.Xr switchd 8 . +.El +.Pp +The following commands are available to control +.Xr switchd 8 : +.Bl -tag -width Ds +.It Cm device add Ar filename +Add new +.Xr switch 4 +control device, for example +.Pa /dev/switch0 . +.It Cm load Ar filename +Reload the configuration from the specified file. +.It Cm log brief +Disable verbose logging. +.It Cm log verbose +Enable verbose logging. +.It Cm monitor +Monitor internal messages of the +.Xr switchd 8 +subsystems. +.It Cm show macs +Display all known mac addresses. +.It Cm show summary +Display a list of all switches and mac addresses. +.It Cm show switches +Display all known switches. +.It Cm reload +Reload the configuration from the default configuration file. +.It Cm reset all +Reset the running state. +.El +.Sh FILES +.Bl -tag -width "/var/run/switchd.sockXX" -compact +.It /etc/switchd.conf +Active configuration. +.It /var/run/switchd.sock +default +.Ux Ns -domain +socket used for communication with +.Xr switchd 8 +.El +.Sh SEE ALSO +.Xr bridge 4 +.Xr switchd 8 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 6.1 . +.Sh AUTHORS +The +.Nm +program was written by +.An Reyk Floeter Aq Mt reyk@openbsd.org . diff --git a/usr.sbin/switchctl/switchctl.c b/usr.sbin/switchctl/switchctl.c new file mode 100644 index 00000000000..2ed852cd313 --- /dev/null +++ b/usr.sbin/switchctl/switchctl.c @@ -0,0 +1,348 @@ +/* $OpenBSD: switchctl.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2007-2015 Reyk Floeter + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2003 Henning Brauer + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "switchd.h" +#include "parser.h" + +__dead void usage(void); + +struct imsgname { + int type; + char *name; + void (*func)(struct imsg *); +}; + +int show_summary_msg(struct imsg *, int); + +struct imsgname *monitor_lookup(uint8_t); +void monitor_id(struct imsg *); +int monitor(struct imsg *); + +int ca_opt(struct parse_result *); + +struct imsgname imsgs[] = { + { IMSG_CTL_OK, "ok", NULL }, + { IMSG_CTL_FAIL, "fail", NULL }, + { IMSG_CTL_VERBOSE, "verbose", NULL }, + { IMSG_CTL_RELOAD, "reload", NULL }, + { IMSG_CTL_RESET, "reset", NULL }, + { 0, NULL, NULL } + +}; +struct imsgname imsgunknown = { + -1, "", NULL +}; + +struct imsgbuf *ibuf; + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-q] [-s socket] command [arg ...]\n", + __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un sun; + struct parse_result *res; + struct imsg imsg; + struct switch_device sdv; + struct switch_controller *swc; + int ctl_sock; + int done = 1; + int n; + int ch; + int v = 0; + int quiet = 0; + const char *sock = SWITCHD_SOCKET; + + while ((ch = getopt(argc, argv, "qs:")) != -1) { + switch (ch) { + case 'q': + quiet = 1; + break; + case 's': + sock = optarg; + break; + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + /* parse options */ + if ((res = parse(argc, argv)) == NULL) + exit(1); + + res->quiet = quiet; + + switch (res->action) { + case NONE: + usage(); + break; + default: + goto connect; + } + + return (0); + + connect: + /* connect to sdnflowd control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, sock, sizeof(sun.sun_path)); + reconnect: + if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + /* Keep retrying if running in monitor mode */ + if (res->action == MONITOR && + (errno == ENOENT || errno == ECONNREFUSED)) { + usleep(100); + goto reconnect; + } + err(1, "connect: %s", sock); + } + + if (res->ibuf != NULL) + ibuf = res->ibuf; + else + if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL) + err(1, "malloc"); + imsg_init(ibuf, ctl_sock); + + /* process user request */ + switch (res->action) { + case RESETALL: + v = RESET_ALL; + break; + case LOG_VERBOSE: + v = 2; + break; + case LOG_BRIEF: + default: + v = 0; + break; + } + + switch (res->action) { + case NONE: + usage(); + /* NOTREACHED */ + break; + case SHOW_SUM: + case SHOW_SWITCHES: + case SHOW_MACS: + imsg_compose(ibuf, IMSG_CTL_SHOW_SUM, 0, 0, -1, NULL, 0); + printf("%-4s\t%-4s\t%-8s\t%-24s\t%s\n", + "Switch", "Port", "Type", "Name", "Info"); + done = 0; + break; + case ADD_DEVICE: + case REMOVE_DEVICE: + memset(&sdv, 0, sizeof(sdv)); + swc = &sdv.sdv_swc; + if (res->path[0] != '/') + strlcpy(sdv.sdv_device, "/dev/", + sizeof(sdv.sdv_device)); + if (strlcat(sdv.sdv_device, res->path, + sizeof(sdv.sdv_device)) >= sizeof(sdv.sdv_device)) + errx(1, "path is too long"); + if (res->action == REMOVE_DEVICE) { + imsg_compose(ibuf, IMSG_CTL_DEVICE_DISCONNECT, 0, 0, -1, + &sdv, sizeof(sdv)); + break; + } + if (res->uri == NULL || res->uri[0] == '\0') + swc->swc_type = SWITCH_CONN_LOCAL; + else { + if (strncmp(res->uri, "tcp:", 4) == 0) + swc->swc_type = SWITCH_CONN_TCP; + else if (strncmp(res->uri, "tls:", 4) == 0) + swc->swc_type = SWITCH_CONN_TLS; + else + errx(1, "protocol field is unknown"); + + if (parsehostport(res->uri + 4, + (struct sockaddr *)&swc->swc_addr, + sizeof(swc->swc_addr)) != 0) + errx(1, + "couldn't parse name-or-address and port"); + warnx("%s", sdv.sdv_device); + } + imsg_compose(ibuf, IMSG_CTL_DEVICE_CONNECT, 0, 0, -1, + &sdv, sizeof(sdv)); + break; + case RESETALL: + imsg_compose(ibuf, IMSG_CTL_RESET, 0, 0, -1, &v, sizeof(v)); + printf("reset request sent.\n"); + break; + case LOAD: + imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, -1, + res->path, strlen(res->path)); + break; + case RELOAD: + imsg_compose(ibuf, IMSG_CTL_RELOAD, 0, 0, -1, NULL, 0); + break; + case MONITOR: + imsg_compose(ibuf, IMSG_CTL_NOTIFY, 0, 0, -1, NULL, 0); + done = 0; + break; + case LOG_VERBOSE: + case LOG_BRIEF: + imsg_compose(ibuf, IMSG_CTL_VERBOSE, 0, 0, -1, &v, sizeof(v)); + printf("logging request sent.\n"); + break; + default: + break; + } + + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) + err(1, "write error"); + + while (!done) { + if ((n = imsg_read(ibuf)) == -1) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + break; + switch (res->action) { + case SHOW_SUM: + case SHOW_SWITCHES: + case SHOW_MACS: + done = show_summary_msg(&imsg, res->action); + break; + case MONITOR: + done = monitor(&imsg); + break; + default: + break; + } + imsg_free(&imsg); + } + } + close(ctl_sock); + free(ibuf); + + return (0); +} + +int +show_summary_msg(struct imsg *imsg, int type) +{ + struct switch_control *sw; + struct macaddr *mac; + struct timeval tv; + static unsigned int sw_id = 0; + + switch (imsg->hdr.type) { + case IMSG_CTL_SWITCH: + IMSG_SIZE_CHECK(imsg, sw); + sw = imsg->data; + sw_id = sw->sw_id; + + if (!(type == SHOW_SUM || type == SHOW_SWITCHES)) + break; + printf("%-4u\t%-4s\t%-8s\t%-24s\n", + sw->sw_id, "", "switch", + print_host(&sw->sw_addr, NULL, 0)); + break; + case IMSG_CTL_MAC: + IMSG_SIZE_CHECK(imsg, mac); + mac = imsg->data; + + if (!(type == SHOW_SUM || type == SHOW_MACS)) + break; + + getmonotime(&tv); + printf("%-4u\t%-4ld\t%-8s\t%-24s\tage %llds\n", + sw_id, mac->mac_port, "mac", + print_ether(mac->mac_addr), + (long long)tv.tv_sec - mac->mac_age); + break; + case IMSG_CTL_END: + return (1); + default: + errx(1, "wrong message in summary: %u", imsg->hdr.type); + break; + } + return (0); +} + +struct imsgname * +monitor_lookup(uint8_t type) +{ + int i; + + for (i = 0; imsgs[i].name != NULL; i++) + if (imsgs[i].type == type) + return (&imsgs[i]); + return (&imsgunknown); +} + +int +monitor(struct imsg *imsg) +{ + time_t now; + int done = 0; + struct imsgname *imn; + + now = time(NULL); + + imn = monitor_lookup(imsg->hdr.type); + printf("%s: imsg type %u len %u peerid %u pid %d\n", imn->name, + imsg->hdr.type, imsg->hdr.len, imsg->hdr.peerid, imsg->hdr.pid); + printf("\ttimestamp: %lld, %s", (long long)now, ctime(&now)); + if (imn->type == -1) + done = 1; + if (imn->func != NULL) + (*imn->func)(imsg); + + return (done); +} diff --git a/usr.sbin/switchd/Makefile b/usr.sbin/switchd/Makefile new file mode 100644 index 00000000000..2f546532cbb --- /dev/null +++ b/usr.sbin/switchd/Makefile @@ -0,0 +1,36 @@ +# $OpenBSD: Makefile,v 1.1 2016/07/19 16:54:26 reyk Exp $ + +NAME= switch + +PROG= ${NAME}d +MAN= ${NAME}d.8 ${NAME}d.conf.5 + +SRCS= imsg_util.c log.c packet.c switch.c timer.c util.c +SRCS+= switchd.c ofp.c ofp10.c ofp13.c control.c proc.c +SRCS+= ${.OBJDIR}/ofp_map.c ${.OBJDIR}/ofp10_map.c +SRCS+= parse.y ofcconn.c + +LDADD= -levent -lutil +DPADD= ${LIBEVENT} ${LIBUTIL} + +CFLAGS+= -DSWITCHD_NAME=\"${NAME}\" +CFLAGS+= -Wall -I${.CURDIR} -I${.CURDIR}/../${NAME}d +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare + +GENERATED= ofp_map.c ofp10_map.c +CLEANFILES+= ${GENERATED} *~ + +ofp_map.c: genmap.sh ${.CURDIR}/ofp.h + /bin/sh ${.CURDIR}/genmap.sh -i ${.CURDIR}/ofp.h -t ofp \ + -m ${.CURDIR}/ofp_map.h -h '"ofp.h"' > $@ + @touch $@ + +ofp10_map.c: genmap.sh ${.CURDIR}/ofp10.h + /bin/sh ${.CURDIR}/genmap.sh -i ${.CURDIR}/ofp10.h -t ofp10 \ + -m ${.CURDIR}/ofp_map.h -h '"ofp10.h"' > $@ + @touch $@ + +.include diff --git a/usr.sbin/switchd/control.c b/usr.sbin/switchd/control.c new file mode 100644 index 00000000000..bd979430a61 --- /dev/null +++ b/usr.sbin/switchd/control.c @@ -0,0 +1,373 @@ +/* $OpenBSD: control.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2010-2016 Reyk Floeter + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "proc.h" +#include "switchd.h" + +#define CONTROL_BACKLOG 5 + +struct ctl_connlist ctl_conns; + +void + control_accept(int, short, void *); +struct ctl_conn + *control_connbyfd(int); +void control_close(int, struct control_sock *); +void control_dispatch_imsg(int, short, void *); +void control_imsg_forward(struct imsg *); + +int +control_init(struct privsep *ps, struct control_sock *cs) +{ + struct switchd *env = ps->ps_env; + struct sockaddr_un sun; + int fd; + mode_t old_umask, mode; + + if (cs->cs_name == NULL) + return (0); + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + log_warn("%s: socket", __func__); + return (-1); + } + + sun.sun_family = AF_UNIX; + if (strlcpy(sun.sun_path, cs->cs_name, + sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) { + log_warn("%s: %s name too long", __func__, cs->cs_name); + close(fd); + return (-1); + } + + if (unlink(cs->cs_name) == -1) + if (errno != ENOENT) { + log_warn("%s: unlink %s", __func__, cs->cs_name); + close(fd); + return (-1); + } + + if (cs->cs_restricted) { + old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH); + mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; + } else { + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP; + } + + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("%s: bind: %s", __func__, cs->cs_name); + close(fd); + (void)umask(old_umask); + return (-1); + } + (void)umask(old_umask); + + if (chmod(cs->cs_name, mode) == -1) { + log_warn("%s: chmod", __func__); + close(fd); + (void)unlink(cs->cs_name); + return (-1); + } + + socket_set_blockmode(fd, BM_NONBLOCK); + cs->cs_fd = fd; + cs->cs_env = env; + + return (0); +} + +int +control_listen(struct control_sock *cs) +{ + if (cs->cs_name == NULL) + return (0); + + if (listen(cs->cs_fd, CONTROL_BACKLOG) == -1) { + log_warn("%s: listen", __func__); + return (-1); + } + + event_set(&cs->cs_ev, cs->cs_fd, EV_READ, + control_accept, cs); + event_add(&cs->cs_ev, NULL); + evtimer_set(&cs->cs_evt, control_accept, cs); + + return (0); +} + +void +control_cleanup(struct control_sock *cs) +{ + if (cs->cs_name == NULL) + return; + event_del(&cs->cs_ev); + event_del(&cs->cs_evt); + (void)unlink(cs->cs_name); +} + +/* ARGSUSED */ +void +control_accept(int listenfd, short event, void *arg) +{ + struct control_sock *cs = arg; + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + + event_add(&cs->cs_ev, NULL); + if ((event & EV_TIMEOUT)) + return; + + len = sizeof(sun); + if ((connfd = accept(listenfd, + (struct sockaddr *)&sun, &len)) == -1) { + /* + * Pause accept if we are out of file descriptors, or + * libevent will haunt us here too. + */ + if (errno == ENFILE || errno == EMFILE) { + struct timeval evtpause = { 1, 0 }; + + event_del(&cs->cs_ev); + evtimer_add(&cs->cs_evt, &evtpause); + } else if (errno != EWOULDBLOCK && errno != EINTR && + errno != ECONNABORTED) + log_warn("%s: accept", __func__); + return; + } + + socket_set_blockmode(connfd, BM_NONBLOCK); + + if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { + log_warn("%s", __func__); + close(connfd); + return; + } + + imsg_init(&c->iev.ibuf, connfd); + c->iev.handler = control_dispatch_imsg; + c->iev.events = EV_READ; + c->iev.data = cs; + event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, + c->iev.handler, c->iev.data); + event_add(&c->iev.ev, NULL); + + TAILQ_INSERT_TAIL(&ctl_conns, c, entry); +} + +struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->iev.ibuf.fd != fd; + c = TAILQ_NEXT(c, entry)) + ; /* nothing */ + + return (c); +} + +void +control_close(int fd, struct control_sock *cs) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warn("%s: fd %d: not found", __func__, fd); + return; + } + + msgbuf_clear(&c->iev.ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->iev.ev); + close(c->iev.ibuf.fd); + + /* Some file descriptors are available again. */ + if (evtimer_pending(&cs->cs_evt, NULL)) { + evtimer_del(&cs->cs_evt); + event_add(&cs->cs_ev, NULL); + } + + free(c); +} + +/* ARGSUSED */ +void +control_dispatch_imsg(int fd, short event, void *arg) +{ + struct control_sock *cs = arg; + struct switchd *env = cs->cs_env; + struct ctl_conn *c; + struct imsg imsg; + int n, v; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warn("%s: fd %d: not found", __func__, fd); + return; + } + + if (event & EV_READ) { + if ((n = imsg_read(&c->iev.ibuf)) == -1 || n == 0) { + control_close(fd, cs); + return; + } + } + if (event & EV_WRITE) { + if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { + control_close(fd, cs); + return; + } + } + + for (;;) { + if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { + control_close(fd, cs); + return; + } + + if (n == 0) + break; + + control_imsg_forward(&imsg); + + switch (imsg.hdr.type) { + case IMSG_CTL_SHOW_SUM: + /* Forward request and use control fd as _id_ */ + proc_compose(&env->sc_ps, PROC_OFP, + imsg.hdr.type, &fd, sizeof(fd)); + break; + case IMSG_CTL_DEVICE_CONNECT: + case IMSG_CTL_DEVICE_DISCONNECT: + proc_compose(&env->sc_ps, PROC_PARENT, + imsg.hdr.type, imsg.data, IMSG_DATA_SIZE(&imsg)); + break; + case IMSG_CTL_NOTIFY: + if (c->flags & CTL_CONN_NOTIFY) { + log_debug("%s: " + "client requested notify more than once", + __func__); + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + break; + } + c->flags |= CTL_CONN_NOTIFY; + break; + case IMSG_CTL_VERBOSE: + IMSG_SIZE_CHECK(&imsg, &v); + + memcpy(&v, imsg.data, sizeof(v)); + log_verbose(v); + + proc_forward_imsg(&env->sc_ps, &imsg, PROC_PARENT, -1); + proc_forward_imsg(&env->sc_ps, &imsg, PROC_OFP, -1); + break; + default: + log_debug("%s: error handling imsg %d", + __func__, imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->iev); +} + +void +control_imsg_forward(struct imsg *imsg) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) + if (c->flags & CTL_CONN_NOTIFY) + imsg_compose(&c->iev.ibuf, imsg->hdr.type, + 0, imsg->hdr.pid, -1, imsg->data, + imsg->hdr.len - IMSG_HEADER_SIZE); +} + +int control_dispatch_ofp(int, struct privsep_proc *, struct imsg *); + +static struct privsep_proc procs[] = { + { "ofp", PROC_OFP, control_dispatch_ofp }, + { "parent", PROC_PARENT, NULL }, + { "ofcconn", PROC_OFCCONN, NULL } +}; + +pid_t +control(struct privsep *ps, struct privsep_proc *p) +{ + return (proc_run(ps, p, procs, nitems(procs), NULL, NULL)); +} + +int +control_dispatch_ofp(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + int cfd; + struct ctl_conn *c; + uint8_t *d = imsg->data; + size_t s; + + switch (imsg->hdr.type) { + case IMSG_CTL_SWITCH: + case IMSG_CTL_MAC: + IMSG_SIZE_CHECK(imsg, &cfd); + memcpy(&cfd, d, sizeof(cfd)); + + if ((c = control_connbyfd(cfd)) == NULL) + fatalx("invalid control connection"); + + s = IMSG_DATA_SIZE(imsg) - sizeof(cfd); + d += sizeof(cfd); + imsg_compose_event(&c->iev, imsg->hdr.type, 0, 0, -1, d, s); + return (0); + case IMSG_CTL_END: + IMSG_SIZE_CHECK(imsg, &cfd); + memcpy(&cfd, d, sizeof(cfd)); + + if ((c = control_connbyfd(cfd)) == NULL) + fatalx("invalid control connection"); + + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); + return (0); + + default: + break; + } + + return (-1); +} diff --git a/usr.sbin/switchd/genmap.sh b/usr.sbin/switchd/genmap.sh new file mode 100644 index 00000000000..76ecee1497c --- /dev/null +++ b/usr.sbin/switchd/genmap.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# $OpenBSD: genmap.sh,v 1.1 2016/07/19 16:54:26 reyk Exp $ + +# Copyright (c) 2010-2013 Reyk Floeter +# +# 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. + +TOKEN="" +MAPFILE="" +INPUT="" +HEADER="" + +args=`getopt i:o:h:t:m: $*` + +if [ $? -ne 0 ]; then + echo "usage: $0 -i input -h header -t token [-m mapfile]" + exit 1 +fi + +set -- $args +while [ $# -ne 0 ]; do + case "$1" in + -i) + INPUT="$2"; shift; shift; + ;; + -h) + HEADER="$2"; shift; shift; + ;; + -t) + TOKEN="$2"; shift; shift; + ;; + -m) + MAPFILE="$2"; shift; shift; + ;; + --) + shift; + break + ;; + esac +done + +if [ -z "$MAPFILE" ]; then + MAPFILE=$INPUT +fi + +TOK=$(echo ${TOKEN} | tr "[:lower:]" "[:upper:]") +tok=$(echo ${TOKEN} | tr "[:upper:]" "[:lower:]") +INC="#include ${HEADER}" + +MAP=$(grep "struct constmap ${tok}_" $MAPFILE | + sed -Ee "s/.*${tok}_([^_]+)_map.*/\1/g") + +# Print license/copyright notice and headers +cat < + +#include "types.h" +${INC} + +EOF + +for i in $MAP; do + lower=$(echo $i | tr "[:upper:]" "[:lower:]") + upper=$(echo $i | tr "[:lower:]" "[:upper:]") + + echo "struct constmap ${tok}_${lower}_map[] = {" + + X="${TOK}_${upper}_" + grep "$X" $INPUT | grep -v '\\' | sed -Ee \ + "s/#define.*${X}([^[:blank:]]+).*\/\* (.+) \*\/$\ +/ { ${X}\1, \"\1\", \"\2\" },/" | grep -v '\#define' + + echo " { 0 }" + echo "};" +done diff --git a/usr.sbin/switchd/imsg_util.c b/usr.sbin/switchd/imsg_util.c new file mode 100644 index 00000000000..9274276f62e --- /dev/null +++ b/usr.sbin/switchd/imsg_util.c @@ -0,0 +1,226 @@ +/* $OpenBSD: imsg_util.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2010-2016 Reyk Floeter + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "switchd.h" + +/* + * Extending the imsg buffer API for internal use + */ + +int +ibuf_cat(struct ibuf *dst, struct ibuf *src) +{ + return (ibuf_add(dst, src->buf, ibuf_size(src))); +} + +void +ibuf_zero(struct ibuf *buf) +{ + memset(buf->buf, 0, buf->wpos); +} + +void +ibuf_reset(struct ibuf *buf) +{ + ibuf_zero(buf); + buf->rpos = buf->wpos = 0; +} + +struct ibuf * +ibuf_new(void *data, size_t len) +{ + struct ibuf *buf; + + if ((buf = ibuf_dynamic(len, + SWITCHD_MSGBUF_MAX)) == NULL) + return (NULL); + + ibuf_zero(buf); + + if (data == NULL && len) { + if (ibuf_advance(buf, len) == NULL) { + ibuf_free(buf); + return (NULL); + } + } else { + if (ibuf_add(buf, data, len) != 0) { + ibuf_free(buf); + return (NULL); + } + } + + return (buf); +} + +struct ibuf * +ibuf_static(void) +{ + struct ibuf *buf; + + if ((buf = ibuf_open(SWITCHD_MSGBUF_MAX)) == NULL) + return (NULL); + + ibuf_zero(buf); + + return (buf); +} + +void * +ibuf_advance(struct ibuf *buf, size_t len) +{ + void *ptr; + + if ((ptr = ibuf_reserve(buf, len)) != NULL) + memset(ptr, 0, len); + + return (ptr); +} + +void +ibuf_release(struct ibuf *buf) +{ + if (buf == NULL) + return; + if (buf->buf != NULL) + free(buf->buf); + free(buf); +} + +size_t +ibuf_length(struct ibuf *buf) +{ + if (buf == NULL || buf->buf == NULL) + return (0); + return (ibuf_size(buf)); +} + +uint8_t * +ibuf_data(struct ibuf *buf) +{ + return (ibuf_seek(buf, 0, 0)); +} + +void * +ibuf_getdata(struct ibuf *buf, size_t len) +{ + void *data; + + if ((data = ibuf_seek(buf, buf->rpos, len)) == NULL) + return (NULL); + buf->rpos += len; + + return (data); +} + +ssize_t +ibuf_dataleft(struct ibuf *buf) +{ + return (buf->wpos - buf->rpos); +} + +size_t +ibuf_dataoffset(struct ibuf *buf) +{ + return (buf->rpos); +} + +struct ibuf * +ibuf_get(struct ibuf *buf, size_t len) +{ + void *data; + + if ((data = ibuf_getdata(buf, len)) == NULL) + return (NULL); + + return (ibuf_new(data, len)); +} + +struct ibuf * +ibuf_dup(struct ibuf *buf) +{ + if (buf == NULL) + return (NULL); + return (ibuf_new(ibuf_data(buf), ibuf_size(buf))); +} + +struct ibuf * +ibuf_random(size_t len) +{ + struct ibuf *buf; + void *ptr; + + if ((buf = ibuf_open(len)) == NULL) + return (NULL); + if ((ptr = ibuf_reserve(buf, len)) == NULL) { + ibuf_free(buf); + return (NULL); + } + arc4random_buf(ptr, len); + return (buf); +} + +int +ibuf_setsize(struct ibuf *buf, size_t len) +{ + if (len > buf->size) + return (-1); + buf->wpos = len; + return (0); +} + +int +ibuf_prepend(struct ibuf *buf, void *data, size_t len) +{ + struct ibuf *new; + + /* Swap buffers (we could also use memmove here) */ + if ((new = ibuf_new(data, len)) == NULL) + return (-1); + if (ibuf_cat(new, buf) == -1) { + ibuf_release(new); + return (-1); + } + free(buf->buf); + memcpy(buf, new, sizeof(*buf)); + free(new); + + return (0); +} diff --git a/usr.sbin/switchd/log.c b/usr.sbin/switchd/log.c new file mode 100644 index 00000000000..ecd94e44674 --- /dev/null +++ b/usr.sbin/switchd/log.c @@ -0,0 +1,206 @@ +/* $OpenBSD: log.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include +#include +#include +#include +#include +#include + +int debug; +int verbose; +const char *log_procname; + +void log_init(int, int); +void log_procinit(const char *); +void log_verbose(int); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +void +log_init(int n_debug, int facility) +{ + extern char *__progname; + + debug = n_debug; + verbose = n_debug; + log_procinit(__progname); + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, facility); + + tzset(); +} + +void +log_procinit(const char *procname) +{ + if (procname != NULL) + log_procname = procname; +} + +void +log_verbose(int v) +{ + verbose = v; +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); +} + + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_CRIT, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_CRIT, emsg, ap); + logit(LOG_CRIT, "%s", strerror(errno)); + } else { + vlog(LOG_CRIT, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_CRIT, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose > 1) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +static void +vfatal(const char *emsg, va_list ap) +{ + static char s[BUFSIZ]; + const char *sep; + + if (emsg != NULL) { + (void)vsnprintf(s, sizeof(s), emsg, ap); + sep = ": "; + } else { + s[0] = '\0'; + sep = ""; + } + if (errno) + logit(LOG_CRIT, "%s: %s%s%s", + log_procname, s, sep, strerror(errno)); + else + logit(LOG_CRIT, "%s%s%s", log_procname, sep, s); +} + +void +fatal(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatal(emsg, ap); + va_end(ap); + exit(1); +} + +void +fatalx(const char *emsg, ...) +{ + va_list ap; + + errno = 0; + va_start(ap, emsg); + vfatal(emsg, ap); + va_end(ap); + exit(1); +} diff --git a/usr.sbin/switchd/ofcconn.c b/usr.sbin/switchd/ofcconn.c new file mode 100644 index 00000000000..8a90fc0366e --- /dev/null +++ b/usr.sbin/switchd/ofcconn.c @@ -0,0 +1,509 @@ +/* $OpenBSD: ofcconn.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2016 YASUOKA Masahiko + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ofp.h" +#include "ofp10.h" +#include "types.h" +#include "switchd.h" + +int ofcconn_dispatch_parent(int, struct privsep_proc *, struct imsg *); + +static struct privsep_proc procs[] = { + { "parent", PROC_PARENT, ofcconn_dispatch_parent }, + { "control", PROC_CONTROL, NULL }, + { "ofp", PROC_OFP, NULL } +}; + +/* OpenFlow Channel Connection */ +struct ofcconn { + char *oc_device; + struct sockaddr_storage oc_peer; + int oc_sock; + int oc_devf; + int oc_sock_write_ready; + int oc_devf_write_ready; + int oc_connected; + int oc_conn_fails; + struct ibuf *oc_buf; + TAILQ_ENTRY(ofcconn) oc_next; + struct event oc_evsock; + struct event oc_evdevf; + struct event oc_evtimer; +}; + +TAILQ_HEAD(, ofcconn) ofcconn_list = TAILQ_HEAD_INITIALIZER(ofcconn_list); + +struct ofcconn *ofcconn_create(const char *, struct switch_controller *, int); +int ofcconn_connect(struct ofcconn *); +void ofcconn_on_sockio(int, short, void *); +void ofcconn_on_devfio(int, short, void *); +int ofcconn_write(struct ofcconn *); +void ofcconn_connect_again(struct ofcconn *); +void ofcconn_on_timer(int, short, void *); +void ofcconn_reset_evsock(struct ofcconn *); +void ofcconn_reset_evdevf(struct ofcconn *); +void ofcconn_close(struct ofcconn *); +void ofcconn_free(struct ofcconn *); +void ofcconn_shutdown_all(void); +int ofcconn_say_hello(struct ofcconn *); + +pid_t +ofcconn_proc_init(struct privsep *ps, struct privsep_proc *p) +{ + return (proc_run(ps, p, procs, nitems(procs), NULL, NULL)); +} + +void +ofcconn_proc_shutdown(void) +{ + struct ofcconn *e, *t; + + TAILQ_FOREACH_SAFE(e, &ofcconn_list, oc_next, t) { + ofcconn_close(e); + ofcconn_free(e); + } +} + +int +ofcconn_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + struct ofcconn *conn; + struct switch_device *sdv; + struct switch_controller *swc; + + switch (imsg->hdr.type) { + case IMSG_CTL_DEVICE_CONNECT: + if (IMSG_DATA_SIZE(imsg) < sizeof(*sdv)) { + log_warnx("%s: IMSG_CTL_DEVICE_CONNECT: " + "invalid message size", __func__); + return (0); + } + sdv = imsg->data; + swc = &sdv->sdv_swc; + if ((conn = ofcconn_create(sdv->sdv_device, swc, + imsg->fd)) != NULL) + ofcconn_connect(conn); + return (0); + case IMSG_CTL_DEVICE_DISCONNECT: + if (IMSG_DATA_SIZE(imsg) < sizeof(*sdv)) { + log_warnx("%s: IMSG_CTL_DEVICE_DISCONNECT: " + "invalid message size", __func__); + return (0); + } + sdv = imsg->data; + TAILQ_FOREACH(conn, &ofcconn_list, oc_next) { + if (!strcmp(conn->oc_device, sdv->sdv_device)) + break; + } + if (conn) { + log_warnx("%s: closed by request", + conn->oc_device); + ofcconn_close(conn); + ofcconn_free(conn); + } + return (0); + default: + break; + } + + return (-1); +} + +struct ofcconn * +ofcconn_create(const char *name, struct switch_controller *swc, int fd) +{ + struct ofcconn *oc = NULL; + + if ((oc = calloc(1, sizeof(struct ofcconn))) == NULL) { + log_warn("%s: calloc() failed", __func__); + goto on_error; + } + if ((oc->oc_device = strdup(name)) == NULL) { + log_warn("%s: calloc() failed", __func__); + goto on_error; + } + if ((oc->oc_buf = ibuf_new(NULL, 0)) == NULL) { + log_warn("%s: ibuf_new() failed", __func__); + goto on_error; + } + + oc->oc_sock = -1; + oc->oc_devf = fd; + TAILQ_INSERT_TAIL(&ofcconn_list, oc, oc_next); + + memcpy(&oc->oc_peer, &swc->swc_addr, sizeof(oc->oc_peer)); + + if (ntohs(((struct sockaddr_in *)&oc->oc_peer)->sin_port) == 0) + ((struct sockaddr_in *)&oc->oc_peer)->sin_port = + htons(SWITCHD_CTLR_PORT); + + evtimer_set(&oc->oc_evtimer, ofcconn_on_timer, oc); + + return (oc); + +on_error: + if (oc != NULL) { + free(oc->oc_device); + ibuf_release(oc->oc_buf); + } + free(oc); + + return (NULL); +} + +int +ofcconn_connect(struct ofcconn *oc) +{ + int sock = -1; + char buf[256]; + struct timeval tv; + + if ((sock = socket(oc->oc_peer.ss_family, SOCK_STREAM | SOCK_NONBLOCK, + IPPROTO_TCP)) == -1) { + log_warn("%s: opening of channel with %s failed. " + "socket()", oc->oc_device, + print_host(&oc->oc_peer, buf, sizeof(buf))); + goto on_error; + } + + if (connect(sock, (struct sockaddr *)&oc->oc_peer, + oc->oc_peer.ss_len) == -1) { + if (errno != EINPROGRESS) { + log_warn("%s: opening OpenFlow channel with %s " + "failed. connect()", oc->oc_device, + print_host(&oc->oc_peer, buf, sizeof(buf))); + goto on_error; + } + } + + oc->oc_sock = sock; + event_set(&oc->oc_evsock, oc->oc_sock, EV_READ|EV_WRITE, + ofcconn_on_sockio, oc); + event_set(&oc->oc_evdevf, oc->oc_devf, EV_READ|EV_WRITE, + ofcconn_on_devfio, oc); + event_add(&oc->oc_evdevf, NULL); + event_add(&oc->oc_evsock, NULL); + + tv.tv_sec = SWITCHD_OFCCONN_TIMEOUT; + tv.tv_usec = 0; + event_add(&oc->oc_evtimer, &tv); + + return (0); + +on_error: + if (sock >= 0) + close(sock); + + oc->oc_conn_fails++; + ofcconn_connect_again(oc); + + return (-1); +} + +void +ofcconn_on_sockio(int fd, short evmask, void *ctx) +{ + struct ofcconn *oc = ctx; + ssize_t sz; + size_t wpos; + char buf[256]; + int err; + socklen_t optlen; + + if (evmask & EV_WRITE) { + if (oc->oc_connected == 0) { + optlen = sizeof(err); + getsockopt(oc->oc_sock, SOL_SOCKET, SO_ERROR, &err, + &optlen); + if (err != 0) { + log_warnx("%s: opening OpenFlow channel with " + "%s failed: %s", + oc->oc_device, print_host(&oc->oc_peer, + buf, sizeof(buf)), strerror(err)); + oc->oc_conn_fails++; + ofcconn_close(oc); + ofcconn_connect_again(oc); + return; + } + log_info("%s: OpenFlow channel with %s connected", + oc->oc_device, + print_host(&oc->oc_peer, buf, sizeof(buf))); + event_del(&oc->oc_evtimer); + oc->oc_connected = 1; + oc->oc_conn_fails = 0; + if (ofcconn_say_hello(oc) != 0) + return; + } else { + oc->oc_sock_write_ready = 1; + /* schedule an event to reset the event handlers */ + event_active(&oc->oc_evdevf, 0, 1); + } + } + + if (evmask & EV_READ && ibuf_left(oc->oc_buf) > 0) { + wpos = ibuf_length(oc->oc_buf); + + /* XXX temporally fix not to access unallocated area */ + if (wpos + ibuf_left(oc->oc_buf) > oc->oc_buf->size) { + ibuf_reserve(oc->oc_buf, ibuf_left(oc->oc_buf)); + ibuf_setsize(oc->oc_buf, wpos); + } + + if ((sz = read(oc->oc_sock, + ibuf_data(oc->oc_buf) + wpos, + ibuf_left(oc->oc_buf))) <= 0) { + if (sz == 0) + log_warnx("%s: OpenFlow channel is closed by " + "peer", + oc->oc_device); + else + log_warn("%s: OpenFlow channel read error", + oc->oc_device); + goto on_fail; + } + if (ibuf_setsize(oc->oc_buf, wpos + sz) == -1) + goto on_fail; + if (oc->oc_devf_write_ready) { + if (ofcconn_write(oc) == -1) + goto on_fail; + event_active(&oc->oc_evdevf, 0, 1); + } + } + ofcconn_reset_evsock(oc); + + return; +on_fail: + ofcconn_close(oc); + ofcconn_connect_again(oc); +} + +void +ofcconn_on_devfio(int fd, short evmask, void *ctx) +{ + struct ofcconn *oc = ctx; + static char buf[65536];/* max size of OpenFlow message */ + size_t sz, sz2; + struct ofp_header *hdr; + + if (evmask & EV_WRITE) { + oc->oc_devf_write_ready = 1; + if (ofcconn_write(oc) == -1) + goto on_fail; + } + + if (evmask & EV_READ && oc->oc_sock_write_ready) { + if ((sz = read(oc->oc_devf, buf, sizeof(buf))) <= 0) { + if (sz < 0) + log_warn("%s: %s read()", __func__, + oc->oc_device); + goto on_fail; + } + hdr = (struct ofp_header *)buf; + if (hdr->oh_type == OFP_T_HELLO) + goto dont_forward; + if ((sz2 = write(oc->oc_sock, buf, sz)) != sz) { + log_warn("%s: %s write()", __func__, oc->oc_device); + goto on_fail; + } + oc->oc_sock_write_ready = 0; + /* schedule an event to reset the event handlers */ + event_active(&oc->oc_evsock, 0, 1); +dont_forward: ; + } + ofcconn_reset_evdevf(oc); + + return; +on_fail: + ofcconn_close(oc); + ofcconn_connect_again(oc); +} + +void +ofcconn_connect_again(struct ofcconn *oc) +{ + struct timeval tv; + const int ofcconn_backoffs[] = { 1, 2, 4, 8, 16 }; + + tv.tv_sec = (oc->oc_conn_fails < (int)nitems(ofcconn_backoffs)) + ? ofcconn_backoffs[oc->oc_conn_fails] + : ofcconn_backoffs[nitems(ofcconn_backoffs) - 1]; + tv.tv_usec = 0; + event_add(&oc->oc_evtimer, &tv); +} + +void +ofcconn_on_timer(int fd, short evmask, void *ctx) +{ + struct ofcconn *oc = ctx; + char buf[256]; + + if (oc->oc_sock < 0) + ofcconn_connect(oc); + else if (!oc->oc_connected) { + log_warnx("%s: opening OpenFlow channel with %s failed: " + "timeout", oc->oc_device, + print_host(&oc->oc_peer, buf, sizeof(buf))); + ofcconn_close(oc); + oc->oc_conn_fails++; + ofcconn_connect_again(oc); + } +} + +void +ofcconn_reset_evsock(struct ofcconn *oc) +{ + short evmask = 0, oevmask; + + oevmask = event_pending(&oc->oc_evsock, EV_READ|EV_WRITE, NULL); + + if (ibuf_left(oc->oc_buf) > 0) + evmask |= EV_READ; + if (!oc->oc_sock_write_ready) + evmask |= EV_WRITE; + + if (oevmask != evmask) { + if (oevmask) + event_del(&oc->oc_evsock); + event_set(&oc->oc_evsock, oc->oc_sock, evmask, + ofcconn_on_sockio, oc); + event_add(&oc->oc_evsock, NULL); + } +} + +void +ofcconn_reset_evdevf(struct ofcconn *oc) +{ + short evmask = 0, oevmask; + + oevmask = event_pending(&oc->oc_evdevf, EV_READ|EV_WRITE, NULL); + + if (oc->oc_sock_write_ready) + evmask |= EV_READ; + if (!oc->oc_devf_write_ready) + evmask |= EV_WRITE; + + if (oevmask != evmask) { + if (oevmask) + event_del(&oc->oc_evdevf); + event_set(&oc->oc_evdevf, oc->oc_devf, evmask, + ofcconn_on_devfio, oc); + event_add(&oc->oc_evdevf, NULL); + } +} + +int +ofcconn_write(struct ofcconn *oc) +{ + struct ofp_header *hdr; + size_t sz, pktlen; + void *pkt; + /* XXX */ + u_char buf[65535]; + int remain = 0; + + /* Try to write if the OFP header has arrived */ + if (!oc->oc_devf_write_ready || + (hdr = ibuf_seek(oc->oc_buf, 0, sizeof(*hdr))) == NULL) + return (0); + + /* Check the length in the OFP header */ + pktlen = ntohs(hdr->oh_length); + + if ((pkt = ibuf_seek(oc->oc_buf, 0, pktlen)) != NULL) { + hdr = pkt; + if (hdr->oh_type == OFP_T_HELLO) + goto dont_forward; + /* Has entire packet already */ + if ((sz = write(oc->oc_devf, pkt, pktlen)) != pktlen) { + log_warn("%s: %s(%d, %d)", __func__, oc->oc_device, + (int)sz, (int)pktlen); + return (-1); + } +dont_forward: + /* XXX preserve the remaining part */ + if ((remain = oc->oc_buf->wpos - pktlen) > 0) + memmove(buf, (caddr_t)pkt + pktlen, remain); + ibuf_reset(oc->oc_buf); + oc->oc_devf_write_ready = 0; + } + /* XXX put the remaining part again */ + if (remain > 0) + ibuf_add(oc->oc_buf, buf, remain); + + return (0); +} + +void +ofcconn_close(struct ofcconn *oc) +{ + if (oc->oc_sock >= 0) { + event_del(&oc->oc_evsock); + close(oc->oc_sock); + oc->oc_sock = -1; + oc->oc_sock_write_ready = 0; + } + event_del(&oc->oc_evdevf); + event_del(&oc->oc_evtimer); + oc->oc_connected = 0; +} + +void +ofcconn_free(struct ofcconn *oc) +{ + if (oc == NULL) + return; + close(oc->oc_devf); + TAILQ_REMOVE(&ofcconn_list, oc, oc_next); + ibuf_release(oc->oc_buf); + free(oc->oc_device); + free(oc); +} + +int +ofcconn_say_hello(struct ofcconn *oc) +{ + struct ofp_header hdr; + ssize_t sz; + + hdr.oh_version = OFP_V_1_3; + hdr.oh_type = OFP_T_HELLO; + hdr.oh_length = htons(sizeof(hdr)); + hdr.oh_xid = htonl(0xffffffffUL); + + if ((sz = write(oc->oc_sock, &hdr, sizeof(hdr))) != sz) { + log_warn("%s: %s write()", __func__, oc->oc_device); + ofcconn_close(oc); + ofcconn_connect_again(oc); + return (-1); + } + + return (0); +} diff --git a/usr.sbin/switchd/ofp.c b/usr.sbin/switchd/ofp.c new file mode 100644 index 00000000000..1efe9c8ff64 --- /dev/null +++ b/usr.sbin/switchd/ofp.c @@ -0,0 +1,336 @@ +/* $OpenBSD: ofp.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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 +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ofp.h" +#include "ofp10.h" +#include "switchd.h" + +int ofp_dispatch_control(int, struct privsep_proc *, struct imsg *); +int ofp_dispatch_parent(int, struct privsep_proc *, struct imsg *); +void ofp_init(struct privsep *, struct privsep_proc *, void *); +int ofp_add_device(struct switchd *, int, const char *); + +static unsigned int id = 0; + +static struct privsep_proc procs[] = { + { "control", PROC_CONTROL, ofp_dispatch_control }, + { "parent", PROC_PARENT, ofp_dispatch_parent }, + { "ofcconn", PROC_OFCCONN, NULL } +}; + +static TAILQ_HEAD(, switch_connection) conn_head = + TAILQ_HEAD_INITIALIZER(conn_head); + +pid_t +ofp(struct privsep *ps, struct privsep_proc *p) +{ + struct switchd *sc = ps->ps_env; + struct switch_server *srv = &sc->sc_server; + pid_t pid; + + if ((sc->sc_tap = switchd_tap()) == -1) + fatal("tap"); + + log_info("listen on %s", print_host(&srv->srv_addr, NULL, 0)); + + if ((srv->srv_fd = switchd_listen((struct sockaddr *) + &srv->srv_addr)) == -1) + fatal("listen"); + + pid = proc_run(ps, p, procs, nitems(procs), ofp_init, NULL); + close(srv->srv_fd); + + return (pid); +} + +void +ofp_init(struct privsep *ps, struct privsep_proc *p, void *arg) +{ + struct switchd *sc = ps->ps_env; + struct switch_server *srv = &sc->sc_server; + + event_set(&srv->srv_ev, srv->srv_fd, EV_READ, ofp_accept, srv); + event_add(&srv->srv_ev, NULL); +} + +int +ofp_dispatch_control(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_SUM: + return (switch_dispatch_control(fd, p, imsg)); + default: + break; + } + + return (-1); +} + +int +ofp_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + struct switchd *sc = p->p_ps->ps_env; + struct switch_device *sdv; + struct switch_connection *c; + + switch (imsg->hdr.type) { + case IMSG_CTL_DEVICE_CONNECT: + case IMSG_CTL_DEVICE_DISCONNECT: + if (IMSG_DATA_SIZE(imsg) < sizeof(*sdv)) { + log_warnx("%s: IMSG_CTL_DEVICE_CONNECT: " + "message size is wrong", __func__); + return (0); + } + sdv = imsg->data; + if (imsg->hdr.type == IMSG_CTL_DEVICE_CONNECT) + ofp_add_device(sc, imsg->fd, sdv->sdv_device); + else { + TAILQ_FOREACH(c, &conn_head, con_next) { + if (c->con_peer.ss_family == AF_UNIX && + strcmp(sdv->sdv_device, + ((struct sockaddr_un *)&c->con_peer) + ->sun_path) == 0) + break; + } + if (c) + ofp_close(c); + } + return (0); + default: + + break; + } + + return (-1); +} + +void +ofp_close(struct switch_connection *con) +{ + log_info("%s: connection %u closed", __func__, con->con_id); + switch_remove(con->con_sc, con->con_switch); + close(con->con_fd); + TAILQ_REMOVE(&conn_head, con, con_next); +} + +void +ofp_read(int fd, short event, void *arg) +{ + uint8_t buf[SWITCHD_READ_BUFFER]; + struct switch_connection *con = arg; + struct switch_control *sw; + struct switchd *sc = con->con_sc; + struct ofp_header *oh; + ssize_t len; + struct ibuf *ibuf = NULL; + + event_add(&con->con_ev, NULL); + if ((event & EV_TIMEOUT)) + goto fail; + + if ((len = read(fd, buf, sizeof(buf))) == -1) + goto fail; + if (len == 0) + return; + + if ((ibuf = ibuf_new(buf, len)) == NULL) + goto fail; + + sw = con->con_switch; + log_debug("%s: connection %d: %ld bytes from switch %u", __func__, + con->con_id, len, sw == NULL ? 0 : sw->sw_id); + + if ((oh = ibuf_seek(ibuf, 0, sizeof(*oh))) == NULL) { + log_debug("short header"); + goto fail; + } + + switch (oh->oh_version) { + case OFP_V_1_0: + ofp10_input(sc, con, oh, ibuf); + break; + case OFP_V_1_3: + ofp13_input(sc, con, oh, ibuf); + break; + case OFP_V_1_1: + case OFP_V_1_2: + ofp10_debug(sc, &con->con_peer, &con->con_local, oh, ibuf); + /* FALLTHROUGH */ + default: + ofp10_debug(sc, &con->con_peer, &con->con_local, oh, ibuf); + ofp10_hello(sc, con, oh, ibuf); + break; + } + + return; + + fail: + ibuf_release(ibuf); + ofp_close(con); +} + +int +ofp_send(struct switch_connection *con, struct ofp_header *oh, + struct ibuf *obuf) +{ + struct iovec iov[2]; + int cnt = 0; + void *data; + size_t len; + + if (oh != NULL) { + iov[cnt].iov_base = oh; + iov[cnt++].iov_len = sizeof(*oh); + } + + if (ibuf_length(obuf)) { + if (oh != NULL && (ibuf_seek(obuf, 0, sizeof(*oh)) == NULL)) + return (-1); + len = ibuf_dataleft(obuf); + if (len < 0) { + return (-1); + } else if (len > 0 && + (data = ibuf_getdata(obuf, len)) != NULL) { + iov[cnt].iov_base = data; + iov[cnt++].iov_len = len; + } + } + + if (cnt == 0) + return (-1); + + /* XXX */ + if (writev(con->con_fd, iov, cnt) == -1) + return (-1); + + return (0); +} + +int +ofp_add_device(struct switchd *sc, int fd, const char *name) +{ + struct switch_connection *con = NULL; + struct sockaddr_un *sun; + struct switch_control *sw; + + if ((con = calloc(1, sizeof(*con))) == NULL) { + log_warn("calloc"); + goto fail; + } + con->con_fd = fd; + con->con_sc = sc; + con->con_id = ++id; + sun = (struct sockaddr_un *)&con->con_peer; + sun->sun_family = AF_LOCAL; + strlcpy(sun->sun_path, name, sizeof(sun->sun_path)); + + /* Get associated switch, if it exists */ + sw = switch_get(con); + + log_info("%s: new device %u (%s) from switch %u", + __func__, con->con_id, name, sw == NULL ? 0 : sw->sw_id); + + bzero(&con->con_ev, sizeof(con->con_ev)); + event_set(&con->con_ev, con->con_fd, EV_READ, ofp_read, con); + event_add(&con->con_ev, NULL); + + TAILQ_INSERT_TAIL(&conn_head, con, con_next); + + return (0); +fail: + if (fd != -1) + close(fd); + free(con); + + return (-1); +} + +void +ofp_accept(int fd, short event, void *arg) +{ + struct switch_server *server = arg; + struct switch_connection *con = NULL; + struct switchd *sc = server->srv_sc; + struct switch_control *sw; + struct sockaddr_storage ss; + socklen_t slen; + int s; + + event_add(&server->srv_ev, NULL); + if ((event & EV_TIMEOUT)) + return; + + /* XXX accept_reserve() */ + slen = sizeof(ss); + if ((s = accept(fd, (struct sockaddr *)&ss, &slen)) == -1) { + log_warn("accept"); + goto fail; + } + + if ((con = calloc(1, sizeof(*con))) == NULL) { + log_warn("calloc"); + goto fail; + } + + slen = sizeof(con->con_local); + if (getsockname(s, (struct sockaddr *)&con->con_local, &slen) == -1) { + log_warn("getsockname"); + goto fail; + } + + con->con_fd = s; + con->con_sc = sc; + con->con_id = ++id; + con->con_port = htons(socket_getport(&ss)); + memcpy(&con->con_peer, &ss, sizeof(ss)); + + /* Get associated switch, if it exists */ + sw = switch_get(con); + + log_info("%s: new connection %u from switch %u", + __func__, con->con_id, sw == NULL ? 0 : sw->sw_id); + + bzero(&con->con_ev, sizeof(con->con_ev)); + event_set(&con->con_ev, con->con_fd, EV_READ, ofp_read, con); + event_add(&con->con_ev, NULL); + + TAILQ_INSERT_TAIL(&conn_head, con, con_next); + + return; + fail: + if (s != -1) + close(s); + free(con); +} diff --git a/usr.sbin/switchd/ofp.h b/usr.sbin/switchd/ofp.h new file mode 100644 index 00000000000..ba9ec7166e7 --- /dev/null +++ b/usr.sbin/switchd/ofp.h @@ -0,0 +1,848 @@ +/* $OpenBSD: ofp.h,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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. + */ + +#ifndef _NET_OFP_H_ +#define _NET_OFP_H_ + +#include +#include + +#include +#include +#include + +#define OFP_IFNAMSIZ 16 /* on-wire (not IF_NAMSIZE) */ + +struct ofp_header { + uint8_t oh_version; /* OpenFlow version */ + uint8_t oh_type; /* message type */ + uint16_t oh_length; /* message length */ + uint32_t oh_xid; /* transaction Id */ +} __packed; + +/* OpenFlow version */ +#define OFP_V_0 0x00 /* OpenFlow 0.0 */ +#define OFP_V_1_0 0x01 /* OpenFlow 1.0 */ +#define OFP_V_1_1 0x02 /* OpenFlow 1.1 */ +#define OFP_V_1_2 0x03 /* OpenFlow 1.2 */ +#define OFP_V_1_3 0x04 /* OpenFlow 1.3 */ +#define OFP_V_1_4 0x05 /* OpenFlow 1.4 */ +#define OFP_V_1_5 0x06 /* OpenFlow 1.5 */ + +/* OpenFlow message type */ +#define OFP_T_HELLO 0 /* Hello */ +#define OFP_T_ERROR 1 /* Error */ +#define OFP_T_ECHO_REQUEST 2 /* Echo Request */ +#define OFP_T_ECHO_REPLY 3 /* Echo Reply */ +#define OFP_T_EXPERIMENTER 4 /* Vendor/Experimenter */ +#define OFP_T_FEATURES_REQUEST 5 /* Features Request (switch) */ +#define OFP_T_FEATURES_REPLY 6 /* Features Reply (switch) */ +#define OFP_T_GET_CONFIG_REQUEST 7 /* Get Config Request (switch) */ +#define OFP_T_GET_CONFIG_REPLY 8 /* Get Config Reply (switch) */ +#define OFP_T_SET_CONFIG 9 /* Set Config (switch) */ +#define OFP_T_PACKET_IN 10 /* Packet In (async) */ +#define OFP_T_FLOW_REMOVED 11 /* Flow Removed (async) */ +#define OFP_T_PORT_STATUS 12 /* Port Status (async) */ +#define OFP_T_PACKET_OUT 13 /* Packet Out (controller) */ +#define OFP_T_FLOW_MOD 14 /* Flow Mod (controller) */ +#define OFP_T_GROUP_MOD 15 /* Group Mod (controller) */ +#define OFP_T_PORT_MOD 16 /* Port Mod (controller) */ +#define OFP_T_TABLE_MOD 17 /* Table Mod (controller) */ +#define OFP_T_MULTIPART_REQUEST 18 /* Multipart Message Request */ +#define OFP_T_MULTIPART_REPLY 19 /* Multipart Message Request */ +#define OFP_T_BARRIER_REQUEST 20 /* Barrier Request */ +#define OFP_T_BARRIER_REPLY 21 /* Barrier Reply */ +#define OFP_T_QUEUE_GET_CONFIG_REQUEST 22 /* Queue Get Config Request */ +#define OFP_T_QUEUE_GET_CONFIG_REPLY 23 /* Queue Get Config Reply */ +#define OFP_T_ROLE_REQUEST 24 /* Role Request */ +#define OFP_T_ROLE_REPLY 25 /* Role Reply */ +#define OFP_T_GET_ASYNC_REQUEST 26 /* Get Async Request */ +#define OFP_T_GET_ASYNC_REPLY 27 /* Get Async Reply */ +#define OFP_T_SET_ASYNC 28 /* Set Async */ +#define OFP_T_METER_MOD 29 /* Meter Mod */ +#define OFP_T_TYPE_MAX 30 + +/* OpenFlow Hello Message */ +struct ofp_hello_element_header { + uint16_t he_type; + uint16_t he_length; +}; + +#define OPF_HELLO_T_VERSION_BITMAP 1 /* Supported version bitmap */ + +struct ofp_hello_element_versionbitmap { + uint16_t hev_type; + uint16_t hev_length; +}; + +/* Ports */ +#define OFP_PORT_MAX 0xffffff00 /* Maximum number of physical ports */ +#define OFP_PORT_INPUT 0xfffffff8 /* Send back to input port */ +#define OFP_PORT_FLOWTABLE 0xfffffff9 /* Perform actions in flow table */ +#define OFP_PORT_NORMAL 0xfffffffa /* Let switch decide */ +#define OFP_PORT_FLOOD 0xfffffffb /* All non-block ports except input */ +#define OFP_PORT_ALL 0xfffffffc /* All ports except input */ +#define OFP_PORT_CONTROLLER 0xfffffffd /* Send to controller */ +#define OFP_PORT_LOCAL 0xfffffffe /* Local virtual OpenFlow port */ +#define OFP_PORT_ANY 0xffffffff /* No port */ + +/* Switch Config Message (reply) */ +struct ofp_switch_config { + struct ofp_header cfg_oh; /* OpenFlow header */ + uint16_t cfg_flags; /* Configuration flags */ + uint16_t cfg_miss_send_len; /* Max bytes from datapath */ +} __packed; + +/* Switch Config */ +#define OFP_CONFIG_FRAG_NORMAL 0x0000 /* No special frag handling */ +#define OFP_CONFIG_FRAG_DROP 0x0001 /* Drop fragments */ +#define OFP_CONFIG_FRAG_REASM 0x0002 /* Reassemble fragments */ +#define OFP_CONFIG_FRAG_MASK 0x0003 /* Fragment mask */ + +/* Switch port description */ +struct ofp_switch_port { + uint32_t swp_number; /* Switch port number */ + uint8_t swp_pad[4]; /* Padding */ + uint8_t swp_macaddr[ETHER_ADDR_LEN]; /* Port MAC address */ + uint8_t swp_pad2[2]; /* Padding */ + char swp_name[OFP_IFNAMSIZ]; /* Switch port name */ + uint32_t swp_config; /* Configuration flags */ + uint32_t swp_state; /* State flags */ + uint32_t swp_cur; /* Current features */ + uint32_t swp_advertised; /* Advertised by the port */ + uint32_t swp_supported; /* Supported by the port */ + uint32_t swp_peer; /* Advertised by peer */ + uint32_t swp_cur_speed; /* Current port speed in Kbps*/ + uint32_t swp_max_speed; /* Support port max speed in Kbps*/ +}; + +/* Physical port configuration */ +#define OFP_PORTCONFIG_PORT_DOWN 0x1 /* Port is down */ +#define OFP_PORTCONFIG_NO_STP 0x2 /* Disable STP on port */ +#define OFP_PORTCONFIG_NO_RECV 0x4 /* Drop everything except STP */ +#define OFP_PORTCONFIG_NO_RECV_STP 0x8 /* Drop received STP */ +#define OFP_PORTCONFIG_NO_FLOOD 0x10 /* Do not flood to this port */ +#define OFP_PORTCONFIG_NO_FWD 0x20 /* Drop packets to port */ +#define OFP_PORTCONFIG_NO_PACKET_IN 0x40 /* NO PACKET_IN on port */ + +/* Physical port state */ +#define OFP_PORTSTATE_LINK_DOWN 0x1 /* Link not active */ +#define OFP_PORTSTATE_STP_LISTEN 0x000 /* Not learning or forwarding */ +#define OFP_PORTSTATE_STP_LEARN 0x100 /* Learning but not forwarding */ +#define OFP_PORTSTATE_STP_FORWARD 0x200 /* Learning and forwarding */ +#define OFP_PORTSTATE_STP_BLOCK 0x300 /* Not part of spanning tree */ +#define OFP_PORTSTATE_STP_MASK 0x300 /* Spanning tree values */ + +/* Physical port media types */ +#define OFP_PORTMEDIA_10MB_HD 0x1 /* 10 Mb half-duplex */ +#define OFP_PORTMEDIA_10MB_FD 0x2 /* 10 Mb full-duplex */ +#define OFP_PORTMEDIA_100MB_HD 0x4 /* 100 Mb half-duplex */ +#define OFP_PORTMEDIA_100MB_FD 0x8 /* 100 Mb full-duplex */ +#define OFP_PORTMEDIA_1GB_HD 0x10 /* 1 Gb half-duplex */ +#define OFP_PORTMEDIA_1GB_FD 0x20 /* 1 Gb full-duplex */ +#define OFP_PORTMEDIA_10GB_FD 0x40 /* 10 Gb full-duplex */ +#define OFP_PORTMEDIA_COPPER 0x80 /* Copper */ +#define OFP_PORTMEDIA_FIBER 0x100 /* Fiber */ +#define OFP_PORTMEDIA_AUTONEG 0x200 /* Auto-negotiation */ +#define OFP_PORTMEDIA_PAUSE 0x400 /* Pause */ +#define OFP_PORTMEDIA_PAUSE_ASYM 0x800 /* Asymmetric pause */ + +/* Switch Features Message (reply) */ +struct ofp_switch_features { + struct ofp_header swf_oh; /* OpenFlow header */ + uint64_t swf_datapath_id; /* Datapath unique ID */ + uint32_t swf_nbuffers; /* Max packets buffered */ + uint8_t swf_ntables; /* Number of supported tables */ + uint8_t swf_aux_id; /* Identify auxiliary connections */ + uint8_t swf_pad[2]; /* Align to 64 bits */ + uint32_t swf_capabilities; /* Capability flags */ + uint32_t swf_actions; /* Supported action flags */ +}; + +/* Switch capabilities */ +#define OFP_SWCAP_FLOW_STATS 0x1 /* Flow statistics */ +#define OFP_SWCAP_TABLE_STATS 0x2 /* Table statistics */ +#define OFP_SWCAP_PORT_STATS 0x4 /* Port statistics */ +#define OFP_SWCAP_GROUP_STATS 0x8 /* Group statistics */ +#define OFP_SWCAP_IP_REASM 0x20 /* Can reassemble IP frags */ +#define OFP_SWCAP_QUEUE_STATS 0x40 /* Queue statistics */ +#define OFP_SWCAP_ARP_MATCH_IP 0x80 /* Match IP addresses in ARP pkts */ +#define OFP_SWCAP_PORT_BLOCKED 0x100 /* Switch will block ports */ + +/* Flow matching */ +struct ofp_match { + uint16_t om_type; + uint16_t om_length; +} __packed; + +/* Flow matching type type */ +#define OFP_MATCH_STANDARD 0 /* Standard match deprecated */ +#define OFP_MATCH_OXM 1 /* OpenFlow Extensible Match */ + +/* Packet-In Message */ +struct ofp_packet_in { + struct ofp_header pin_oh; /* OpenFlow header */ + uint32_t pin_buffer_id; + uint16_t pin_total_len; + uint8_t pin_reason; + uint8_t pin_table_id; + uint64_t pin_cookie; + struct ofp_match pin_match; +} __packed; + +/* Reason */ +#define OFP_PKTIN_REASON_NO_MATCH 0 /* No matching flow */ +#define OFP_PKTIN_REASON_ACTION 1 /* Explicit output */ +#define OFP_PKTIN_REASON_TTL 2 /* Packet has invalid TTL */ + +/* Flow Instruction */ +struct ofp_instruction { + uint16_t i_type; + uint16_t i_len; +}; + +/* Instruction types */ +#define OFP_INSTRUCTION_T_GOTO_TABLE 1 /* Goto-Table */ +#define OFP_INSTRUCTION_T_WRITE_META 2 /* Write-Metadata */ +#define OFP_INSTRUCTION_T_WRITE_ACTIONS 3 /* Write-Actions */ +#define OFP_INSTRUCTION_T_APPLY_ACTIONS 4 /* Apply-Actions */ +#define OFP_INSTRUCTION_T_CLEAR_ACTIONS 5 /* Clear-Actions */ +#define OFP_INSTRUCTION_T_METER 6 /* Meter */ +#define OFP_INSTRUCTION_T_EXPERIMENTER 0xffff /* Experimenter */ + +/* Write-Metadata instruction */ +struct ofp_instruction_write_metadata { + uint16_t iwm_type; + uint16_t iwm_len; + uint8_t iwm_pad[4]; + uint64_t iwm_metadata; + uint64_t iwm_metadata_mask; +}; + +/* Goto-Table instruction */ +struct ofp_instruction_goto_table { + uint16_t igt_type; + uint16_t igt_len; + uint8_t igt_table_id; + uint8_t igt_pad[3]; +}; + +/* Apply-Actions instruction */ +struct ofp_instruction_actions { + uint16_t ia_type; + uint16_t ia_len; + uint8_t pad[4]; +}; + +/* Meter instruction */ +struct ofp_instruction_meter { + uint16_t im_type; + uint16_t im_len; + uint32_t im_meter_id; +}; + +/* Experimenter instruction */ +struct ofp_instruction_experimenter { + uint16_t ie_type; + uint16_t ie_len; + uint32_t ie_experimenter; +}; + +/* Actions */ +#define OFP_ACTION_OUTPUT 0 /* Output to switch port */ +#define OFP_ACTION_COPY_TTL_OUT 11 /* */ +#define OFP_ACTION_COPY_TTL_IN 12 /* */ +#define OFP_ACTION_SET_MPLS_TTL 15 /* */ +#define OFP_ACTION_DEC_MPLS_TTL 16 /* */ +#define OFP_ACTION_PUSH_VLAN 17 /* */ +#define OFP_ACTION_POP_VLAN 18 /* */ +#define OFP_ACTION_PUSH_MPLS 19 /* */ +#define OFP_ACTION_POP_MPLS 20 /* */ +#define OFP_ACTION_SET_QUEUE 21 /* */ +#define OFP_ACTION_GROUP 22 /* */ +#define OFP_ACTION_SET_NW_TTL 23 /* */ +#define OFP_ACTION_DEC_NW_TTL 24 /* */ +#define OFP_ACTION_SET_FIELD 25 /* */ +#define OFP_ACTION_PUSH_PBB 26 /* */ +#define OFP_ACTION_POP_PBB 27 /* */ +#define OFP_ACTION_EXPERIMENTER 0xffff /* Vendor-specific action */ + +/* Action Header */ +struct ofp_action_header { + uint16_t ah_type; + uint16_t ah_len; + uint32_t ah_pad; +} __packed; + +#define OFP_CONTROLLER_MAXLEN_MAX 0xffe5 +#define OFP_CONTROLLER_MAXLEN_NO_BUFFER 0xffff + +/* Output Action */ +struct ofp_action_output { + uint16_t ao_type; + uint16_t ao_len; + uint32_t ao_port; + uint16_t ao_max_len; + uint8_t ao_pad[6]; +} __packed; + +struct ofp_action_mpls_ttl { + uint16_t amt_type; + uint16_t amt_len; + uint8_t amt_ttl; + uint8_t amt_pad[3]; +} __packed; + +struct ofp_action_push { + uint16_t ap_type; + uint16_t ap_len; + uint16_t ap_ethertype; + uint8_t pad[2]; +} __packed; + +struct ofp_action_pop_mpls { + uint16_t apm_type; + uint16_t apm_len; + uint16_t apm_ethertype; + uint8_t pad[2]; +} __packed; + +struct ofp_action_group { + uint16_t ag_type; + uint16_t ag_len; + uint32_t ag_group_id; +} __packed; + +struct ofp_action_nw_ttl { + uint16_t ant_type; + uint16_t ant_len; + uint8_t ant_ttl; + uint8_t ant_pad[3]; +} __packed; + +struct ofp_action_set_field { + uint16_t asf_type; + uint16_t asf_len; + uint8_t asf_field[4]; +} __packed; + +/* Packet-Out Message */ +struct ofp_packet_out { + struct ofp_header pout_oh; /* OpenFlow header */ + uint32_t pout_buffer_id; + uint32_t pout_in_port; + uint16_t pout_actions_len; + uint8_t pout_pad[6]; + struct ofp_action_header pout_actions[0]; + /* Followed by optional packet data if buffer_id == 0xffffffff */ +} __packed; + +/* Flow match fields for basic class */ +#define OFP_XM_T_IN_PORT 0 +#define OFP_XM_T_IN_PHY_PORT 1 +#define OFP_XM_T_META 2 +#define OFP_XM_T_ETH_DST 3 +#define OFP_XM_T_ETH_SRC 4 +#define OFP_XM_T_ETH_TYPE 5 +#define OFP_XM_T_VLAN_VID 6 +#define OFP_XM_T_VLAN_PCP 7 +#define OFP_XM_T_IP_DSCP 8 +#define OFP_XM_T_IP_ECN 9 +#define OFP_XM_T_IP_PROTO 10 +#define OFP_XM_T_IPV4_SRC 11 +#define OFP_XM_T_IPV4_DST 12 +#define OFP_XM_T_TCP_SRC 13 +#define OFP_XM_T_TCP_DST 14 +#define OFP_XM_T_UDP_SRC 15 +#define OFP_XM_T_UDP_DST 16 +#define OFP_XM_T_SCTP_SRC 17 +#define OFP_XM_T_SCTP_DST 18 +#define OFP_XM_T_ICMPV4_TYPE 19 +#define OFP_XM_T_ICMPV4_CODE 20 +#define OFP_XM_T_ARP_OP 21 +#define OFP_XM_T_ARP_SPA 22 +#define OFP_XM_T_ARP_TPA 23 +#define OFP_XM_T_ARP_SHA 24 +#define OFP_XM_T_ARP_THA 25 +#define OFP_XM_T_IPV6_SRC 26 +#define OFP_XM_T_IPV6_DST 27 +#define OFP_XM_T_IPV6_FLABEL 28 +#define OFP_XM_T_ICMPV6_TYPE 29 +#define OFP_XM_T_ICMPV6_CODE 30 +#define OFP_XM_T_IPV6_ND_TARGET 31 +#define OFP_XM_T_IPV6_ND_SLL 32 +#define OFP_XM_T_IPV6_ND_TLL 33 +#define OFP_XM_T_MPLS_LABEL 34 +#define OFP_XM_T_MPLS_TC 35 +#define OFP_XM_T_MPLS_BOS 36 +#define OFP_XM_T_PBB_ISID 37 +#define OFP_XM_T_TUNNEL_ID 38 +#define OFP_XM_T_IPV6_EXTHDR 39 +#define OFP_XM_T_MAX 40 + +/* Flow match fields for nxm1 class */ +#define OFP_XM_NXMT_TUNNEL_ID 38 +#define OFP_XM_NXMT_TUNNEL_IPV4_SRC 31 +#define OFP_XM_NXMT_TUNNEL_IPV4_DST 32 +#define OFP_XM_NXMT_TUNNEL_IPV6_SRC 109 +#define OFP_XM_NXMT_TUNNEL_IPV6_DST 110 + +/* OXM class */ +#define OFP_OXM_C_NXM_0 0x0000 +#define OFP_OXM_C_NXM_1 0x0001 +#define OFP_OXM_C_OPENFLOW_BASIC 0x8000 +#define OFP_OXM_C_OPENFLOW_EXPERIMENTER 0xffff + +/* VLAN matching flag */ +#define OFP_XM_VID_PRESENT 0x1000 +#define OFP_XM_VID_NONE 0x0000 + +struct ofp_ox_match { + uint16_t oxm_class; + uint8_t oxm_fh; + uint8_t oxm_length; +}; + +#define OFP_OXM_GET_FIELD(o) (((o)->oxm_fh) >> 1) +#define OFP_OXM_GET_HASMASK(o) (((o)->oxm_fh) & 0x1) +#define OFP_OXM_SET_FIELD(o, t) (((o)->oxm_fh) = ((t) << 1)) +#define OFP_OXM_SET_HASMASK(o) (((o)->oxm_fh) |= 0x1) + +/* Flow modification commands */ +#define OFP_FLOWCMD_ADD 0 /* Add new flow */ +#define OFP_FLOWCMD_MODIFY 1 /* Modify flow */ +#define OFP_FLOWCMD_MODIFY_STRICT 2 /* Modify flow w/o wildcard */ +#define OFP_FLOWCMD_DELETE 3 /* Delete flow */ +#define OFP_FLOWCMD_DELETE_STRICT 4 /* Delete flow w/o wildcard */ + +/* Flow modification flags */ +#define OFP_FLOWFLAG_SEND_FLOW_REMOVED 0x01 /* Send flow removed message */ +#define OFP_FLOWFLAG_CHECK_OVERLAP 0x02 /* Check flow overlap first */ +#define OFP_FLOWFLAG_RESET_COUNTS 0x04 /* Reset flow packet and byte counters */ +#define OFP_FLOWFLAG_NO_PACKET_COUNTS 0x08 /* Don't keep track of packet count */ +#define OFP_FLOWFLAG_NO_BYTE_COUNTS 0x10 /* Don't keep track of byte count */ + +/* Flow modification message */ +struct ofp_flow_mod { + struct ofp_header fm_oh; /* OpenFlow header */ + uint64_t fm_cookie; + uint64_t fm_cookie_mask; + uint8_t fm_table_id; + uint8_t fm_command; + uint16_t fm_idle_timeout; + uint16_t fm_hard_timeout; + uint16_t fm_priority; + uint32_t fm_buffer_id; + uint32_t fm_out_port; + uint32_t fm_out_group; + uint16_t fm_flags; + uint8_t fm_pad[2]; + struct ofp_match fm_match; +} __packed; + +/* Flow removed reasons */ +#define OFP_FLOWREM_REASON_IDLE_TIMEOUT 0 /* Flow idle time exceeded idle_timeout */ +#define OFP_FLOWREM_REASON_HARD_TIMEOUT 1 /* Time exceeded hard_timeout */ +#define OFP_FLOWREM_REASON_DELETE 2 /* Evicted by a DELETE flow mod */ +#define OFP_FLOWREM_REASON_GROUP_DELETE 3 /* Group was removed */ + +/* Flow removed message */ +struct ofp_flow_removed { + struct ofp_header fr_oh; + uint64_t fr_cookie; + uint16_t fr_priority; + uint8_t fr_reason; + uint8_t fr_table_id; + uint32_t fr_duration_sec; + uint32_t fr_duration_nsec; + uint16_t fr_idle_timeout; + uint16_t fr_hard_timeout; + uint64_t fr_packet_count; + uint64_t fr_byte_count; + struct ofp_match fr_match; +} __packed; + +/* Error message */ +struct ofp_error { + struct ofp_header err_oh; + uint16_t err_type; + uint16_t err_code; + uint8_t err_data[0]; + /* Followed by optional data */ +} __packed; + +/* Error types */ +#define OFP_ERRTYPE_HELLO_FAILED 0 /* Hello protocol failed */ +#define OFP_ERRTYPE_BAD_REQUEST 1 /* Request was not understood */ +#define OFP_ERRTYPE_BAD_ACTION 2 /* Error in action */ +#define OFP_ERRTYPE_BAD_INSTRUCTION 3 /* Error in instruction list */ +#define OFP_ERRTYPE_BAD_MATCH 4 /* Error in match */ +#define OFP_ERRTYPE_FLOW_MOD_FAILED 5 /* Problem modifying flow */ +#define OFP_ERRTYPE_GROUP_MOD_FAILED 6 /* Problem modifying group */ +#define OFP_ERRTYPE_PORT_MOD_FAILED 7 /* Port mod request failed */ +#define OFP_ERRTYPE_TABLE_MOD_FAILED 8 /* Port mod request failed */ +#define OFP_ERRTYPE_QUEUE_OP_FAILED 9 /* Queue operation failed */ +#define OFP_ERRTYPE_SWITCH_CFG_FAILED 10 /* Switch Config request failed */ +#define OFP_ERRTYPE_ROLE_REQUEST_FAILED 11 /* Controller role request failed */ +#define OFP_ERRTYPE_METER_MOD_FAILED 12 /* Error in meter */ +#define OFP_ERRTYPE_TABLE_FEATURES_FAILED 13 /* Setting table features failed */ +#define OFP_ERRTYPE_EXPERIMENTER 0xffff /* Experimenter error message */ + +/* HELLO error codes */ +#define OFP_ERRHELLO_INCOMPATIBLE 0 /* No compatible version */ +#define OFP_ERRHELLO_EPERM 1 /* Permissions error */ + +/* REQUEST error codes */ +#define OFP_ERRREQ_VERSION 0 /* Version not supported */ +#define OFP_ERRREQ_TYPE 1 /* Type not supported */ +#define OFP_ERRREQ_MULTIPART 2 /* Multipart type not supported */ +#define OFP_ERRREQ_EXPERIMENTER 3 /* Experimenter id not supported */ +#define OFP_ERRREQ_EXP_TYPE 4 /* Experimenter type not supported */ +#define OFP_ERRREQ_EPERM 5 /* Permission error */ +#define OFP_ERRREQ_LEN 6 /* Wrong request length for type */ +#define OFP_ERRREQ_BUFFER_EMPTY 7 /* Specified buffer has already been used */ +#define OFP_ERRREQ_BUFFER_UNKNOWN 8 /* Specified buffer does not exist */ +#define OFP_ERRREQ_TABLE_ID 9 /* Specified table-id invalid or does not exit */ +#define OFP_ERRREQ_IS_SLAVE 10 /* Denied because controller is slave */ +#define OFP_ERRREQ_PORT 11 /* Invalid port */ +#define OFP_ERRREQ_PACKET 12 /* Invalid packet in packet-out */ +#define OFP_ERRREQ_MULTIPART_OVERFLOW 13 /* Multipart overflowed the assigned buffer */ + +/* ACTION error codes */ +#define OFP_ERRACTION_TYPE 0 /* Unknown or unsupported action type */ +#define OFP_ERRACTION_LEN 1 /* Length problem in actions */ +#define OFP_ERRACTION_EXPERIMENTER 2 /* Unknown experimenter id specified */ +#define OFP_ERRACTION_EXP_TYPE 3 /* Unknown action for experimenter id */ +#define OFP_ERRACTION_OUT_PORT 4 /* Problem validating output port */ +#define OFP_ERRACTION_ARGUMENT 5 /* Bad action argument */ +#define OFP_ERRACTION_EPERM 6 /* Permission error */ +#define OFP_ERRACTION_TOO_MANY 7 /* Can't handle this many actions */ +#define OFP_ERRACTION_BAD_QUEUE 8 /* Problem validating output queue */ +#define OFP_ERRACTION_BAD_OUT_GROPU 9 /* Invalid group id in forward action */ +#define OFP_ERRACTION_MATCH_INCONSIST 10 /* Action can't apply or Set-Field failed */ +#define OFP_ERRACTION_UNSUPPORTED_ORDER 11 /* Action order is unsupported for Apply-Actions */ +#define OFP_ERRACTION_TAG 12 /* Actions uses an unsupported tag/encap */ +#define OFP_ERRACTION_SET_TYPE 13 /* Unsupported type in SET_FIELD action */ +#define OFP_ERRACTION_SET_LEN 14 /* Length problem in SET_FIELD action */ +#define OFP_ERRACTION_SET_ARGUMENT 15 /* Bad argument in SET_FIELD action */ + +/* INSTRUCTION error codes */ +#define OFP_ERRINST_UNKNOWN_INST 0 /* Unknown instruction */ +#define OFP_ERRINST_UNSUPPORTED_INST 1 /* Switch or table does not support */ +#define OFP_ERRINST_TABLE_ID 2 /* Invalid Table-ID specified */ +#define OFP_ERRINST_UNSUPP_META 3 /* Metadata value unsupported by datapath */ +#define OFP_ERRINST_UNSUPP_META_MASK 4 /* Metadata mask value unsupported by datapath */ +#define OFP_ERRINST_BAD_EXPERIMENTER 5 /* Unknown experimenter id specified */ +#define OFP_ERRINST_BAD_EXPERIMENTER_TYPE 6 /* Unknown instruction for experimenter id */ +#define OFP_ERRINST_BAD_LEN 7 /* Length problem in instructions */ +#define OFP_ERRINST_EPERM 8 /* Permissions error */ + +/* MATCH error codes */ +#define OFP_ERRMATCH_BAD_TYPE 0 /* Unsupported match type */ +#define OFP_ERRMATCH_BAD_LEN 1 /* Length problem in match */ +#define OFP_ERRMATCH_BAD_TAG 2 /* Match uses an unsupported tag/encap */ +#define OFP_ERRMATCH_BAD_DL_ADDR_MASK 3 /* Unsupported datalink addr mask */ +#define OFP_ERRMATCH_BAD_NW_ADDR_MASK 4 /* Unsupported network addr mask */ +#define OFP_ERRMATCH_BAD_WILDCARDS 5 /* Unsupported combination of fields */ +#define OFP_ERRMATCH_BAD_FIELD 6 /* Unsupported field type in the match */ +#define OFP_ERRMATCH_BAD_VALUE 7 /* Unsupported value in a match field */ +#define OFP_ERRMATCH_BAD_MASK 8 /* Unsupported mask specified in match */ +#define OFP_ERRMATCH_BAD_PREREQ 9 /* A prerequisite was not met */ +#define OFP_ERRMATCH_DUP_FIELD 10 /* A field type was duplicated */ +#define OFP_ERRMATCH_EPERM 11 /* Permissions error */ + +/* FLOW MOD error codes */ +#define OFP_ERRFLOWMOD_UNKNOWN 0 /* Unknown */ +#define OFP_ERRFLOWMOD_ALL_TABLES_FULL 1 /* Not added, full tables */ +#define OFP_ERRFLOWMOD_TABLE_ID 2 /* Invalid table id */ +#define OFP_ERRFLOWMOD_OVERLAP 3 /* Overlapping flow */ +#define OFP_ERRFLOWMOD_EPERM 4 /* Permissions error */ +#define OFP_ERRFLOWMOD_BAD_TIMEOUT 5 /* non-zero idle/hard timeout */ +#define OFP_ERRFLOWMOD_BAD_COMMAND 6 /* Unsupported or Unknown command */ +#define OFP_ERRFLOWMOD_BAD_FLAGS 7 /* Unsupported or Unknown flags */ + +/* GROUP MOD error codes */ +#define OFP_ERRGROUPMOD_GROUP_EXISTS 0 /* Already present group */ +#define OFP_ERRGROUPMOD_INVALID_GROUP 1 /* Group specified is invalid */ +#define OFP_ERRGROUPMOD_WEIGHT_UNSUPP 2 /* Switch does not support unequal load sharing */ +#define OFP_ERRGROUPMOD_OUT_OF_GROUPS 3 /* The Group table is full */ +#define OFP_ERRGROUPMOD_OUT_OF_BUCKETS 4 /* The maximum number of action buckets */ +#define OFP_ERRGROUPMOD_CHAINING_UNSUPP 5 /* Switch does not support groups forwarding to groups */ +#define OFP_ERRGROUPMOD_WATCH_UNSUPP 6 /* This group cannot watch the watch_port */ +#define OFP_ERRGROUPMOD_LOOP 7 /* Group entry would cause a loop */ +#define OFP_ERRGROUPMOD_UNKNOWN_GROUP 8 /* MODIFY attempted to modify a non-existent group */ +#define OFP_ERRGROUPMOD_CHAINED_GROUP 9 /* Group not deleted because another group is forwarding to it */ +#define OFP_ERRGROUPMOD_BAD_TYPE 10 /* Unsupported or unknown group type */ +#define OFP_ERRGROUPMOD_BAD_COMMAND 11 /* Unsupported or unknown command */ +#define OFP_ERRGROUPMOD_BAD_BUCKET 12 /* Error in bucket */ +#define OFP_ERRGROUPMOD_BAD_WATCH 13 /* Error in watch port/group */ +#define OFP_ERRGROUPMOD_EPERM 14 /* Permission error */ + +/* GROUP MOD message */ +#define OFP_GROUPCMD_ADD 0 /* Add group */ +#define OFP_GROUPCMD_MODIFY 1 /* Modify group */ +#define OFP_GROUPCMD_DELETE 2 /* Delete group */ + +/* Group types */ +#define OFP_GROUP_T_ALL 0 +#define OFP_GROUP_T_SELECT 1 +#define OFP_GROUP_T_INDIRECT 2 +#define OFP_GROUP_T_FAST_FAILOVER 3 + +#define OFP_GROUP_MAX 0xffffff00 +#define OFP_GROUP_ALL 0xfffffffc +#define OFP_GROUP_ANY 0xffffffff + +struct ofp_bucket { + uint16_t b_len; + uint16_t b_weight; + uint32_t b_watch_port; + uint32_t b_watch_group; + uint8_t b_pad[4]; + struct ofp_action_header b_actions[0]; +} __packed; + +struct ofp_group_mod { + struct ofp_header gm_oh; + uint16_t gm_command; + uint8_t gm_type; + uint8_t gm_pad; + uint32_t gm_group_id; + struct ofp_bucket gm_buckets[0]; +} __packed; + +struct ofp_multipart { + struct ofp_header mp_oh; + uint16_t mp_type; + uint16_t mp_flags; + uint8_t mp_pad[4]; +} __packed; + +#define OFP_MP_FLAG_REQ_MORE 1 +#define OFP_MP_FLAG_REPLY_MORE 1 + +/* Multipart types */ +#define OFP_MP_T_DESC 0 +#define OFP_MP_T_FLOW 1 +#define OFP_MP_T_AGGREGATE 2 +#define OFP_MP_T_TABLE 3 +#define OFP_MP_T_PORT_STATS 4 +#define OFP_MP_T_QUEUE 5 +#define OFP_MP_T_GROUP 6 +#define OFP_MP_T_GROUP_DESC 7 +#define OFP_MP_T_GROUP_FEATURES 8 +#define OFP_MP_T_METER 9 +#define OFP_MP_T_METER_CONFIG 10 +#define OFP_MP_T_METER_FEATURES 11 +#define OFP_MP_T_TABLE_FEATURES 12 +#define OFP_MP_T_PORT_DESC 13 +#define OFP_MP_T_EXPERIMENTER 0xffff + +#define OFP_DESC_STR_LEN 256 +#define OFP_SERIAL_NUM_LEN 32 + +struct ofp_desc { + char d_mfr_desc[OFP_DESC_STR_LEN]; + char d_hw_desc[OFP_DESC_STR_LEN]; + char d_sw_desc[OFP_DESC_STR_LEN]; + char d_serial_num[OFP_SERIAL_NUM_LEN]; + char d_dp_desc[OFP_DESC_STR_LEN]; +} __packed; + +/* Flow stats request */ +struct ofp_flow_stats_request { + uint8_t fsr_table_id; + uint8_t fsr_pad[3]; + uint32_t fsr_out_port; + uint32_t fsr_out_group; + uint8_t fsr_pad2[4]; + uint64_t fsr_cookie; + uint64_t fsr_cookie_mask; + struct ofp_match fsr_match; +} __packed; + +/* Flow stats */ +struct ofp_flow_stats { + uint16_t fs_length; + uint8_t fs_table_id; + uint8_t fs_pad; + uint32_t fs_duration_sec; + uint32_t fs_duration_nsec; + uint16_t fs_priority; + uint16_t fs_idle_timeout; + uint16_t fs_hard_timeout; + uint16_t fs_flags; + uint8_t fs_pad2[4]; + uint64_t fs_cookie; + uint64_t fs_packet_count; + uint64_t fs_byte_count; + struct ofp_match fs_match; +} __packed; + +/* Aggregate flow stats request */ +struct ofp_aggregate_stats_request { + uint8_t asr_table_id; + uint8_t asr_pad[3]; + uint32_t asr_out_port; + uint32_t asr_out_group; + uint8_t asr_pad2[4]; + uint64_t asr_cookie; + uint64_t asr_cookie_mask; + struct ofp_match asr_match; +} __packed; + +struct ofp_aggregate_stats { + uint64_t as_packet_count; + uint64_t as_byte_count; + uint32_t as_flow_count; + uint8_t as_pad[4]; +} __packed; + +#define OFP_TABLE_ID_MAX 0xfe +#define OFP_TABLE_ID_ALL 0xff + +struct ofp_table_stats { + uint8_t ts_table_id; + uint8_t ts_pad[3]; + uint32_t ts_active_count; + uint64_t ts_lookup_count; + uint64_t ts_matched_count; +} __packed; + +/* Table features */ +#define OFP_TABLE_FEATPROP_INSTRUCTION 0 +#define OFP_TABLE_FEATPROP_INSTRUCTION_MISS 1 +#define OFP_TABLE_FEATPROP_NEXT_TABLES 2 +#define OFP_TABLE_FEATPROP_NEXT_TABLES_MISS 3 +#define OFP_TABLE_FEATPROP_WRITE_ACTIONS 4 +#define OFP_TABLE_FEATPROP_WRITE_ACTIONS_MISS 5 +#define OFP_TABLE_FEATPROP_APPLY_ACTIONS 6 +#define OFP_TABLE_FEATPROP_APPLY_ACTIONS_MISS 7 +#define OFP_TABLE_FEATPROP_MATCH 8 +#define OFP_TABLE_FEATPROP_WILDCARDS 10 +#define OFP_TABLE_FEATPROP_WRITE_SETFIELD 12 +#define OFP_TABLE_FEATPROP_WRITE_SETFIELD_MISS 13 +#define OFP_TABLE_FEATPROP_APPLY_SETFIELD 14 +#define OFP_TABLE_FEATPROP_APPLY_SETFIELD_MISS 15 +#define OFP_TABLE_FEATPROP_EXPERIMENTER 0xfffe +#define OFP_TABLE_FEATPROP_EXPERIMENTER_MISS 0xffff + +#define OFP_TABLE_MAX_NAME_LEN 32 + +struct ofp_table_features { + uint16_t tf_length; + uint8_t tf_tableid; + uint8_t tf_pad[5]; + char tf_name[OFP_TABLE_MAX_NAME_LEN]; + uint64_t tf_metadata_match; + uint64_t tf_metadata_write; + uint32_t tf_config; + uint32_t tf_max_entries; +} __packed; + +struct ofp_table_feature_property { + uint16_t tp_type; + uint16_t tp_length; +} __packed; + +struct ofp_table_feature_property_instruction { + uint16_t tpi_type; + uint16_t tpi_length; + struct ofp_instruction tpi_instructions[0]; +} __packed; + +struct ofp_table_feature_property_next_tables { + uint16_t tpnt_type; + uint16_t tpnt_length; + uint8_t tpnt_tables[0]; +} __packed; + +struct ofp_table_feature_property_actions { + uint16_t tpa_type; + uint16_t tpa_length; + struct ofp_action_header tpa_actions[0]; +} __packed; + +struct ofp_table_feature_property_oxm { + uint16_t tpoxm_type; + uint16_t tpoxm_length; + uint32_t tpoxm_oxm[0]; +} __packed; + +struct ofp_table_feature_property_experimenter { + uint16_t tfpexp_type; + uint16_t tfpexp_length; + uint32_t tfpexp_experimenter; + uint32_t tfpexp_exp_type; + uint32_t tfpexp_experimenter_data[0]; +} __packed; + +struct ofp_port_stats { + uint32_t pt_port_no; + uint8_t pt_pad[4]; + uint64_t pt_rx_packets; + uint64_t pt_tx_packets; + uint64_t pt_rx_bytes; + uint64_t pt_tx_bytes; + uint64_t pt_rx_dropped; + uint64_t pt_tx_dropped; + uint64_t pt_rx_errors; + uint64_t pt_tx_errors; + uint64_t pt_rx_frame_err; + uint64_t pt_rx_over_err; + uint64_t pt_rx_crc_err; + uint64_t pt_collision; + uint32_t pt_duration_sec; + uint32_t pt_duration_nsec; +} __packed; + +/* Groups stats request */ +struct ofp_group_stats_request { + uint32_t gsr_group_id; + uint8_t gsr_pad[4]; +} __packed; + +struct ofp_bucket_counter { + uint64_t gs_packet_count; + uint64_t gs_byte_count; +} __packed; + +/* Group stats */ +struct ofp_group_stats { + uint16_t gs_length; + uint8_t gs_pad[2]; + uint32_t gs_group_id; + uint32_t gs_ref_count; + uint8_t gs_pad2[4]; + uint64_t gs_packet_count; + uint64_t gs_byte_count; + uint32_t gs_duration_sec; + uint32_t gs_duration_nsec; + struct ofp_bucket_counter gs_bucket_stats[0]; +} __packed; + +/* Group description */ +struct ofp_group_desc { + uint16_t gd_length; + uint8_t gd_type; + uint8_t gd_pad; + uint32_t gd_group_id; + struct ofp_bucket gd_buckets[0]; +} __packed; + +#endif /* _NET_OPF_H_ */ diff --git a/usr.sbin/switchd/ofp10.c b/usr.sbin/switchd/ofp10.c new file mode 100644 index 00000000000..2a27e061f06 --- /dev/null +++ b/usr.sbin/switchd/ofp10.c @@ -0,0 +1,425 @@ +/* $OpenBSD: ofp10.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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 +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ofp.h" +#include "ofp10.h" +#include "switchd.h" +#include "ofp_map.h" + +int ofp10_echo_request(struct switchd *, struct switch_connection *, + struct ofp_header *, struct ibuf *); +int ofp10_packet_in(struct switchd *, struct switch_connection *, + struct ofp_header *, struct ibuf *); +int ofp10_error(struct switchd *, struct switch_connection *, + struct ofp_header *, struct ibuf *); + +int ofp10_packet_match(struct packet *, struct ofp10_match *, unsigned int); + +int ofp10_debug_packet_in(struct switchd *, + struct sockaddr_storage *, struct sockaddr_storage *, + struct ofp_header *, struct ibuf *); +int ofp10_debug_packet_out(struct switchd *, + struct sockaddr_storage *, struct sockaddr_storage *, + struct ofp_header *, struct ibuf *); +int ofp10_debug_error(struct switchd *, + struct sockaddr_storage *, struct sockaddr_storage *, + struct ofp_header *, struct ibuf *); + +struct ofp_callback ofp10_callbacks[] = { + { OFP10_T_HELLO, ofp10_hello, NULL }, + { OFP10_T_ERROR, NULL, ofp10_debug_error }, + { OFP10_T_ECHO_REQUEST, ofp10_echo_request, NULL }, + { OFP10_T_ECHO_REPLY, NULL, NULL }, + { OFP10_T_EXPERIMENTER, NULL, NULL }, + { OFP10_T_FEATURES_REQUEST, NULL, NULL }, + { OFP10_T_FEATURES_REPLY, NULL, NULL }, + { OFP10_T_GET_CONFIG_REQUEST, NULL, NULL }, + { OFP10_T_GET_CONFIG_REPLY, NULL, NULL }, + { OFP10_T_SET_CONFIG, NULL, NULL }, + { OFP10_T_PACKET_IN, ofp10_packet_in, ofp10_debug_packet_in }, + { OFP10_T_FLOW_REMOVED, NULL, NULL }, + { OFP10_T_PORT_STATUS, NULL, NULL }, + { OFP10_T_PACKET_OUT, NULL, ofp10_debug_packet_out }, + { OFP10_T_FLOW_MOD, NULL, NULL }, + { OFP10_T_PORT_MOD, NULL, NULL }, + { OFP10_T_STATS_REQUEST, NULL, NULL }, + { OFP10_T_STATS_REPLY, NULL, NULL }, + { OFP10_T_BARRIER_REQUEST, NULL, NULL }, + { OFP10_T_BARRIER_REPLY, NULL, NULL }, + { OFP10_T_QUEUE_GET_CONFIG_REQUEST, NULL, NULL }, + { OFP10_T_QUEUE_GET_CONFIG_REPLY, NULL, NULL } +}; + +void +ofp10_debug_header(struct switchd *sc, + struct sockaddr_storage *src, struct sockaddr_storage *dst, + struct ofp_header *oh) +{ + log_debug("%s > %s: version %s type %s length %u xid %u", + print_host(src, NULL, 0), + print_host(dst, NULL, 0), + print_map(oh->oh_version, ofp_v_map), + print_map(oh->oh_type, ofp10_t_map), + ntohs(oh->oh_length), ntohl(oh->oh_xid)); +} + +void +ofp10_debug(struct switchd *sc, + struct sockaddr_storage *src, struct sockaddr_storage *dst, + struct ofp_header *oh, struct ibuf *ibuf) +{ + ofp10_debug_header(sc, src, dst, oh); + + if (ibuf == NULL || + oh->oh_version != OFP_V_1_0 || + oh->oh_type >= OFP_T_TYPE_MAX || + ofp10_callbacks[oh->oh_type].debug == NULL) + return; + if (ofp10_callbacks[oh->oh_type].debug(sc, src, dst, oh, ibuf) != 0) + goto fail; + + return; + fail: + log_debug("\tinvalid packet"); +} + +int +ofp10_debug_packet_in(struct switchd *sc, + struct sockaddr_storage *src, struct sockaddr_storage *dst, + struct ofp_header *oh, struct ibuf *ibuf) +{ + struct ofp10_packet_in *pin; + uint8_t *p; + size_t len; + off_t off; + + off = 0; + if ((pin = ibuf_seek(ibuf, off, sizeof(*pin))) == NULL) + return (-1); + log_debug("\tbuffer %d port %s " + "length %u reason %u", + ntohl(pin->pin_buffer_id), + print_map(ntohs(pin->pin_port), ofp10_port_map), + ntohs(pin->pin_total_len), + pin->pin_reason); + len = ntohs(pin->pin_total_len); + off += sizeof(*pin); + if ((p = ibuf_seek(ibuf, off, len)) == NULL) + return (-1); + if (sc->sc_tap != -1) + (void)write(sc->sc_tap, p, len); + return (0); +} + +int +ofp10_debug_packet_out(struct switchd *sc, + struct sockaddr_storage *src, struct sockaddr_storage *dst, + struct ofp_header *oh, struct ibuf *ibuf) +{ + struct ofp10_packet_out *pout; + size_t len; + off_t off; + struct ofp_action_header *ah; + struct ofp10_action_output *ao; + + off = 0; + if ((pout = ibuf_seek(ibuf, off, sizeof(*pout))) == NULL) { + log_debug("%s: seek failed: length %zd", + __func__, ibuf_length(ibuf)); + return (-1); + } + log_debug("\tbuffer %d port %s " + "actions length %u", + ntohl(pout->pout_buffer_id), + print_map(ntohs(pout->pout_port), ofp10_port_map), + ntohs(pout->pout_actions_len)); + len = ntohs(pout->pout_actions_len); + + off += sizeof(*pout); + while ((ah = ibuf_seek(ibuf, off, len)) != NULL && + ntohs(ah->ah_len) >= sizeof(*ah)) { + switch (ntohs(ah->ah_type)) { + case OFP10_ACTION_OUTPUT: + ao = (struct ofp10_action_output *)ah; + log_debug("\t\taction type %s length %d " + "port %s max length %d", + print_map(ntohs(ao->ao_type), ofp10_action_map), + ntohs(ao->ao_len), + print_map(ntohs(ao->ao_port), ofp10_port_map), + ntohs(ao->ao_max_len)); + break; + default: + log_debug("\t\taction type %s length %d", + print_map(ntohs(ah->ah_type), ofp10_action_map), + ntohs(ah->ah_len)); + break; + } + if (pout->pout_buffer_id == (uint32_t)-1) + break; + off += ntohs(ah->ah_len); + } + + return (0); +} + +int +ofp10_debug_error(struct switchd *sc, + struct sockaddr_storage *src, struct sockaddr_storage *dst, + struct ofp_header *oh, struct ibuf *ibuf) +{ + struct ofp_error *err; + off_t off; + const char *code; + + off = 0; + if ((err = ibuf_seek(ibuf, off, sizeof(*err))) == NULL) { + log_debug("%s: seek failed: length %zd", + __func__, ibuf_length(ibuf)); + return (-1); + } + + switch (ntohs(err->err_type)) { + case OFP10_ERRTYPE_FLOW_MOD_FAILED: + code = print_map(ntohs(err->err_code), ofp10_errflowmod_map); + break; + default: + code = NULL; + break; + } + + log_debug("\terror type %s code %u%s%s", + print_map(ntohs(err->err_type), ofp10_errtype_map), + ntohs(err->err_code), + code == NULL ? "" : ": ", + code == NULL ? "" : code); + + return (0); +} + +int +ofp10_input(struct switchd *sc, struct switch_connection *con, + struct ofp_header *oh, struct ibuf *ibuf) +{ + ofp10_debug(sc, &con->con_peer, &con->con_local, oh, ibuf); + + if (oh->oh_version != OFP_V_1_0 || + oh->oh_type >= OFP_T_TYPE_MAX) { + log_debug("unsupported packet"); + return (-1); + } + + if (ofp10_callbacks[oh->oh_type].cb == NULL) { + log_debug("message not supported: %s", + print_map(oh->oh_type, ofp10_t_map)); + return (-1); + } + if (ofp10_callbacks[oh->oh_type].cb(sc, con, oh, ibuf) != 0) + return (-1); + + return (0); +} + +int +ofp10_hello(struct switchd *sc, struct switch_connection *con, + struct ofp_header *oh, struct ibuf *ibuf) +{ + if (oh->oh_version == OFP_V_1_0 && + switch_add(con) == NULL) { + log_debug("%s: failed to add switch", __func__); + ofp_close(con); + return (-1); + } + + /* Echo back the received Hello packet */ + oh->oh_version = OFP_V_1_0; + oh->oh_length = htons(sizeof(*oh)); + oh->oh_xid = htonl(con->con_xidnxt++); + ofp_send(con, oh, NULL); + ofp10_debug(sc, &con->con_local, &con->con_peer, oh, NULL); + +#if 0 + (void)write(fd, &oh, sizeof(oh)); + ofd_debug(sc, &sname, &con->con_ss, &oh, buf, len); + oh.oh_xid = htonl(1); + oh.oh_type = OFP10_T_FEATURES_REQUEST; + (void)write(fd, &oh, sizeof(oh)); + ofd_debug(sc, &sname, &con->con_ss, &oh, buf, len); + oh.oh_xid = htonl(2); + oh.oh_type = OFP10_T_GET_CONFIG_REQUEST; + (void)write(fd, &oh, sizeof(oh)); +#endif + return (0); +} + +int +ofp10_echo_request(struct switchd *sc, struct switch_connection *con, + struct ofp_header *oh, struct ibuf *ibuf) +{ + /* Echo reply */ + oh->oh_type = OFP10_T_ECHO_REPLY; + ofp10_debug(sc, &con->con_local, &con->con_peer, oh, NULL); + ofp_send(con, oh, NULL); + + return (0); +} + +int +ofp10_packet_match(struct packet *pkt, struct ofp10_match *m, uint32_t flags) +{ + struct ether_header *eh = pkt->pkt_eh; + + bzero(m, sizeof(*m)); + m->m_wildcards = htonl(~flags); + + if ((flags & (OFP10_WILDCARD_DL_SRC|OFP10_WILDCARD_DL_DST)) && (eh == NULL)) + return (-1); + + if (flags & OFP10_WILDCARD_DL_SRC) + memcpy(m->m_dl_src, eh->ether_shost, ETHER_ADDR_LEN); + if (flags & OFP10_WILDCARD_DL_DST) + memcpy(m->m_dl_dst, eh->ether_dhost, ETHER_ADDR_LEN); + + return (0); +} + +int +ofp10_packet_in(struct switchd *sc, struct switch_connection *con, + struct ofp_header *ih, struct ibuf *ibuf) +{ + struct ofp10_packet_in *pin; + struct ofp10_packet_out *pout; + struct ofp10_action_output *ao; + struct ofp10_flow_mod *fm; + struct ofp_header *oh; + struct packet pkt; + struct ibuf *obuf = NULL; + int ret = -1; + size_t len; + long srcport, dstport; + int addflow = 0; + int addpacket = 0; + + if ((pin = ibuf_getdata(ibuf, sizeof(*pin))) == NULL) + return (-1); + + bzero(&pkt, sizeof(pkt)); + len = ntohs(pin->pin_total_len); + srcport = ntohs(pin->pin_port); + + if ((dstport = packet_input(sc, con->con_switch, + srcport, ibuf, len, &pkt)) == -1 || + dstport > OFP10_PORT_MAX) { + /* fallback to flooding */ + dstport = OFP10_PORT_FLOOD; + } else if (srcport == dstport) { + /* + * silently drop looping packet + * (don't use OFP10_PORT_INPUT here) + */ + dstport = OFP10_PORT_ANY; + } + + if (dstport <= OFP10_PORT_MAX) + addflow = 1; + + if ((obuf = ibuf_static()) == NULL) + goto done; + + again: + if (addflow) { + if ((fm = ibuf_advance(obuf, sizeof(*fm))) == NULL) + goto done; + + ofp10_packet_match(&pkt, &fm->fm_match, OFP10_WILDCARD_DL_DST); + + oh = &fm->fm_oh; + fm->fm_cookie = 0; /* XXX should we set a cookie? */ + fm->fm_command = htons(OFP_FLOWCMD_ADD); + fm->fm_idle_timeout = htons(sc->sc_cache_timeout); + fm->fm_hard_timeout = 0; /* permanent */ + fm->fm_priority = 0; + fm->fm_buffer_id = pin->pin_buffer_id; + fm->fm_flags = htons(OFP_FLOWFLAG_SEND_FLOW_REMOVED); + if (pin->pin_buffer_id == (uint32_t)-1) + addpacket = 1; + } else { + if ((pout = ibuf_advance(obuf, sizeof(*pout))) == NULL) + goto done; + + oh = &pout->pout_oh; + pout->pout_buffer_id = pin->pin_buffer_id; + pout->pout_port = pin->pin_port; + pout->pout_actions_len = htons(sizeof(*ao)); + + if (pin->pin_buffer_id == (uint32_t)-1) + addpacket = 1; + } + + if ((ao = ibuf_advance(obuf, sizeof(*ao))) == NULL) + goto done; + ao->ao_type = htons(OFP_ACTION_OUTPUT); + ao->ao_len = htons(sizeof(*ao)); + ao->ao_port = htons((uint16_t)dstport); + ao->ao_max_len = 0; + + /* Add optional packet payload */ + if (addpacket && + imsg_add(obuf, pkt.pkt_buf, pkt.pkt_len) == -1) + goto done; + + /* Set output header */ + memcpy(oh, ih, sizeof(*oh)); + oh->oh_length = htons(ibuf_length(obuf)); + oh->oh_type = addflow ? OFP10_T_FLOW_MOD : OFP10_T_PACKET_OUT; + oh->oh_xid = htonl(con->con_xidnxt++); + + ofp10_debug(sc, &con->con_local, &con->con_peer, oh, obuf); + + ofp_send(con, NULL, obuf); + + if (addflow && addpacket) { + /* loop to output the packet again */ + addflow = 0; + if ((obuf = ibuf_static()) == NULL) + goto done; + goto again; + } + + ret = 0; + done: + ibuf_release(obuf); + return (ret); +} diff --git a/usr.sbin/switchd/ofp10.h b/usr.sbin/switchd/ofp10.h new file mode 100644 index 00000000000..04ac946fe52 --- /dev/null +++ b/usr.sbin/switchd/ofp10.h @@ -0,0 +1,184 @@ +/* $OpenBSD: ofp10.h,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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. + */ + +#ifndef _NET_OFP10_H_ +#define _NET_OFP10_H_ + +#include + +/* OpenFlow message type */ +#define OFP10_T_HELLO 0 /* Hello */ +#define OFP10_T_ERROR 1 /* Error */ +#define OFP10_T_ECHO_REQUEST 2 /* Echo Request */ +#define OFP10_T_ECHO_REPLY 3 /* Echo Reply */ +#define OFP10_T_EXPERIMENTER 4 /* Vendor/Experimenter */ +#define OFP10_T_FEATURES_REQUEST 5 /* Features Request (switch) */ +#define OFP10_T_FEATURES_REPLY 6 /* Features Reply (switch) */ +#define OFP10_T_GET_CONFIG_REQUEST 7 /* Get Config Request (switch) */ +#define OFP10_T_GET_CONFIG_REPLY 8 /* Get Config Reply (switch) */ +#define OFP10_T_SET_CONFIG 9 /* Set Config (switch) */ +#define OFP10_T_PACKET_IN 10 /* Packet In (async) */ +#define OFP10_T_FLOW_REMOVED 11 /* Flow Removed (async) */ +#define OFP10_T_PORT_STATUS 12 /* Port Status (async) */ +#define OFP10_T_PACKET_OUT 13 /* Packet Out (controller) */ +#define OFP10_T_FLOW_MOD 14 /* Flow Mod (controller) */ +#define OFP10_T_PORT_MOD 16 /* Port Mod (controller) */ +#define OFP10_T_STATS_REQUEST 17 /* Stats Request */ +#define OFP10_T_STATS_REPLY 18 /* Stats Reply */ +#define OFP10_T_BARRIER_REQUEST 19 /* Barrier Request */ +#define OFP10_T_BARRIER_REPLY 20 /* Barrier Reply */ +#define OFP10_T_QUEUE_GET_CONFIG_REQUEST 21 /* Queue Get Config Request */ +#define OFP10_T_QUEUE_GET_CONFIG_REPLY 22 /* Queue Get Config Reply */ +#define OFP10_T_TYPE_MAX 23 + +/* Ports */ +#define OFP10_PORT_MAX 0xff00 /* Maximum number of physical ports */ +#define OFP10_PORT_INPUT 0xfff8 /* Send back to input port */ +#define OFP10_PORT_FLOWTABLE 0xfff9 /* Perform actions in flow table */ +#define OFP10_PORT_NORMAL 0xfffa /* Let switch decide */ +#define OFP10_PORT_FLOOD 0xfffb /* All non-block ports except input */ +#define OFP10_PORT_ALL 0xfffc /* All ports except input */ +#define OFP10_PORT_CONTROLLER 0xfffd /* Send to controller */ +#define OFP10_PORT_LOCAL 0xfffe /* Local virtual OpenFlow port */ +#define OFP10_PORT_ANY 0xffff /* No port */ + +/* Switch port description */ +struct ofp10_phy_port { + uint16_t swp_number; + uint8_t swp_macaddr[ETHER_ADDR_LEN]; + char swp_name[OFP_IFNAMSIZ]; + uint32_t swp_config; /* Configuration flags */ + uint32_t swp_state; /* State flags */ + uint32_t swp_cur; /* Current features */ + uint32_t swp_advertised; /* Advertised by the port */ + uint32_t swp_supported; /* Supported by the port */ + uint32_t swp_peer; /* Advertised by peer */ +}; + +/* Packet-In Message */ +struct ofp10_packet_in { + struct ofp_header pin_oh; /* OpenFlow header */ + uint32_t pin_buffer_id; + uint16_t pin_total_len; + uint16_t pin_port; + uint8_t pin_reason; + uint8_t pin_pad; + uint8_t pin_data[0]; +} __packed; + +/* Actions */ +#define OFP10_ACTION_OUTPUT 0 /* Output to switch port */ +#define OFP10_ACTION_SET_VLAN_VID 1 /* Set the 802.1q VLAN id */ +#define OFP10_ACTION_SET_VLAN_PCP 2 /* Set the 802.1q priority */ +#define OFP10_ACTION_STRIP_VLAN 3 /* Strip the 802.1q header */ +#define OFP10_ACTION_SET_DL_SRC 4 /* Ethernet src address */ +#define OFP10_ACTION_SET_DL_DST 5 /* Ethernet dst address */ +#define OFP10_ACTION_SET_NW_SRC 6 /* IP src address */ +#define OFP10_ACTION_SET_NW_DST 7 /* IP dst address */ +#define OFP10_ACTION_SET_NW_TOS 8 /* IP TOS */ +#define OFP10_ACTION_SET_TP_SRC 9 /* TCP/UDP src port */ +#define OFP10_ACTION_SET_TP_DST 10 /* TCP/UDP dst port */ +#define OFP10_ACTION_ENQUEUE 11 /* Output to queue */ +#define OFP10_ACTION_EXPERIMENTER 0xffff /* Vendor-specific action */ + +/* Output Action */ +struct ofp10_action_output { + uint16_t ao_type; + uint16_t ao_len; + uint16_t ao_port; + uint16_t ao_max_len; +} __packed; + +/* Packet-Out Message */ +struct ofp10_packet_out { + struct ofp_header pout_oh; /* OpenFlow header */ + uint32_t pout_buffer_id; + uint16_t pout_port; + uint16_t pout_actions_len; + struct ofp_action_header pout_actions[0]; + /* Followed by optional packet data if buffer_id == 0xffffffff */ +} __packed; + +/* Flow matching wildcards */ +#define OFP10_WILDCARD_IN_PORT 0x00000001 /* Switch input port */ +#define OFP10_WILDCARD_DL_VLAN 0x00000002 /* VLAN id */ +#define OFP10_WILDCARD_DL_SRC 0x00000004 /* Ethernet src address */ +#define OFP10_WILDCARD_DL_DST 0x00000008 /* Ethernet dst address */ +#define OFP10_WILDCARD_DL_TYPE 0x00000010 /* Ethernet frame type */ +#define OFP10_WILDCARD_NW_PROTO 0x00000020 /* IPv4 protocol */ +#define OFP10_WILDCARD_TP_SRC 0x00000040 /* TCP/UDP source port */ +#define OFP10_WILDCARD_TP_DST 0x00000080 /* TCP/UDP destination port */ +#define OFP10_WILDCARD_NW_SRC 0x00003f00 /* IPv4 source address */ +#define OFP10_WILDCARD_NW_SRC_S 8 +#define OFP10_WILDCARD_NW_DST 0x000fc000 /* IPv4 destination address */ +#define OFP10_WILDCARD_NW_DST_S 14 +#define OFP10_WILDCARD_DL_VLANPCP 0x00100000 /* VLAN prio */ +#define OFP10_WILDCARD_NW_TOS 0x00200000 /* IPv4 ToS/DSCP */ +#define OFP10_WILDCARD_MASK 0x003fffff /* All wildcard flags */ + +/* Flow matching */ +struct ofp10_match { + uint32_t m_wildcards; /* Wildcard options */ + uint16_t m_in_port; /* Switch port */ + uint8_t m_dl_src[ETHER_ADDR_LEN]; /* Ether src addr */ + uint8_t m_dl_dst[ETHER_ADDR_LEN]; /* Ether dst addr */ + uint16_t m_dl_vlan; /* Input VLAN id */ + uint8_t m_dl_vlan_pcp; /* Input VLAN prio */ + uint8_t m_pad1[1]; + uint16_t m_dl_type; /* Ether type */ + uint8_t m_nw_tos; /* IPv4 ToS/DSCP */ + uint8_t m_nw_proto; /* IPv4 Proto */ + uint8_t m_pad2[2]; + uint32_t m_nw_src; /* IPv4 source */ + uint32_t m_nw_dst; /* IPv4 destination */ + uint16_t m_tp_src; /* TCP/UDP src port */ + uint16_t m_tp_dst; /* TCP/UDP dst port */ +} __packed; + +/* Flow modification message */ +struct ofp10_flow_mod { + struct ofp_header fm_oh; /* OpenFlow header */ + struct ofp10_match fm_match; + uint64_t fm_cookie; + uint16_t fm_command; + uint16_t fm_idle_timeout; + uint16_t fm_hard_timeout; + uint16_t fm_priority; + uint32_t fm_buffer_id; + uint16_t fm_port; + uint16_t fm_flags; + struct ofp_action_header fm_actions[0]; +} __packed; + +/* Error types */ +#define OFP10_ERRTYPE_HELLO_FAILED 0 /* Hello protocol failed */ +#define OFP10_ERRTYPE_BAD_REQUEST 1 /* Request was not understood */ +#define OFP10_ERRTYPE_BAD_ACTION 2 /* Error in action */ +#define OFP10_ERRTYPE_FLOW_MOD_FAILED 3 /* Problem modifying flow */ +#define OFP10_ERRTYPE_PORT_MOD_FAILED 4 /* Port mod request failed */ +#define OFP10_ERRTYPE_QUEUE_OP_FAILED 5 /* Queue operation failed */ + +/* FLOW MOD error codes */ +#define OFP10_ERRFLOWMOD_ALL_TABLES_FULL 0 /* Not added, full tables */ +#define OFP10_ERRFLOWMOD_OVERLAP 1 /* Overlapping flow */ +#define OFP10_ERRFLOWMOD_EPERM 2 /* Permissions error */ +#define OFP10_ERRFLOWMOD_BAD_TIMEOUT 3 /* non-zero idle/hardtimeout */ +#define OFP10_ERRFLOWMOD_BAD_COMMAND 4 /* Unknown command */ +#define OFP10_ERRFLOWMOD_UNSUPPORTED 5 /* Unsupported action list */ + +#endif /* _NET_OPF_H_ */ diff --git a/usr.sbin/switchd/ofp13.c b/usr.sbin/switchd/ofp13.c new file mode 100644 index 00000000000..0747d92f15b --- /dev/null +++ b/usr.sbin/switchd/ofp13.c @@ -0,0 +1,119 @@ +/* $OpenBSD: ofp13.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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. + */ + +/* XXX not implemented, this is just a stub */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ofp.h" +#include "ofp10.h" +#include "switchd.h" + +void +ofp13_debug(struct switchd *sc, + struct sockaddr_storage *src, struct sockaddr_storage *dst, + struct ofp_header *oh, struct ibuf *ibuf) +{ + struct ofp_packet_in pin; +#if 0 + uint8_t *p; +#endif + uint8_t *buf; + size_t len; + + len = ibuf_length(ibuf); + buf = ibuf_data(ibuf); + + ofp10_debug_header(sc, src, dst, oh); + + if (oh->oh_version != OFP_V_1_3) + return; + + switch (oh->oh_type) { + case OFP_T_PACKET_IN: + if (len < sizeof(pin)) + goto fail; + memcpy(&pin, buf, sizeof(pin)); +#if 0 + log_debug("\tbuffer %d port 0x%08x " + "phy port 0x%08x length %u " + "reason %u table id %u", + ntohl(pin13.pin_buffer_id), + ntohl(pin13.pin_port), + ntohl(pin13.pin_phy_port), + ntohs(pin13.pin_total_len), + pin13.pin_reason, + pin13.pin_table_id); + if ((len - sizeof(pin)) < ntohs(pin.pin_total_len)) + goto fail; + if (sc->sc_tap != -1) { + p = (uint8_t *)&buf[sizeof(pin)]; + (void)write(sc->sc_tap, p, + ntohs(pin.pin_total_len)); + } +#endif + break; + } + return; + + fail: + log_debug("\tinvalid packet\n"); +} + +int +ofp13_input(struct switchd *sc, struct switch_connection *con, + struct ofp_header *oh, struct ibuf *ibuf) +{ + uint8_t *buf; + ssize_t len; + + len = ibuf_length(ibuf); + buf = ibuf_data(ibuf); + + ofp13_debug(sc, &con->con_peer, &con->con_local, oh, ibuf); + + switch (oh->oh_type) { + case OFP_T_HELLO: + /* Echo back the received Hello packet */ + ofp_send(con, oh, NULL); + break; + case OFP_T_ECHO_REQUEST: + /* Echo reply */ + oh->oh_type = OFP_T_ECHO_REPLY; + ofp_send(con, oh, NULL); + break; + default: + /* not implemented */ + break; + } + + return (0); +} diff --git a/usr.sbin/switchd/ofp_map.h b/usr.sbin/switchd/ofp_map.h new file mode 100644 index 00000000000..3f2fe51f0bb --- /dev/null +++ b/usr.sbin/switchd/ofp_map.h @@ -0,0 +1,54 @@ +/* $OpenBSD: ofp_map.h,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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. + */ + +#ifndef _SWITCHD_OFP_MAP_H +#define _SWITCHD_OFP_MAP_H + +/* + * Each map is generated from lists of #define's in ofp.h, using the format: + * #define OFP_{MAPNAME}_FLAG {value} / * COMMENT * / + * + * Please make sure that the flags in ofp.h match this style (incl. comment) + */ + +/* OpenFlow 1.0 maps */ +extern struct constmap ofp10_t_map[]; +extern struct constmap ofp10_port_map[]; +extern struct constmap ofp10_action_map[]; +extern struct constmap ofp10_wildcard_map[]; +extern struct constmap ofp10_errtype_map[]; +extern struct constmap ofp10_errflowmod_map[]; + +/* OpenFlow 1.3+ maps */ +extern struct constmap ofp_v_map[]; +extern struct constmap ofp_t_map[]; +extern struct constmap ofp_hi_map[]; +extern struct constmap ofp_port_map[]; +extern struct constmap ofp_config_map[]; +extern struct constmap ofp_portstate_map[]; +extern struct constmap ofp_portconfig_map[]; +extern struct constmap ofp_portmedia_map[]; +extern struct constmap ofp_swcap_map[]; +extern struct constmap ofp_match_map[]; +extern struct constmap ofp_action_map[]; +extern struct constmap ofp_flowcmd_map[]; +extern struct constmap ofp_flowflag_map[]; +extern struct constmap ofp_errtype_map[]; +extern struct constmap ofp_errflowmod_map[]; + +#endif /* _SWITCHD_OFP_MAP_H */ diff --git a/usr.sbin/switchd/packet.c b/usr.sbin/switchd/packet.c new file mode 100644 index 00000000000..39df7a9af2b --- /dev/null +++ b/usr.sbin/switchd/packet.c @@ -0,0 +1,88 @@ +/* $OpenBSD: packet.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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 +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "switchd.h" + +const uint8_t etherbroadcastaddr[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; +const uint8_t etherzeroaddr[] = { 0, 0, 0, 0, 0, 0 }; + +int packet_ether_unicast(uint8_t *); + +int +packet_ether_unicast(uint8_t *ea) +{ + if (memcmp(ea, etherbroadcastaddr, ETHER_ADDR_LEN) == 0 || + memcmp(ea, etherzeroaddr, ETHER_ADDR_LEN) == 0 || + ETHER_IS_MULTICAST(ea)) + return (-1); + return (0); +} + +long +packet_input(struct switchd *sc, struct switch_control *sw, long port, + struct ibuf *ibuf, size_t len, struct packet *pkt) +{ + struct ether_header *eh; + struct macaddr *src, *dst; + + if (sw == NULL) + return (-1); + if (len < sizeof(*eh)) + return (-1); + + pkt->pkt_len = ibuf_dataleft(ibuf); + if ((pkt->pkt_eh = eh = ibuf_getdata(ibuf, sizeof(*eh))) == NULL) { + log_debug("short packet"); + return (-1); + } + len -= sizeof(*eh); + + if ((packet_ether_unicast(eh->ether_shost) == -1) || + (src = switch_learn(sc, sw, eh->ether_shost, port)) == NULL) + return (-1); + + if (packet_ether_unicast(eh->ether_dhost) == -1) + dst = NULL; + else + dst = switch_cached(sw, eh->ether_dhost); + + log_debug("%s: %s -> %s, port %ld -> %ld", __func__, + print_ether(eh->ether_shost), + print_ether(eh->ether_dhost), + src->mac_port, + dst == NULL ? -1 : dst->mac_port); + + return (dst == NULL ? -1 : dst->mac_port); +} diff --git a/usr.sbin/switchd/parse.y b/usr.sbin/switchd/parse.y new file mode 100644 index 00000000000..7193ceb9d95 --- /dev/null +++ b/usr.sbin/switchd/parse.y @@ -0,0 +1,708 @@ +/* $OpenBSD: parse.y,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2007-2016 Reyk Floeter + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "switchd.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); +int host(const char *, struct sockaddr *, socklen_t); + +struct switchd *conf; + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +typedef struct { + union { + int64_t number; + char *string; + in_port_t port; + struct switch_device + *conn; + } v; + int lineno; +} YYSTYPE; + +%} + +%token INCLUDE ERROR LISTEN ON TLS PORT DEVICE FORWARD TO +%token STRING +%token NUMBER +%type opttls +%type optofcconn +%type port + +%% + +grammar : /* empty */ + | grammar '\n' + | grammar include '\n' + | grammar listen '\n' + | grammar device '\n' + | grammar varset '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +listen : LISTEN ON STRING opttls port { + if (host($3, + (struct sockaddr *)&conf->sc_server.srv_addr, + sizeof(conf->sc_server.srv_addr)) != 0) { + free($3); + YYERROR; + } + free($3); + conf->sc_server.srv_tls = $4; + ((struct sockaddr_in *)&conf->sc_server.srv_addr) + ->sin_port = $5; + } + | LISTEN ON STRING opttls { + if (host($3, + (struct sockaddr *)&conf->sc_server.srv_addr, + sizeof(conf->sc_server.srv_addr)) != 0) { + free($3); + YYERROR; + } + free($3); + } + ; + +port : PORT NUMBER { + if ($2 <= 0 || $2 >= (int)USHRT_MAX) { + yyerror("invalid port: %lld", $2); + YYERROR; + } + $$ = htons($2); + } + ; + +opttls : /* empty */ { $$ = 0; } + | TLS { $$ = 1; } + ; + +device : DEVICE STRING optofcconn { + struct switch_device *c; + + TAILQ_FOREACH(c, &conf->sc_conns, sdv_next) { + if (strcmp(c->sdv_device, $2) == 0) + break; + } + if (c != NULL) { + yyerror("device name is duplicated"); + YYERROR; + } + if (strlcpy($3->sdv_device, $2, sizeof($3->sdv_device)) + >= sizeof($3->sdv_device)) { + yyerror("device name is too long"); + YYERROR; + } + free($2); + TAILQ_INSERT_TAIL(&conf->sc_conns, $3, sdv_next); + } + ; + +optofcconn : /* empty */ { + if (($$ = calloc(1, + sizeof(struct switch_device))) == NULL) + fatal("calloc"); + $$->sdv_swc.swc_type = SWITCH_CONN_LOCAL; + } + | FORWARD TO STRING { + if (($$ = calloc(1, + sizeof(struct switch_device))) == NULL) + fatal("calloc"); + if (strncmp($3, "tcp:", 4) == 0) + $$->sdv_swc.swc_type = SWITCH_CONN_TCP; + else if (strncmp($3, "tls:", 4) == 0) + $$->sdv_swc.swc_type = SWITCH_CONN_TLS; + else { + yyerror("foward to proto is not supported"); + free($$); + free($3); + YYERROR; + } + if (parsehostport($3 + 4, + (struct sockaddr *)&$$->sdv_swc.swc_addr, + sizeof($$->sdv_swc.swc_addr)) == -1) { + yyerror("could not parse host and port part " + "of connect-to"); + free($$); + free($3); + YYERROR; + } + free($3); + } + ; + +varset : STRING '=' STRING { + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatal("yyerror vasprintf"); + va_end(ap); + log_warnx("%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "device", DEVICE }, + { "forward", FORWARD }, + { "include", INCLUDE }, + { "listen", LISTEN }, + { "on", ON }, + { "port", PORT }, + { "tls", TLS }, + { "to", TO }, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +u_char *parsebuf; +int parseindex; +u_char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == '\t' || c == ' ') { + /* Compress blanks to a single space. */ + do { + c = getc(file->stream); + } while (c == '\t' || c == ' '); + ungetc(c, file->stream); + c = ' '; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + + /* skip to either EOF or the first real EOL */ + while (1) { + if (pushback_index) + c = pushback_buffer[--pushback_index]; + else + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + u_char buf[8096]; + u_char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + fatal("yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_' || c == '/') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + fatal("yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("malloc"); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("malloc"); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +int +parse_config(const char *filename, struct switchd *sc) +{ + struct sym *sym; + int errors = 0; + struct sockaddr_in *sin4; + + conf = sc; + + /* Set the default 0.0.0.0 6633/tcp */ + memset(&conf->sc_server.srv_addr, 0, sizeof(conf->sc_server.srv_addr)); + sin4 = (struct sockaddr_in *)&conf->sc_server.srv_addr; + sin4->sin_family = AF_INET; + sin4->sin_port = htons(SWITCHD_CTLR_PORT); + sin4->sin_len = sizeof(struct sockaddr_in); + + if ((file = pushfile(filename, 0)) == NULL) { + log_warn("failed to open %s", filename); + return (0); + } + topfile = file; + setservent(1); + + yyparse(); + errors = file->errors; + popfile(); + + endservent(); + + /* Free macros and check which have not been used. */ + while ((sym = TAILQ_FIRST(&symhead))) { + if (!sym->used) + log_debug("warning: macro '%s' not " + "used\n", sym->nam); + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + + return (errors ? -1 : 0); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entry)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = (val - s) + 1; + if ((sym = malloc(len)) == NULL) + fatal("cmdline_symset: malloc"); + + (void)strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} + +int +host(const char *str, struct sockaddr *sa, socklen_t salen) +{ + struct addrinfo hints, *ai0; + int error; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = AF_UNSPEC; + + if ((error = getaddrinfo(str, NULL, &hints, &ai0)) != 0) { + yyerror("invalid listen address: %s: %s", str, + gai_strerror(error)); + return (-1); + } + if (salen >= ai0->ai_addrlen) + memcpy(sa, ai0->ai_addr, ai0->ai_addrlen); + else { + yyerror("addrlen is invalid: %d", (int)ai0->ai_addrlen); + freeaddrinfo(ai0); + return (-1); + } + freeaddrinfo(ai0); + + return (0); +} diff --git a/usr.sbin/switchd/proc.c b/usr.sbin/switchd/proc.c new file mode 100644 index 00000000000..fb3dfcfa50c --- /dev/null +++ b/usr.sbin/switchd/proc.c @@ -0,0 +1,648 @@ +/* $OpenBSD: proc.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2010 - 2014 Reyk Floeter + * Copyright (c) 2008 Pierre-Yves Ritschard + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "proc.h" + +void proc_open(struct privsep *, struct privsep_proc *, + struct privsep_proc *, size_t); +void proc_close(struct privsep *); +int proc_ispeer(struct privsep_proc *, unsigned int, enum privsep_procid); +void proc_shutdown(struct privsep_proc *); +void proc_sig_handler(int, short, void *); +void proc_range(struct privsep *, enum privsep_procid, int *, int *); +int proc_dispatch_null(int, struct privsep_proc *, struct imsg *); + +int +proc_ispeer(struct privsep_proc *procs, unsigned int nproc, + enum privsep_procid type) +{ + unsigned int i; + + for (i = 0; i < nproc; i++) + if (procs[i].p_id == type) + return (1); + return (0); +} + +void +proc_init(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc) +{ + unsigned int i, j, src, dst; + struct privsep_pipes *pp; + + /* + * Allocate pipes for all process instances (incl. parent) + * + * - ps->ps_pipes: N:M mapping + * N source processes connected to M destination processes: + * [src][instances][dst][instances], for example + * [PROC_RELAY][3][PROC_CA][3] + * + * - ps->ps_pp: per-process 1:M part of ps->ps_pipes + * Each process instance has a destination array of socketpair fds: + * [dst][instances], for example + * [PROC_PARENT][0] + */ + for (src = 0; src < PROC_MAX; src++) { + /* Allocate destination array for each process */ + if ((ps->ps_pipes[src] = calloc(ps->ps_ninstances, + sizeof(struct privsep_pipes))) == NULL) + fatal("proc_init: calloc"); + + for (i = 0; i < ps->ps_ninstances; i++) { + pp = &ps->ps_pipes[src][i]; + + for (dst = 0; dst < PROC_MAX; dst++) { + /* Allocate maximum fd integers */ + if ((pp->pp_pipes[dst] = + calloc(ps->ps_ninstances, + sizeof(int))) == NULL) + fatal("proc_init: calloc"); + + /* Mark fd as unused */ + for (j = 0; j < ps->ps_ninstances; j++) + pp->pp_pipes[dst][j] = -1; + } + } + } + + /* + * Setup and run the parent and its children + */ + privsep_process = PROC_PARENT; + ps->ps_instances[PROC_PARENT] = 1; + ps->ps_title[PROC_PARENT] = "parent"; + ps->ps_pid[PROC_PARENT] = getpid(); + ps->ps_pp = &ps->ps_pipes[privsep_process][0]; + + for (i = 0; i < nproc; i++) { + /* Default to 1 process instance */ + if (ps->ps_instances[procs[i].p_id] < 1) + ps->ps_instances[procs[i].p_id] = 1; + ps->ps_title[procs[i].p_id] = procs[i].p_title; + } + + proc_open(ps, NULL, procs, nproc); + + /* Engage! */ + for (i = 0; i < nproc; i++) + ps->ps_pid[procs[i].p_id] = (*procs[i].p_init)(ps, &procs[i]); +} + +void +proc_kill(struct privsep *ps) +{ + pid_t pid; + unsigned int i; + + if (privsep_process != PROC_PARENT) + return; + + for (i = 0; i < PROC_MAX; i++) { + if (ps->ps_pid[i] == 0) + continue; + killpg(ps->ps_pid[i], SIGTERM); + } + + do { + pid = waitpid(WAIT_ANY, NULL, 0); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + proc_close(ps); +} + +void +proc_open(struct privsep *ps, struct privsep_proc *p, + struct privsep_proc *procs, size_t nproc) +{ + struct privsep_pipes *pa, *pb; + int fds[2]; + unsigned int i, j, src, proc; + + if (p == NULL) + src = privsep_process; /* parent */ + else + src = p->p_id; + + /* + * Open socket pairs for our peers + */ + for (proc = 0; proc < nproc; proc++) { + procs[proc].p_ps = ps; + procs[proc].p_env = ps->ps_env; + if (procs[proc].p_cb == NULL) + procs[proc].p_cb = proc_dispatch_null; + + for (i = 0; i < ps->ps_instances[src]; i++) { + for (j = 0; j < ps->ps_instances[procs[proc].p_id]; + j++) { + pa = &ps->ps_pipes[src][i]; + pb = &ps->ps_pipes[procs[proc].p_id][j]; + + /* Check if fds are already set by peer */ + if (pa->pp_pipes[procs[proc].p_id][j] != -1) + continue; + + if (socketpair(AF_UNIX, + SOCK_STREAM | SOCK_NONBLOCK, + PF_UNSPEC, fds) == -1) + fatal("socketpair"); + + pa->pp_pipes[procs[proc].p_id][j] = fds[0]; + pb->pp_pipes[src][i] = fds[1]; + } + } + } +} + +void +proc_listen(struct privsep *ps, struct privsep_proc *procs, size_t nproc) +{ + unsigned int i, dst, src, n, m; + struct privsep_pipes *pp; + + /* + * Close unused pipes + */ + for (src = 0; src < PROC_MAX; src++) { + for (n = 0; n < ps->ps_instances[src]; n++) { + /* Ingore current process */ + if (src == (unsigned int)privsep_process && + n == ps->ps_instance) + continue; + + pp = &ps->ps_pipes[src][n]; + + for (dst = 0; dst < PROC_MAX; dst++) { + if (src == dst) + continue; + for (m = 0; m < ps->ps_instances[dst]; m++) { + if (pp->pp_pipes[dst][m] == -1) + continue; + + /* Close and invalidate fd */ + close(pp->pp_pipes[dst][m]); + pp->pp_pipes[dst][m] = -1; + } + } + } + } + + src = privsep_process; + ps->ps_pp = pp = &ps->ps_pipes[src][ps->ps_instance]; + + /* + * Listen on appropriate pipes + */ + for (i = 0; i < nproc; i++) { + dst = procs[i].p_id; + + if (src == dst) + fatal("proc_listen: cannot peer with oneself"); + + if ((ps->ps_ievs[dst] = calloc(ps->ps_instances[dst], + sizeof(struct imsgev))) == NULL) + fatal("proc_open"); + + for (n = 0; n < ps->ps_instances[dst]; n++) { + if (pp->pp_pipes[dst][n] == -1) + continue; + + imsg_init(&(ps->ps_ievs[dst][n].ibuf), + pp->pp_pipes[dst][n]); + ps->ps_ievs[dst][n].handler = proc_dispatch; + ps->ps_ievs[dst][n].events = EV_READ; + ps->ps_ievs[dst][n].proc = &procs[i]; + ps->ps_ievs[dst][n].data = &ps->ps_ievs[dst][n]; + procs[i].p_instance = n; + + event_set(&(ps->ps_ievs[dst][n].ev), + ps->ps_ievs[dst][n].ibuf.fd, + ps->ps_ievs[dst][n].events, + ps->ps_ievs[dst][n].handler, + ps->ps_ievs[dst][n].data); + event_add(&(ps->ps_ievs[dst][n].ev), NULL); + } + } +} + +void +proc_close(struct privsep *ps) +{ + unsigned int dst, n; + struct privsep_pipes *pp; + + if (ps == NULL) + return; + + pp = ps->ps_pp; + + for (dst = 0; dst < PROC_MAX; dst++) { + if (ps->ps_ievs[dst] == NULL) + continue; + + for (n = 0; n < ps->ps_instances[dst]; n++) { + if (pp->pp_pipes[dst][n] == -1) + continue; + + /* Cancel the fd, close and invalidate the fd */ + event_del(&(ps->ps_ievs[dst][n].ev)); + imsg_clear(&(ps->ps_ievs[dst][n].ibuf)); + close(pp->pp_pipes[dst][n]); + pp->pp_pipes[dst][n] = -1; + } + free(ps->ps_ievs[dst]); + } +} + +void +proc_shutdown(struct privsep_proc *p) +{ + struct privsep *ps = p->p_ps; + + if (p->p_id == PROC_CONTROL && ps) + control_cleanup(&ps->ps_csock); + + if (p->p_shutdown != NULL) + (*p->p_shutdown)(); + + proc_close(ps); + + log_info("%s exiting, pid %d", p->p_title, getpid()); + + _exit(0); +} + +void +proc_sig_handler(int sig, short event, void *arg) +{ + struct privsep_proc *p = arg; + + switch (sig) { + case SIGINT: + case SIGTERM: + proc_shutdown(p); + break; + case SIGCHLD: + case SIGHUP: + case SIGPIPE: + case SIGUSR1: + /* ignore */ + break; + default: + fatalx("proc_sig_handler: unexpected signal"); + /* NOTREACHED */ + } +} + +pid_t +proc_run(struct privsep *ps, struct privsep_proc *p, + struct privsep_proc *procs, unsigned int nproc, + void (*run)(struct privsep *, struct privsep_proc *, void *), void *arg) +{ + pid_t pid; + struct passwd *pw; + const char *root; + struct control_sock *rcs; + unsigned int n; + + if (ps->ps_noaction) + return (0); + + proc_open(ps, p, procs, nproc); + + /* Fork child handlers */ + switch (pid = fork()) { + case -1: + fatal("proc_run: cannot fork"); + case 0: + log_procinit(p->p_title); + + /* Set the process group of the current process */ + setpgid(0, 0); + break; + default: + return (pid); + } + + pw = ps->ps_pw; + + if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) { + if (control_init(ps, &ps->ps_csock) == -1) + fatalx(__func__); + TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry) + if (control_init(ps, rcs) == -1) + fatalx(__func__); + } + + /* Change root directory */ + if (p->p_chroot != NULL) + root = p->p_chroot; + else + root = pw->pw_dir; + + if (chroot(root) == -1) + fatal("proc_run: chroot"); + if (chdir("/") == -1) + fatal("proc_run: chdir(\"/\")"); + + privsep_process = p->p_id; + + setproctitle("%s", p->p_title); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("proc_run: cannot drop privileges"); + + /* Fork child handlers */ + for (n = 1; n < ps->ps_instances[p->p_id]; n++) { + if (fork() == 0) { + ps->ps_instance = p->p_instance = n; + break; + } + } + +#ifdef DEBUG + log_debug("%s: %s %d/%d, pid %d", __func__, p->p_title, + ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid()); +#endif + + event_init(); + + signal_set(&ps->ps_evsigint, SIGINT, proc_sig_handler, p); + signal_set(&ps->ps_evsigterm, SIGTERM, proc_sig_handler, p); + signal_set(&ps->ps_evsigchld, SIGCHLD, proc_sig_handler, p); + signal_set(&ps->ps_evsighup, SIGHUP, proc_sig_handler, p); + signal_set(&ps->ps_evsigpipe, SIGPIPE, proc_sig_handler, p); + signal_set(&ps->ps_evsigusr1, SIGUSR1, proc_sig_handler, p); + + signal_add(&ps->ps_evsigint, NULL); + signal_add(&ps->ps_evsigterm, NULL); + signal_add(&ps->ps_evsigchld, NULL); + signal_add(&ps->ps_evsighup, NULL); + signal_add(&ps->ps_evsigpipe, NULL); + signal_add(&ps->ps_evsigusr1, NULL); + + proc_listen(ps, procs, nproc); + + if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) { + TAILQ_INIT(&ctl_conns); + if (control_listen(&ps->ps_csock) == -1) + fatalx(__func__); + TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry) + if (control_listen(rcs) == -1) + fatalx(__func__); + } + + if (run != NULL) + run(ps, p, arg); + + event_dispatch(); + + proc_shutdown(p); + + return (0); +} + +void +proc_dispatch(int fd, short event, void *arg) +{ + struct imsgev *iev = arg; + struct privsep_proc *p = iev->proc; + struct privsep *ps = p->p_ps; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + int verbose; + const char *title; + + title = ps->ps_title[privsep_process]; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal(__func__); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) + fatal(__func__); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal(__func__); + if (n == 0) + break; + +#if DEBUG > 1 + log_debug("%s: %s %d got imsg %d peerid %d from %s %d", + __func__, title, ps->ps_instance + 1, + imsg.hdr.type, imsg.hdr.peerid, p->p_title, p->p_instance); +#endif + + /* + * Check the message with the program callback + */ + if ((p->p_cb)(fd, p, &imsg) == 0) { + /* Message was handled by the callback, continue */ + imsg_free(&imsg); + continue; + } + + /* + * Generic message handling + */ + switch (imsg.hdr.type) { + case IMSG_CTL_VERBOSE: + IMSG_SIZE_CHECK(&imsg, &verbose); + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_verbose(verbose); + break; + default: + log_warnx("%s: %s %d got invalid imsg %d peerid %d " + "from %s %d", + __func__, title, ps->ps_instance + 1, + imsg.hdr.type, imsg.hdr.peerid, + p->p_title, p->p_instance); + fatalx(__func__); + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} + +int +proc_dispatch_null(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + return (-1); +} + +/* + * imsg helper functions + */ + +void +imsg_event_add(struct imsgev *iev) +{ + if (iev->handler == NULL) { + imsg_flush(&iev->ibuf); + return; + } + + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data); + event_add(&iev->ev, NULL); +} + +int +imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, + pid_t pid, int fd, void *data, uint16_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, + pid, fd, data, datalen)) == -1) + return (ret); + imsg_event_add(iev); + return (ret); +} + +int +imsg_composev_event(struct imsgev *iev, uint16_t type, uint32_t peerid, + pid_t pid, int fd, const struct iovec *iov, int iovcnt) +{ + int ret; + + if ((ret = imsg_composev(&iev->ibuf, type, peerid, + pid, fd, iov, iovcnt)) == -1) + return (ret); + imsg_event_add(iev); + return (ret); +} + +void +proc_range(struct privsep *ps, enum privsep_procid id, int *n, int *m) +{ + if (*n == -1) { + /* Use a range of all target instances */ + *n = 0; + *m = ps->ps_instances[id]; + } else { + /* Use only a single slot of the specified peer process */ + *m = *n + 1; + } +} + +int +proc_compose_imsg(struct privsep *ps, enum privsep_procid id, int n, + uint16_t type, uint32_t peerid, int fd, void *data, uint16_t datalen) +{ + int m; + + proc_range(ps, id, &n, &m); + for (; n < m; n++) { + if (imsg_compose_event(&ps->ps_ievs[id][n], + type, peerid, 0, fd, data, datalen) == -1) + return (-1); + } + + return (0); +} + +int +proc_compose(struct privsep *ps, enum privsep_procid id, + uint16_t type, void *data, uint16_t datalen) +{ + return (proc_compose_imsg(ps, id, -1, type, -1, -1, data, datalen)); +} + +int +proc_composev_imsg(struct privsep *ps, enum privsep_procid id, int n, + uint16_t type, uint32_t peerid, int fd, const struct iovec *iov, int iovcnt) +{ + int m; + + proc_range(ps, id, &n, &m); + for (; n < m; n++) + if (imsg_composev_event(&ps->ps_ievs[id][n], + type, peerid, 0, fd, iov, iovcnt) == -1) + return (-1); + + return (0); +} + +int +proc_composev(struct privsep *ps, enum privsep_procid id, + uint16_t type, const struct iovec *iov, int iovcnt) +{ + return (proc_composev_imsg(ps, id, -1, type, -1, -1, iov, iovcnt)); +} + +int +proc_forward_imsg(struct privsep *ps, struct imsg *imsg, + enum privsep_procid id, int n) +{ + return (proc_compose_imsg(ps, id, n, imsg->hdr.type, + imsg->hdr.peerid, imsg->fd, imsg->data, IMSG_DATA_SIZE(imsg))); +} + +struct imsgbuf * +proc_ibuf(struct privsep *ps, enum privsep_procid id, int n) +{ + int m; + + proc_range(ps, id, &n, &m); + return (&ps->ps_ievs[id][n].ibuf); +} + +struct imsgev * +proc_iev(struct privsep *ps, enum privsep_procid id, int n) +{ + int m; + + proc_range(ps, id, &n, &m); + return (&ps->ps_ievs[id][n]); +} diff --git a/usr.sbin/switchd/proc.h b/usr.sbin/switchd/proc.h new file mode 100644 index 00000000000..26e82cf517f --- /dev/null +++ b/usr.sbin/switchd/proc.h @@ -0,0 +1,175 @@ +/* $OpenBSD: proc.h,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2010-2015 Reyk Floeter + * + * 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 +#include +#include "types.h" + +#ifndef _PROC_H +#define _PROC_H + +struct control_sock { + const char *cs_name; + struct event cs_ev; + struct event cs_evt; + int cs_fd; + int cs_restricted; + void *cs_env; + + TAILQ_ENTRY(control_sock) cs_entry; +}; +TAILQ_HEAD(control_socks, control_sock); + +struct privsep_pipes { + int *pp_pipes[PROC_MAX]; +}; + +struct privsep { + struct privsep_pipes *ps_pipes[PROC_MAX]; + struct privsep_pipes *ps_pp; + + struct imsgev *ps_ievs[PROC_MAX]; + const char *ps_title[PROC_MAX]; + pid_t ps_pid[PROC_MAX]; + struct passwd *ps_pw; + int ps_noaction; + + struct control_sock ps_csock; + struct control_socks ps_rcsocks; + + unsigned int ps_instances[PROC_MAX]; + unsigned int ps_ninstances; + unsigned int ps_instance; + + /* Event and signal handlers */ + struct event ps_evsigint; + struct event ps_evsigterm; + struct event ps_evsigchld; + struct event ps_evsighup; + struct event ps_evsigpipe; + struct event ps_evsigusr1; + + void *ps_env; +}; + +struct privsep_proc { + const char *p_title; + enum privsep_procid p_id; + int (*p_cb)(int, struct privsep_proc *, + struct imsg *); + pid_t (*p_init)(struct privsep *, + struct privsep_proc *); + const char *p_chroot; + struct privsep *p_ps; + void *p_env; + void (*p_shutdown)(void); + unsigned int p_instance; +}; + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + struct privsep_proc *proc; + void *data; + short events; + const char *name; +}; + +#ifndef IMSG_DATA_SIZE +#define IMSG_DATA_SIZE(_imsg) ((_imsg)->hdr.len - IMSG_HEADER_SIZE) +#endif + +#ifndef IMSG_SIZE_CHECK +#define IMSG_SIZE_CHECK(_imsg, _type) \ + do { \ + if (IMSG_DATA_SIZE(_imsg) < sizeof(*(_type))) \ + fatal("received imsg size was wrong."); \ + } while (0 /* CONSTCOND */) +#endif + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + uint8_t flags; +#define CTL_CONN_NOTIFY 0x01 + struct imsgev iev; + int restricted; +}; +TAILQ_HEAD(ctl_connlist, ctl_conn); +extern struct ctl_connlist ctl_conns; + +/* proc.c */ +void proc_init(struct privsep *, struct privsep_proc *, unsigned int); +void proc_kill(struct privsep *); +void proc_listen(struct privsep *, struct privsep_proc *, size_t); +void proc_dispatch(int, short event, void *); +pid_t proc_run(struct privsep *, struct privsep_proc *, + struct privsep_proc *, unsigned int, + void (*)(struct privsep *, struct privsep_proc *, void *), void *); +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, + pid_t, int, void *, uint16_t); +int imsg_composev_event(struct imsgev *, uint16_t, uint32_t, + pid_t, int, const struct iovec *, int); +int proc_compose_imsg(struct privsep *, enum privsep_procid, int, + uint16_t, uint32_t, int, void *, uint16_t); +int proc_compose(struct privsep *, enum privsep_procid, + uint16_t, void *data, uint16_t); +int proc_composev_imsg(struct privsep *, enum privsep_procid, int, + uint16_t, uint32_t, int, const struct iovec *, int); +int proc_composev(struct privsep *, enum privsep_procid, + uint16_t, const struct iovec *, int); +int proc_forward_imsg(struct privsep *, struct imsg *, + enum privsep_procid, int); +struct imsgbuf * + proc_ibuf(struct privsep *, enum privsep_procid, int); +struct imsgev * + proc_iev(struct privsep *, enum privsep_procid, int); + +/* control.c */ +int control_init(struct privsep *, struct control_sock *); +int control_listen(struct control_sock *); +void control_cleanup(struct control_sock *); +struct ctl_conn + *control_connbyfd(int); +pid_t control(struct privsep *, struct privsep_proc *); + +/* log.c */ +void log_init(int, int); +void log_procinit(const char *); +void log_verbose(int); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +void socket_set_blockmode(int, enum blockmodes); + +#endif /* _PROC_H */ diff --git a/usr.sbin/switchd/switch.c b/usr.sbin/switchd/switch.c new file mode 100644 index 00000000000..a8cedb5fc84 --- /dev/null +++ b/usr.sbin/switchd/switch.c @@ -0,0 +1,248 @@ +/* $OpenBSD: switch.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "switchd.h" + +void switch_timer(struct switchd *, void *); + +static __inline int + switch_cmp(struct switch_control *, struct switch_control *); +static __inline int + switch_maccmp(struct macaddr *, struct macaddr *); + +void +switch_init(struct switchd *sc) +{ + RB_INIT(&sc->sc_switches); +} + +int +switch_dispatch_control(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + struct switchd *sc = p->p_env; + struct privsep *ps = p->p_ps; + struct switch_control *sw; + struct macaddr *mac; + struct iovec iov[2]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_SUM: + IMSG_SIZE_CHECK(imsg, &fd); + + RB_FOREACH(sw, switch_head, &sc->sc_switches) { + iov[0].iov_base = imsg->data; + iov[0].iov_len = IMSG_DATA_SIZE(imsg); + iov[1].iov_base = sw; + iov[1].iov_len = sizeof(*sw); + + proc_composev(ps, PROC_CONTROL, + IMSG_CTL_SWITCH, iov, 2); + + RB_FOREACH(mac, macaddr_head, &sw->sw_addrcache) { + iov[0].iov_base = imsg->data; + iov[0].iov_len = IMSG_DATA_SIZE(imsg); + iov[1].iov_base = mac; + iov[1].iov_len = sizeof(*mac); + + proc_composev(ps, PROC_CONTROL, + IMSG_CTL_MAC, iov, 2); + } + } + + proc_compose(ps, PROC_CONTROL, + IMSG_CTL_END, imsg->data, IMSG_DATA_SIZE(imsg)); + return (0); + default: + break; + } + + return (-1); +} + +struct switch_control * +switch_get(struct switch_connection *con) +{ + struct switchd *sc = con->con_sc; + struct switch_control key; + + memcpy(&key.sw_addr, &con->con_peer, sizeof(key.sw_addr)); + + con->con_switch = RB_FIND(switch_head, &sc->sc_switches, &key); + return (con->con_switch); +} + +struct switch_control * +switch_add(struct switch_connection *con) +{ + struct switchd *sc = con->con_sc; + struct switch_control *sw, *oldsw; + static unsigned int id = 0; + + /* Connection already has an associated switch */ + if (con->con_switch != NULL) + return (NULL); + + if ((sw = calloc(1, sizeof(*sw))) == NULL) + return (NULL); + + memcpy(&sw->sw_addr, &con->con_peer, sizeof(sw->sw_addr)); + sw->sw_id = ++id; + RB_INIT(&sw->sw_addrcache); + + if ((oldsw = + RB_INSERT(switch_head, &sc->sc_switches, sw)) != NULL) { + free(sw); + sw = oldsw; + } else { + timer_set(sc, &sw->sw_timer, switch_timer, sw); + timer_add(sc, &sw->sw_timer, sc->sc_cache_timeout); + } + + con->con_switch = sw; + return (con->con_switch); +} + +void +switch_timer(struct switchd *sc, void *arg) +{ + struct switch_control *sw = arg; + struct macaddr *mac, *next; + struct timeval tv; + unsigned int cnt = 0; + + getmonotime(&tv); + + for (mac = RB_MIN(macaddr_head, &sw->sw_addrcache); + mac != NULL; mac = next) { + next = RB_NEXT(macaddr_head, &sw->sw_addrcache, mac); + + /* Simple monotonic timeout */ + if ((tv.tv_sec - mac->mac_age) >= sc->sc_cache_timeout) { + RB_REMOVE(macaddr_head, &sw->sw_addrcache, mac); + sw->sw_cachesize--; + free(mac); + cnt++; + } + } + if (cnt) + log_debug("%s: flushed %d mac from switch %u after timeout", + __func__, cnt, sw->sw_id); + + timer_add(sc, &sw->sw_timer, sc->sc_cache_timeout); +} + +void +switch_remove(struct switchd *sc, struct switch_control *sw) +{ + struct macaddr *mac, *next; + + if (sw == NULL) + return; + + timer_del(sc, &sw->sw_timer); + + for (mac = RB_MIN(macaddr_head, &sw->sw_addrcache); + mac != NULL; mac = next) { + next = RB_NEXT(macaddr_head, &sw->sw_addrcache, mac); + RB_REMOVE(macaddr_head, &sw->sw_addrcache, mac); + sw->sw_cachesize--; + free(mac); + } + RB_REMOVE(switch_head, &sc->sc_switches, sw); + + log_debug("%s: switch %u removed", __func__, sw->sw_id); + + free(sw); +} + +struct macaddr * +switch_learn(struct switchd *sc, struct switch_control *sw, + uint8_t *ea, long port) +{ + struct macaddr *mac, *oldmac = NULL; + struct timeval tv; + + if ((mac = oldmac = switch_cached(sw, ea)) != NULL) + goto update; + + if (sw->sw_cachesize >= sc->sc_cache_max) + return (NULL); + + if ((mac = calloc(1, sizeof(*mac))) == NULL) + return (NULL); + + memcpy(&mac->mac_addr, ea, sizeof(mac->mac_addr)); + + if (RB_INSERT(macaddr_head, &sw->sw_addrcache, mac) != NULL) + fatalx("cache corrupted"); + sw->sw_cachesize++; + + update: + getmonotime(&tv); + mac->mac_port = port; + mac->mac_age = tv.tv_sec; + + log_debug("%s: %s mac %s on switch %u port %ld", + __func__, oldmac == NULL ? "learned new" : "updated", + print_ether(ea), sw->sw_id, port); + + return (mac); +} + +struct macaddr * +switch_cached(struct switch_control *sw, uint8_t *ea) +{ + struct macaddr key; + memcpy(&key.mac_addr, ea, sizeof(key.mac_addr)); + return (RB_FIND(macaddr_head, &sw->sw_addrcache, &key)); +} + +static __inline int +switch_cmp(struct switch_control *a, struct switch_control *b) +{ + int diff = 0; + + diff = sockaddr_cmp((struct sockaddr *)&a->sw_addr, + (struct sockaddr *)&b->sw_addr, 128); + if (!diff) + diff = socket_getport(&a->sw_addr) - + socket_getport(&b->sw_addr); + + return (diff); +} + +static __inline int +switch_maccmp(struct macaddr *a, struct macaddr *b) +{ + return (memcmp(a->mac_addr, b->mac_addr, sizeof(a->mac_addr))); +} + +RB_GENERATE(switch_head, switch_control, sw_entry, switch_cmp); +RB_GENERATE(macaddr_head, macaddr, mac_entry, switch_maccmp); diff --git a/usr.sbin/switchd/switchd.8 b/usr.sbin/switchd/switchd.8 new file mode 100644 index 00000000000..39aa8e8d8a8 --- /dev/null +++ b/usr.sbin/switchd/switchd.8 @@ -0,0 +1,55 @@ +.\" $OpenBSD: switchd.8,v 1.1 2016/07/19 16:54:26 reyk Exp $ +.\" +.\" Copyright (c) 2016 Reyk Floeter +.\" +.\" 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. +.\" +.Dd $Mdocdate: July 19 2016 $ +.Dt SWITCHD 8 +.Os +.Sh NAME +.Nm switchd +.Nd software-defined networking (SDN) sflow controller +.Sh SYNOPSIS +.Nm switchd +.Op Fl 6dnSTtv +.Op Fl D Ar macro Ns = Ns Ar value +.Op Fl f Ar file +.Sh DESCRIPTION +.Nm +is an controller for software-defined networking (SDN) and is +compatible with the OpenFlow protocol. +.Sh STANDARDS +.Rs +.%A Open Networking Foundation (ONF) +.%D December 31, 2009 +.%R Version 1.0.0 (Wire Protocol 0x01) +.%T OpenFlow Switch Specification +.Re +.Pp +.Rs +.%A Open Networking Foundation (ONF) +.%D March 26, 2015 +.%R Version 1.3.5 (Protocol version 0x04) +.%T OpenFlow Switch Specification +.Re +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 6.1 . +.Sh AUTHORS +The +.Nm +program was written by +.An Reyk Floeter Aq Mt reyk@openbsd.org . diff --git a/usr.sbin/switchd/switchd.c b/usr.sbin/switchd/switchd.c new file mode 100644 index 00000000000..3bc84151d27 --- /dev/null +++ b/usr.sbin/switchd/switchd.c @@ -0,0 +1,481 @@ +/* $OpenBSD: switchd.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_device_connect(struct privsep *, struct switch_device *); +int switch_device_cmp(struct switch_device *, struct switch_device *); + +__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_proc_init, + .p_shutdown = ofcconn_proc_shutdown } +}; + +__dead void +usage(void) +{ + extern const char *__progname; + fprintf(stderr, "usage: %s [-dv] [-D macro=value] [-f file] " + "[-c mac-cache-size] [-t cache-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; + unsigned int cache = SWITCHD_CACHE_MAX; + unsigned int timeout = SWITCHD_CACHE_TIMEOUT; + const char *conffile = SWITCHD_CONFIG; + + log_init(1, LOG_DAEMON); + + while ((c = getopt(argc, argv, "c:dD:f:ht: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 't': + timeout = strtonum(optarg, 0, UINT32_MAX, &errstr); + if (errstr != NULL) { + log_warn("cache timeout: %s", errstr); + usage(); + } + break; + case 'v': + 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; + + 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_conns); + + if (parse_config(sc->sc_conffile, sc) == -1) { + proc_kill(&sc->sc_ps); + exit(1); + } + + /* check for root privileges */ + if (geteuid()) + fatalx("need root privileges"); + + if ((ps->ps_pw = getpwnam(SWITCHD_USER)) == NULL) + fatalx("unknown user " SWITCHD_USER); + + /* Configure the control socket */ + ps->ps_csock.cs_name = SWITCHD_SOCKET; + + log_init(debug, LOG_DAEMON); + log_verbose(verbose); + + if (!debug && daemon(0, 0) == -1) + fatal("failed to daemonize"); + + ps->ps_ninstances = 1; + proc_init(ps, procs, nitems(procs)); + + setproctitle("parent"); + + 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_evsigchld, SIGCHLD, 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_evsigchld, NULL); + signal_add(&ps->ps_evsighup, NULL); + signal_add(&ps->ps_evsigpipe, NULL); + signal_add(&ps->ps_evsigusr1, NULL); + + proc_listen(ps, procs, nitems(procs)); + + 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, 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; + } + if (fcntl(s, F_SETFL, O_NONBLOCK) == -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) +{ + int fd; + if ((fd = open("/dev/tun0", O_WRONLY)) == -1) + return (-1); + return (fd); +} + + +void +parent_sig_handler(int sig, short event, void *arg) +{ + struct privsep *ps = arg; + int die = 0, status, fail, id; + pid_t pid; + char *cause; + + 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: + die = 1; + /* FALLTHROUGH */ + case SIGCHLD: + do { + pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) + continue; + + fail = 0; + if (WIFSIGNALED(status)) { + fail = 1; + asprintf(&cause, "terminated; signal %d", + WTERMSIG(status)); + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + fail = 1; + asprintf(&cause, "exited abnormally"); + } else + asprintf(&cause, "exited okay"); + } else + fatalx("unexpected cause of SIGCHLD"); + + die = 1; + + for (id = 0; id < PROC_MAX; id++) + if (pid == ps->ps_pid[id]) { + if (fail) + log_warnx("lost child: %s %s", + ps->ps_title[id], cause); + break; + } + + free(cause); + } while (pid > 0 || (pid == -1 && errno == EINTR)); + + if (die) + parent_shutdown(ps->ps_env); + break; + default: + fatalx("unexpected signal"); + } +} + +int +parent_configure(struct switchd *sc) +{ + struct switch_device *c; + + TAILQ_FOREACH(c, &sc->sc_conns, sdv_next) { + parent_device_connect(&sc->sc_ps, c); + } + + return (0); +} + +int +parent_reload(struct switchd *sc) +{ + struct switchd newconf; + struct switch_device *sdv, *osdv, *sdvn; + enum privsep_procid procid; + + memset(&newconf, 0, sizeof(newconf)); + TAILQ_INIT(&newconf.sc_conns); + + if (parse_config(sc->sc_conffile, &newconf) != -1) { + TAILQ_FOREACH_SAFE(sdv, &sc->sc_conns, sdv_next, sdvn) { + TAILQ_FOREACH(osdv, &newconf.sc_conns, sdv_next) { + if (switch_device_cmp(osdv, sdv) == 0) { + TAILQ_REMOVE(&newconf.sc_conns, + osdv, sdv_next); + break; + } + } + if (osdv == NULL) { + /* Removed */ + TAILQ_REMOVE(&sc->sc_conns, sdv, sdv_next); + procid = (sdv->sdv_swc.swc_type == + SWITCH_CONN_LOCAL) + ? PROC_OFP : PROC_OFCCONN; + proc_compose_imsg(&sc->sc_ps, procid, -1, + IMSG_CTL_DEVICE_DISCONNECT, + -1, -1, sdv, sizeof(*sdv)); + } else { + /* Keep the existing one */ + TAILQ_REMOVE(&newconf.sc_conns, osdv, sdv_next); + free(osdv); + } + } + TAILQ_FOREACH(sdv, &newconf.sc_conns, sdv_next) { + procid = + (sdv->sdv_swc.swc_type == SWITCH_CONN_LOCAL) + ? PROC_OFP : PROC_OFCCONN; + TAILQ_INSERT_TAIL(&sc->sc_conns, sdv, sdv_next); + parent_device_connect(&sc->sc_ps, sdv); + } + } + + return (0); +} + +int +parent_dispatch_control(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_CTL_DEVICE_CONNECT: + case IMSG_CTL_DEVICE_DISCONNECT: + if (IMSG_DATA_SIZE(imsg) < + sizeof(struct switch_device)) { + log_warnx("%s: IMSG_CTL_DEVICE_CONNECT: " + "message size is wrong", __func__); + return (0); + } + if (imsg->hdr.type == IMSG_CTL_DEVICE_CONNECT) + parent_device_connect(p->p_ps, imsg->data); + else { + /* + * Since we don't know which the device was attached + * to, we send the message to the both. + */ + proc_compose(p->p_ps, PROC_OFP, + imsg->hdr.type, imsg->data, IMSG_DATA_SIZE(imsg)); + proc_compose(p->p_ps, PROC_OFCCONN, + imsg->hdr.type, imsg->data, IMSG_DATA_SIZE(imsg)); + } + 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_device_connect(struct privsep *ps, struct switch_device *sdv) +{ + int fd; + + /* restrict the opening path to /dev/switch* */ + if (strncmp(sdv->sdv_device, "/dev/switch", 11) != 0) { + log_warnx("%s: device path is wrong: %s", __func__, + sdv->sdv_device); + goto on_error; + } + + if ((fd = open(sdv->sdv_device, O_RDWR | O_NONBLOCK)) == -1) { + log_warn("%s: open(%s) failed", __func__, sdv->sdv_device); + goto on_error; + } + + switch (sdv->sdv_swc.swc_type) { + case SWITCH_CONN_LOCAL: + proc_compose_imsg(ps, PROC_OFP, -1, IMSG_CTL_DEVICE_CONNECT, + -1, fd, sdv, sizeof(*sdv)); + break; + case SWITCH_CONN_TLS: + case SWITCH_CONN_TCP: + proc_compose_imsg(ps, PROC_OFCCONN, -1, IMSG_CTL_DEVICE_CONNECT, + -1, fd, sdv, sizeof(struct switch_device)); + break; + default: + fatalx("not implemented"); + } +on_error: + return; +} + +int +switch_device_cmp(struct switch_device *a, + struct switch_device *b) +{ + struct switch_controller *ca = &a->sdv_swc; + struct switch_controller *cb = &b->sdv_swc; + int c; + + if ((c = strcmp(a->sdv_device, b->sdv_device)) != 0) + return (c); + if ((c = cb->swc_type - ca->swc_type) != 0) + return (c); + + return (sockaddr_cmp((struct sockaddr *)&ca->swc_addr, + (struct sockaddr *)&cb->swc_addr, -1)); +} diff --git a/usr.sbin/switchd/switchd.conf.5 b/usr.sbin/switchd/switchd.conf.5 new file mode 100644 index 00000000000..a72982b16ae --- /dev/null +++ b/usr.sbin/switchd/switchd.conf.5 @@ -0,0 +1,115 @@ +.\" $OpenBSD: switchd.conf.5,v 1.1 2016/07/19 16:54:26 reyk Exp $ +.\" +.\" Copyright (c) 2014, 2015, 2016 Reyk Floeter +.\" Copyright (c) 2016 YASUOKA Masahiko +.\" +.\" 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. +.\" +.\" The following requests are required for all man pages. +.\" +.Dd $Mdocdate: July 19 2016 $ +.Dt SWITCHD.CONF 5 +.Os +.Sh NAME +.Nm switchd.conf +.Nd Switch daemon configuration file +.Sh DESCRIPTION +.Nm +is the configuration file for the switch daemon, +.Xr switchd 8 . +.Sh SECTIONS +.Nm +files is divided into tw main sections: +.Bl -tag -width xxxx +.It Sy Macros +User-defined variables may be defined and user later, simplifying the +configuration file. +.It Sy Global Configuration +Global runtime settings for +.Xr switchd 8 . +.El +.Pp +The current line can be extended over multiple lines using a backslash +.Pq Sq \e . +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +Care should be taken when commenting out multi-line text: +the comment is effective until the end of the entire block. +.Pp +Argument names not beginning with a letter, digit, or underscore +must be quoted. +.Pp +Additional configuration files can be included with the +.Ic include +keyword, for example: +.Bd -literal -offset indent +include "/etc/snmpd.conf.local" +.Ed +.Sh MACROS +Macros can be defined that will later be expanded in context. +Macro names must start with a letter, digit, or underscore, +and may contain any of those characters. +Macro names may not be reserved words (for example, +.Ic directory , +.Ic log , +or +.Ic root ) . +Macros are not expanded inside quotes. +.Pp +For example: +.Bd -literal -offset indent +ext_ip="10.0.0.1" +listen on $ext_ip +.Ed +.Sh GLOBAL CONFIGURATION +The following options can be set globally: +.Bl -tag -width Ds +.It Ic listen on Ar address Oo Ic tls Oc Op Ic port Ar port +Set the listen address and port to accept connections from remote +OpenFlow switches. +Secure connections can be enabled with the optional +.Ic tls +keyword. +.It Ic device on Ar device-name Oo Ic forward to Ar uri Oc +Attach to a +.Xr switch 4 +device. +When attached, +.Xr switchd 8 +will accept OpenFlow messages from the connected kernel interface. +The daemon either handles the requests locally or sends them to a remote +controller if the +.Ic forward to +directive is set. +The +.Ar uri +is the method and address to connect to the remote controller, +with the format +.Ar protocol:address:port +where the +.Ar protocol +can be either +.Dq tcp +or +.Dq tls . +.El +.Sh EXAMPLES +The folowing example is a typical one. +.Bd -literal -offset indent +listen on 0.0.0.0 port 6633 +device "/dev/switch0" +device "/dev/switch1" forward to tcp:192.168.0.1:6633 +.Ed +.Sh SEE ALSO +.Xr switchd 8 diff --git a/usr.sbin/switchd/switchd.h b/usr.sbin/switchd/switchd.h new file mode 100644 index 00000000000..28b3abdcf8f --- /dev/null +++ b/usr.sbin/switchd/switchd.h @@ -0,0 +1,228 @@ +/* $OpenBSD: switchd.h,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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. + */ + +#ifndef _SWITCHD_H +#define _SWITCHD_H + +#include +#include +#include + +#include +#include + +#include "ofp.h" +#include "ofp10.h" +#include "types.h" +#include "proc.h" + +struct switchd; + +struct timer { + struct event tmr_ev; + struct switchd *tmr_sc; + void (*tmr_cb)(struct switchd *, void *); + void *tmr_cbarg; +}; + +struct packet { + union { + struct ether_header *pkt_eh; + uint8_t *pkt_buf; + }; + size_t pkt_len; +}; + +struct macaddr { + uint8_t mac_addr[ETHER_ADDR_LEN]; + long mac_port; + time_t mac_age; + RB_ENTRY(macaddr) mac_entry; +}; +RB_HEAD(macaddr_head, macaddr); + +struct switch_control { + unsigned int sw_id; + struct sockaddr_storage sw_addr; + struct macaddr_head sw_addrcache; + struct timer sw_timer; + unsigned int sw_cachesize; + RB_ENTRY(switch_control) sw_entry; +}; +RB_HEAD(switch_head, switch_control); + +struct switch_connection { + unsigned int con_id; + int con_fd; + struct sockaddr_storage con_peer; + struct sockaddr_storage con_local; + struct switch_control *con_switch; + in_port_t con_port; + struct event con_ev; + struct switchd *con_sc; + uint32_t con_xidnxt; + TAILQ_ENTRY(switch_connection) + con_next; +}; + +struct switch_server { + int srv_fd; + int srv_tls; + struct sockaddr_storage srv_addr; + struct event srv_ev; + struct switchd *srv_sc; +}; + +struct switch_controller { + enum switch_conn_type swc_type; + struct sockaddr_storage swc_addr; +}; + +struct switch_device { + char sdv_device[PATH_MAX]; + struct switch_controller sdv_swc; + TAILQ_ENTRY(switch_device) + sdv_next; +}; + +struct switchd { + struct privsep sc_ps; + struct switch_server sc_server; + int sc_tap; + struct switch_head sc_switches; + uint32_t sc_swid; + unsigned int sc_cache_max; + unsigned int sc_cache_timeout; + char sc_conffile[PATH_MAX]; + TAILQ_HEAD(, switch_device) + sc_conns; +}; + +struct ofp_callback { + uint8_t cb_type; + int (*cb)(struct switchd *, struct switch_connection *, + struct ofp_header *, struct ibuf *); + int (*debug)(struct switchd *, struct sockaddr_storage *, + struct sockaddr_storage *, struct ofp_header *, + struct ibuf *); +}; + +/* switchd.c */ +int switchd_socket(struct sockaddr *, int); +int switchd_listen(struct sockaddr *); +int switchd_sockaddr(const char *, in_port_t, struct sockaddr_storage *); +int switchd_tap(void); +int switchd_open_device(struct privsep *, const char *, size_t); + +/* packet.c */ +long packet_input(struct switchd *, struct switch_control *, long, + struct ibuf *, size_t, struct packet *); + +/* switch.c */ +void switch_init(struct switchd *); +int switch_dispatch_control(int, struct privsep_proc *, + struct imsg *); +struct switch_control + *switch_add(struct switch_connection *); +void switch_remove(struct switchd *, struct switch_control *); +struct switch_control + *switch_get(struct switch_connection *); +struct macaddr *switch_learn(struct switchd *, struct switch_control *, + uint8_t *, long); +struct macaddr *switch_cached(struct switch_control *, uint8_t *); +RB_PROTOTYPE(switch_head, switch_control, sw_entry, switch_cmp); +RB_PROTOTYPE(macaddr_head, macaddr, mac_entry, switch_maccmp); + +/* timer.c */ +void timer_set(struct switchd *, struct timer *, + void (*)(struct switchd *, void *), void *); +void timer_add(struct switchd *, struct timer *, int); +void timer_del(struct switchd *, struct timer *); + +/* util.c */ +void socket_set_blockmode(int, enum blockmodes); +in_port_t socket_getport(struct sockaddr_storage *); +int sockaddr_cmp(struct sockaddr *, struct sockaddr *, int); +struct in6_addr *prefixlen2mask6(uint8_t, uint32_t *); +uint32_t prefixlen2mask(uint8_t); +const char *print_host(struct sockaddr_storage *, char *, size_t); +const char *print_ether(const uint8_t *) + __attribute__ ((__bounded__(__minbytes__,1,ETHER_ADDR_LEN))); +const char *print_map(unsigned int, struct constmap *); +void print_verbose(const char *emsg, ...) + __attribute__((__format__ (printf, 1, 2))); +void print_debug(const char *emsg, ...) + __attribute__((__format__ (printf, 1, 2))); +void getmonotime(struct timeval *); +int parsehostport(const char *, struct sockaddr *, socklen_t); + +/* ofp.c */ +pid_t ofp(struct privsep *, struct privsep_proc *); +void ofp_close(struct switch_connection *); +void ofp_read(int, short, void *); +int ofp_send(struct switch_connection *, struct ofp_header *, + struct ibuf *); +void ofp_accept(int, short, void *); + +/* ofp10.c */ +int ofp10_hello(struct switchd *, struct switch_connection *, + struct ofp_header *, struct ibuf *); +void ofp10_debug_header(struct switchd *, + struct sockaddr_storage *, struct sockaddr_storage *, + struct ofp_header *); +void ofp10_debug(struct switchd *, + struct sockaddr_storage *, struct sockaddr_storage *, + struct ofp_header *, struct ibuf *); +int ofp10_input(struct switchd *, struct switch_connection *, + struct ofp_header *, struct ibuf *); + +/* ofp13.c */ +void ofp13_debug(struct switchd *, + struct sockaddr_storage *, struct sockaddr_storage *, + struct ofp_header *, struct ibuf *); +int ofp13_input(struct switchd *, struct switch_connection *, + struct ofp_header *, struct ibuf *); + +/* ofcconn.c */ +pid_t ofcconn_proc_init(struct privsep *, struct privsep_proc *); +void ofcconn_proc_shutdown(void); + +/* imsg_util.c */ +struct ibuf *ibuf_new(void *, size_t); +struct ibuf *ibuf_static(void); +int ibuf_cat(struct ibuf *, struct ibuf *); +void ibuf_release(struct ibuf *); +size_t ibuf_length(struct ibuf *); +int ibuf_setsize(struct ibuf *, size_t); +uint8_t *ibuf_data(struct ibuf *); +void *ibuf_getdata(struct ibuf *, size_t); +ssize_t ibuf_dataleft(struct ibuf *); +size_t ibuf_dataoffset(struct ibuf *); +struct ibuf *ibuf_get(struct ibuf *, size_t); +struct ibuf *ibuf_dup(struct ibuf *); +struct ibuf *ibuf_random(size_t); +int ibuf_prepend(struct ibuf *, void *, size_t); +void *ibuf_advance(struct ibuf *, size_t); +void ibuf_zero(struct ibuf *); +void ibuf_reset(struct ibuf *); + +/* parse.y */ +int cmdline_symset(char *); +int parse_config(const char *, struct switchd *); + +#endif /* _SWITCHD_H */ diff --git a/usr.sbin/switchd/timer.c b/usr.sbin/switchd/timer.c new file mode 100644 index 00000000000..a21726071bf --- /dev/null +++ b/usr.sbin/switchd/timer.c @@ -0,0 +1,73 @@ +/* $OpenBSD: timer.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2010-2016 Reyk Floeter + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "switchd.h" + +void timer_callback(int, short, void *); + +void +timer_set(struct switchd *sc, struct timer *tmr, + void (*cb)(struct switchd *, void *), void *arg) +{ + tmr->tmr_sc = sc; + tmr->tmr_cb = cb; + tmr->tmr_cbarg = arg; + evtimer_set(&tmr->tmr_ev, timer_callback, tmr); +} + +void +timer_add(struct switchd *sc, struct timer *tmr, int timeout) +{ + struct timeval tv = { timeout }; + + if (evtimer_initialized(&tmr->tmr_ev) && + evtimer_pending(&tmr->tmr_ev, NULL)) + evtimer_del(&tmr->tmr_ev); + + evtimer_add(&tmr->tmr_ev, &tv); +} + +void +timer_del(struct switchd *sc, struct timer *tmr) +{ + if (tmr->tmr_sc == sc && tmr->tmr_cb && + evtimer_initialized(&tmr->tmr_ev)) + evtimer_del(&tmr->tmr_ev); +} + +void +timer_callback(int fd, short event, void *arg) +{ + struct timer *tmr = arg; + + if (tmr->tmr_cb) + tmr->tmr_cb(tmr->tmr_sc, tmr->tmr_cbarg); +} diff --git a/usr.sbin/switchd/types.h b/usr.sbin/switchd/types.h new file mode 100644 index 00000000000..7dba07ec190 --- /dev/null +++ b/usr.sbin/switchd/types.h @@ -0,0 +1,101 @@ +/* $OpenBSD: types.h,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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. + */ + +#ifndef _SWITCHD_TYPES_H +#define _SWITCHD_TYPES_H + +#ifndef SWITCHD_USER +#define SWITCHD_USER "_hostapd" +#endif + +#ifndef SWITCHD_NAME +#define SWITCHD_NAME "switch" +#endif + +#ifndef SWITCHD_CONFIG +#define SWITCHD_CONFIG "/etc/" SWITCHD_NAME "d.conf" +#endif +#define SWITCHD_SOCKET "/var/run/" SWITCHD_NAME "d.sock" + +#define SWITCHD_CYCLE_BUFFERS 8 /* # of static buffers for mapping */ +#define SWITCHD_READ_BUFFER 0xffff +#define SWITCHD_MSGBUF_MAX 0xffff + +#define SWITCHD_CTLR_PORT 6633 /* Previously used by OpenFlow */ +#define SWITCHD_CTLR_IANA_PORT 6653 /* Assigned by IANA for OpenFlow */ + +#define SWITCHD_CACHE_MAX 4096 /* Default MAC address cache limit */ +#define SWITCHD_CACHE_TIMEOUT 240 /* t/o in seconds for learned MACs */ + +#define SWITCHD_OFCCONN_TIMEOUT 20 /* connect timeout for OpenFlow ch. */ + +#ifndef ETHER_ADDR_LEN +#define ETHER_ADDR_LEN 6 +#endif + +struct constmap { + unsigned int cm_type; + const char *cm_name; + const char *cm_descr; +}; + +enum imsg_type { + IMSG_NONE = 0, + IMSG_CTL_VERBOSE, + IMSG_CTL_NOTIFY, + IMSG_CTL_OK, + IMSG_CTL_FAIL, + IMSG_CTL_END, + IMSG_CTL_RELOAD, + IMSG_CTL_RESET, + IMSG_CTL_SWITCH, + IMSG_CTL_MAC, + IMSG_CTL_SHOW_SUM, + IMSG_CTL_DEVICE_CONNECT, + IMSG_CTL_DEVICE_DISCONNECT +}; + +enum privsep_procid { + PROC_PARENT = 0, + PROC_OFP, + PROC_CONTROL, + PROC_OFCCONN, + PROC_MAX +} privsep_process; + +enum blockmodes { + BM_NORMAL, + BM_NONBLOCK +}; + +enum flushmode { + RESET_RELOAD = 0, + RESET_ALL +}; + +enum switch_conn_type { + SWITCH_CONN_LOCAL, + SWITCH_CONN_TCP, + SWITCH_CONN_TLS +}; + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#endif /* _SWITCHD_TYPES_H */ diff --git a/usr.sbin/switchd/util.c b/usr.sbin/switchd/util.c new file mode 100644 index 00000000000..56e712ac709 --- /dev/null +++ b/usr.sbin/switchd/util.c @@ -0,0 +1,334 @@ +/* $OpenBSD: util.c,v 1.1 2016/07/19 16:54:26 reyk Exp $ */ + +/* + * Copyright (c) 2013-2016 Reyk Floeter + * + * 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 +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "switchd.h" + +extern int debug; +extern int verbose; + +void +socket_set_blockmode(int fd, enum blockmodes bm) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) + fatal("fcntl F_GETFL"); + + if (bm == BM_NONBLOCK) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if ((flags = fcntl(fd, F_SETFL, flags)) == -1) + fatal("fcntl F_SETFL"); +} + +in_port_t +socket_getport(struct sockaddr_storage *ss) +{ + switch (ss->ss_family) { + case AF_INET: + return (ntohs(((struct sockaddr_in *)ss)->sin_port)); + case AF_INET6: + return (ntohs(((struct sockaddr_in6 *)ss)->sin6_port)); + default: + return (0); + } + + /* NOTREACHED */ + return (0); +} + +int +sockaddr_cmp(struct sockaddr *a, struct sockaddr *b, int prefixlen) +{ + struct sockaddr_in *a4, *b4; + struct sockaddr_in6 *a6, *b6; + uint32_t av[4], bv[4], mv[4]; + + if (a->sa_family == AF_UNSPEC || b->sa_family == AF_UNSPEC) + return (0); + else if (a->sa_family > b->sa_family) + return (1); + else if (a->sa_family < b->sa_family) + return (-1); + + if (prefixlen == -1) + memset(&mv, 0xff, sizeof(mv)); + + switch (a->sa_family) { + case AF_INET: + a4 = (struct sockaddr_in *)a; + b4 = (struct sockaddr_in *)b; + + av[0] = a4->sin_addr.s_addr; + bv[0] = b4->sin_addr.s_addr; + if (prefixlen != -1) + mv[0] = prefixlen2mask(prefixlen); + + if ((av[0] & mv[0]) > (bv[0] & mv[0])) + return (1); + if ((av[0] & mv[0]) < (bv[0] & mv[0])) + return (-1); + break; + case AF_INET6: + a6 = (struct sockaddr_in6 *)a; + b6 = (struct sockaddr_in6 *)b; + + memcpy(&av, &a6->sin6_addr.s6_addr, 16); + memcpy(&bv, &b6->sin6_addr.s6_addr, 16); + if (prefixlen != -1) + prefixlen2mask6(prefixlen, mv); + + if ((av[3] & mv[3]) > (bv[3] & mv[3])) + return (1); + if ((av[3] & mv[3]) < (bv[3] & mv[3])) + return (-1); + if ((av[2] & mv[2]) > (bv[2] & mv[2])) + return (1); + if ((av[2] & mv[2]) < (bv[2] & mv[2])) + return (-1); + if ((av[1] & mv[1]) > (bv[1] & mv[1])) + return (1); + if ((av[1] & mv[1]) < (bv[1] & mv[1])) + return (-1); + if ((av[0] & mv[0]) > (bv[0] & mv[0])) + return (1); + if ((av[0] & mv[0]) < (bv[0] & mv[0])) + return (-1); + break; + } + + return (0); +} + +uint32_t +prefixlen2mask(uint8_t prefixlen) +{ + if (prefixlen == 0) + return (0); + + if (prefixlen > 32) + prefixlen = 32; + + return (htonl(0xffffffff << (32 - prefixlen))); +} + +struct in6_addr * +prefixlen2mask6(uint8_t prefixlen, uint32_t *mask) +{ + static struct in6_addr s6; + int i; + + if (prefixlen > 128) + prefixlen = 128; + + bzero(&s6, sizeof(s6)); + for (i = 0; i < prefixlen / 8; i++) + s6.s6_addr[i] = 0xff; + i = prefixlen % 8; + if (i) + s6.s6_addr[prefixlen / 8] = 0xff00 >> i; + + memcpy(mask, &s6, sizeof(s6)); + + return (&s6); +} + +const char * +print_ether(const uint8_t *ea) +{ + static char sbuf[SWITCHD_CYCLE_BUFFERS] + [ETHER_ADDR_LEN * 2 + 5 + 1]; + static int idx = 0; + size_t len; + char *buf; + + buf = sbuf[idx]; + len = sizeof(sbuf[idx]); + if (++idx >= SWITCHD_CYCLE_BUFFERS) + idx = 0; + + snprintf(buf, len, "%02x:%02x:%02x:%02x:%02x:%02x", + ea[0], ea[1], ea[2], ea[3], ea[4], ea[5]); + + return (buf); +} + +const char * +print_host(struct sockaddr_storage *ss, char *buf, size_t len) +{ + static char sbuf[SWITCHD_CYCLE_BUFFERS][NI_MAXHOST + 7]; + struct sockaddr_un *un; + static int idx = 0; + char pbuf[7]; + in_port_t port; + + if (buf == NULL) { + buf = sbuf[idx]; + len = sizeof(sbuf[idx]); + if (++idx >= SWITCHD_CYCLE_BUFFERS) + idx = 0; + } + + if (ss->ss_family == AF_UNSPEC) { + strlcpy(buf, "any", len); + return (buf); + } else if (ss->ss_family == AF_LOCAL) { + un = (struct sockaddr_un *)ss; + strlcpy(buf, un->sun_path, len); + return (buf); + } + + if (getnameinfo((struct sockaddr *)ss, ss->ss_len, + buf, len, NULL, 0, NI_NUMERICHOST) != 0) { + buf[0] = '\0'; + return (NULL); + } + + if ((port = socket_getport(ss)) != 0) { + snprintf(pbuf, sizeof(pbuf), ":%d", port); + (void)strlcat(buf, pbuf, len); + } + + return (buf); +} + +const char * +print_map(unsigned int type, struct constmap *map) +{ + unsigned int i; + static char buf[SWITCHD_CYCLE_BUFFERS][32]; + static int idx = 0; + const char *name = NULL; + + if (idx >= SWITCHD_CYCLE_BUFFERS) + idx = 0; + bzero(buf[idx], sizeof(buf[idx])); + + for (i = 0; map[i].cm_name != NULL; i++) { + if (map[i].cm_type == type) { + name = map[i].cm_name; + break; + } + } + + if (name == NULL) + snprintf(buf[idx], sizeof(buf[idx]), "<%u>", type); + else + strlcpy(buf[idx], name, sizeof(buf[idx])); + + return (buf[idx++]); +} + +void +getmonotime(struct timeval *tv) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts)) + fatal("clock_gettime"); + + TIMESPEC_TO_TIMEVAL(tv, &ts); +} + +void +print_debug(const char *emsg, ...) +{ + va_list ap; + + if (debug && verbose > 2) { + va_start(ap, emsg); + vfprintf(stderr, emsg, ap); + va_end(ap); + } +} + +void +print_verbose(const char *emsg, ...) +{ + va_list ap; + + if (verbose) { + va_start(ap, emsg); + vfprintf(stderr, emsg, ap); + va_end(ap); + } +} + +int +parsehostport(const char *str, struct sockaddr *sa, socklen_t salen) +{ + char buf[NI_MAXHOST + NI_MAXSERV + 8], *servp, *nodep; + struct addrinfo hints, *ai; + + if (strlcpy(buf, str, sizeof(buf)) >= sizeof(buf)) + return (-1); + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = 0; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + if (buf[0] == '[' && + (servp = strchr(buf, ']')) != NULL && + (*(servp + 1) == '\0' || *(servp + 1) == ':')) { + hints.ai_family = AF_INET6; + hints.ai_flags = AI_NUMERICHOST; + nodep = buf + 1; + *servp++ = '\0'; + } else { + nodep = buf; + servp = strrchr(nodep, ':'); + } + if (servp != NULL) { + *servp = '\0'; + servp++; + } else + servp = NULL; + + if (getaddrinfo(nodep, servp, &hints, &ai) != 0) + return (-1); + + if (salen < ai->ai_addrlen) { + freeaddrinfo(ai); + return (-1); + } + memset(sa, 0, salen); + memcpy(sa, ai->ai_addr, ai->ai_addrlen); + + return (0); +}