diff options
| author | Timo Teräs <timo.teras@iki.fi> | 2017-01-19 17:27:01 +0200 | 
|---|---|---|
| committer | David Lamparter <equinox@opensourcerouting.org> | 2017-03-07 16:20:29 +0100 | 
| commit | 2fb975da777d27077b3580cb18390b5015b50fb8 (patch) | |
| tree | a0877c908b64c4dc1cfb6101e61420007038aeca /nhrpd/vici.c | |
| parent | 3b6134583fde24e136c4aaf30d6d3082b9cba48e (diff) | |
nhrpd: implement next hop resolution protocol
This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
[DL: cherry-picked from dafa05e65fe4b3b3ed5525443f554215ba14f42c]
[DL: merge partially resolved, this commit will not build.]
Signed-off-by: Timo Teräs <timo.teras@iki.fi>
Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
Diffstat (limited to 'nhrpd/vici.c')
| -rw-r--r-- | nhrpd/vici.c | 482 | 
1 files changed, 482 insertions, 0 deletions
diff --git a/nhrpd/vici.c b/nhrpd/vici.c new file mode 100644 index 0000000000..507dd14a9c --- /dev/null +++ b/nhrpd/vici.c @@ -0,0 +1,482 @@ +/* strongSwan VICI protocol implementation for NHRP + * Copyright (c) 2014-2015 Timo Teräs + * + * This file is free software: you may copy, redistribute and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + */ + +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include "thread.h" +#include "zbuf.h" +#include "log.h" +#include "nhrpd.h" + +#include "vici.h" + +#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) + +struct blob { +	char *ptr; +	int len; +}; + +static int blob_equal(const struct blob *b, const char *str) +{ +	if (b->len != (int) strlen(str)) return 0; +	return memcmp(b->ptr, str, b->len) == 0; +} + +static int blob2buf(const struct blob *b, char *buf, size_t n) +{ +	if (b->len >= (int) n) return 0; +	memcpy(buf, b->ptr, b->len); +	buf[b->len] = 0; +	return 1; +} + +struct vici_conn { +	struct thread *t_reconnect, *t_read, *t_write; +	struct zbuf ibuf; +	struct zbuf_queue obuf; +	int fd; +	uint8_t ibuf_data[VICI_MAX_MSGLEN]; +}; + +struct vici_message_ctx { +	const char *sections[8]; +	int nsections; +}; + +static int vici_reconnect(struct thread *t); +static void vici_submit_request(struct vici_conn *vici, const char *name, ...); + +static void vici_zbuf_puts(struct zbuf *obuf, const char *str) +{ +	size_t len = strlen(str); +	zbuf_put8(obuf, len); +	zbuf_put(obuf, str, len); +} + +static void vici_connection_error(struct vici_conn *vici) +{ +	nhrp_vc_reset(); + +	THREAD_OFF(vici->t_read); +	THREAD_OFF(vici->t_write); +	zbuf_reset(&vici->ibuf); +	zbufq_reset(&vici->obuf); + +	close(vici->fd); +	vici->fd = -1; +	THREAD_TIMER_ON(master, vici->t_reconnect, vici_reconnect, vici, 2); +} + +static void vici_parse_message( +	struct vici_conn *vici, struct zbuf *msg, +	void (*parser)(struct vici_message_ctx *ctx, enum vici_type_t msgtype, const struct blob *key, const struct blob *val), +	struct vici_message_ctx *ctx) +{ +	uint8_t *type; +	struct blob key; +	struct blob val; + +	while ((type = zbuf_may_pull(msg, uint8_t)) != NULL) { +		switch (*type) { +		case VICI_SECTION_START: +			key.len = zbuf_get8(msg); +			key.ptr = zbuf_pulln(msg, key.len); +			debugf(NHRP_DEBUG_VICI, "VICI: Section start '%.*s'", key.len, key.ptr); +			parser(ctx, *type, &key, NULL); +			ctx->nsections++; +			break; +		case VICI_SECTION_END: +			debugf(NHRP_DEBUG_VICI, "VICI: Section end"); +			parser(ctx, *type, NULL, NULL); +			ctx->nsections--; +			break; +		case VICI_KEY_VALUE: +			key.len = zbuf_get8(msg); +			key.ptr = zbuf_pulln(msg, key.len); +			val.len = zbuf_get_be16(msg); +			val.ptr = zbuf_pulln(msg, val.len); +			debugf(NHRP_DEBUG_VICI, "VICI: Key '%.*s'='%.*s'", key.len, key.ptr, val.len, val.ptr); +			parser(ctx, *type, &key, &val); +			break; +		case VICI_LIST_START: +			key.len = zbuf_get8(msg); +			key.ptr = zbuf_pulln(msg, key.len); +			debugf(NHRP_DEBUG_VICI, "VICI: List start '%.*s'", key.len, key.ptr); +			break; +		case VICI_LIST_ITEM: +			val.len = zbuf_get_be16(msg); +			val.ptr = zbuf_pulln(msg, val.len); +			debugf(NHRP_DEBUG_VICI, "VICI: List item: '%.*s'", val.len, val.ptr); +			parser(ctx, *type, &key, &val); +			break; +		case VICI_LIST_END: +			debugf(NHRP_DEBUG_VICI, "VICI: List end"); +			break; +		default: +			debugf(NHRP_DEBUG_VICI, "VICI: Unsupported message component type %d", *type); +			return; +		} +	} +} + +struct handle_sa_ctx { +	struct vici_message_ctx msgctx; +	int event; +	int child_ok; +	int kill_ikesa; +	uint32_t child_uniqueid, ike_uniqueid; +	struct { +		union sockunion host; +		struct blob id, cert; +	} local, remote; +}; + +static void parse_sa_message( +	struct vici_message_ctx *ctx, +	enum vici_type_t msgtype, +	const struct blob *key, const struct blob *val) +{ +	struct handle_sa_ctx *sactx = container_of(ctx, struct handle_sa_ctx, msgctx); +	struct nhrp_vc *vc; +	char buf[512]; + +	switch (msgtype) { +	case VICI_SECTION_START: +		if (ctx->nsections == 3) { +			/* Begin of child-sa section, reset child vars */ +			sactx->child_uniqueid = 0; +			sactx->child_ok = 0; +		} +		break; +	case VICI_SECTION_END: +		if (ctx->nsections == 3) { +			/* End of child-sa section, update nhrp_vc */ +			int up = sactx->child_ok || sactx->event == 1; +			if (up) { +				vc = nhrp_vc_get(&sactx->local.host, &sactx->remote.host, up); +				if (vc) { +					blob2buf(&sactx->local.id, vc->local.id, sizeof(vc->local.id)); +					if (blob2buf(&sactx->local.cert, (char*)vc->local.cert, sizeof(vc->local.cert))) +						vc->local.certlen = sactx->local.cert.len; +					blob2buf(&sactx->remote.id, vc->remote.id, sizeof(vc->remote.id)); +					if (blob2buf(&sactx->remote.cert, (char*)vc->remote.cert, sizeof(vc->remote.cert))) +						vc->remote.certlen = sactx->remote.cert.len; +					sactx->kill_ikesa |= nhrp_vc_ipsec_updown(sactx->child_uniqueid, vc); +				} +			} else { +				nhrp_vc_ipsec_updown(sactx->child_uniqueid, 0); +			} +		} +		break; +	default: +		switch (key->ptr[0]) { +		case 'l': +			if (blob_equal(key, "local-host") && ctx->nsections == 1) { +				if (blob2buf(val, buf, sizeof(buf))) +					str2sockunion(buf, &sactx->local.host); +			} else if (blob_equal(key, "local-id") && ctx->nsections == 1) { +				sactx->local.id = *val; +			} else if (blob_equal(key, "local-cert-data") && ctx->nsections == 1) { +				sactx->local.cert = *val; +			} +			break; +		case 'r': +			if (blob_equal(key, "remote-host") && ctx->nsections == 1) { +				if (blob2buf(val, buf, sizeof(buf))) +					str2sockunion(buf, &sactx->remote.host); +			} else if (blob_equal(key, "remote-id") && ctx->nsections == 1) { +				sactx->remote.id = *val; +			} else if (blob_equal(key, "remote-cert-data") && ctx->nsections == 1) { +				sactx->remote.cert = *val; +			} +			break; +		case 'u': +			if (blob_equal(key, "uniqueid") && blob2buf(val, buf, sizeof(buf))) { +				if (ctx->nsections == 3) +					sactx->child_uniqueid = strtoul(buf, NULL, 0); +				else if (ctx->nsections == 1) +					sactx->ike_uniqueid = strtoul(buf, NULL, 0); +			} +			break; +		case 's': +			if (blob_equal(key, "state") && ctx->nsections == 3) { +				sactx->child_ok = +					(sactx->event == 0 && +					 (blob_equal(val, "INSTALLED") || +					  blob_equal(val, "REKEYED"))); +			} +			break; +		} +		break; +	} +} + +static void vici_recv_sa(struct vici_conn *vici, struct zbuf *msg, int event) +{ +	char buf[32]; +	struct handle_sa_ctx ctx = { +		.event = event, +	}; + +	vici_parse_message(vici, msg, parse_sa_message, &ctx.msgctx); + +	if (ctx.kill_ikesa && ctx.ike_uniqueid) { +		debugf(NHRP_DEBUG_COMMON, "VICI: Deleting IKE_SA %u", ctx.ike_uniqueid); +		snprintf(buf, sizeof buf, "%u", ctx.ike_uniqueid); +		vici_submit_request( +			vici, "terminate", +			VICI_KEY_VALUE, "ike-id", strlen(buf), buf, +			VICI_END); +	} +} + +static void vici_recv_message(struct vici_conn *vici, struct zbuf *msg) +{ +	uint32_t msglen; +	uint8_t msgtype; +	struct blob name; + +	msglen = zbuf_get_be32(msg); +	msgtype = zbuf_get8(msg); +	debugf(NHRP_DEBUG_VICI, "VICI: Message %d, %d bytes", msgtype, msglen); + +	switch (msgtype) { +	case VICI_EVENT: +		name.len = zbuf_get8(msg); +		name.ptr = zbuf_pulln(msg, name.len); + +		debugf(NHRP_DEBUG_VICI, "VICI: Event '%.*s'", name.len, name.ptr); +		if (blob_equal(&name, "list-sa") || +		    blob_equal(&name, "child-updown") || +		    blob_equal(&name, "child-rekey")) +			vici_recv_sa(vici, msg, 0); +		else if (blob_equal(&name, "child-state-installed") || +			 blob_equal(&name, "child-state-rekeyed")) +			vici_recv_sa(vici, msg, 1); +		else if (blob_equal(&name, "child-state-destroying")) +			vici_recv_sa(vici, msg, 2); +		break; +	case VICI_EVENT_UNKNOWN: +		zlog_err("VICI: StrongSwan does not support mandatory events (unpatched?)"); +		break; +	case VICI_EVENT_CONFIRM: +	case VICI_CMD_RESPONSE: +		break; +	default: +		zlog_notice("VICI: Unrecognized message type %d", msgtype); +		break; +	} +} + +static int vici_read(struct thread *t) +{ +	struct vici_conn *vici = THREAD_ARG(t); +	struct zbuf *ibuf = &vici->ibuf; +	struct zbuf pktbuf; + +	vici->t_read = NULL; +	if (zbuf_read(ibuf, vici->fd, (size_t) -1) < 0) { +		vici_connection_error(vici); +		return 0; +	} + +	/* Process all messages in buffer */ +	do { +		uint32_t *hdrlen = zbuf_may_pull(ibuf, uint32_t); +		if (!hdrlen) +			break; +		if (!zbuf_may_pulln(ibuf, ntohl(*hdrlen))) { +			zbuf_reset_head(ibuf, hdrlen); +			break; +		} + +		/* Handle packet */ +		zbuf_init(&pktbuf, hdrlen, htonl(*hdrlen)+4, htonl(*hdrlen)+4); +		vici_recv_message(vici, &pktbuf); +	} while (1); + +	THREAD_READ_ON(master, vici->t_read, vici_read, vici, vici->fd); +	return 0; +} + +static int vici_write(struct thread *t) +{ +	struct vici_conn *vici = THREAD_ARG(t); +	int r; + +	vici->t_write = NULL; +	r = zbufq_write(&vici->obuf, vici->fd); +	if (r > 0) { +		THREAD_WRITE_ON(master, vici->t_write, vici_write, vici, vici->fd); +	} else if (r < 0) { +		vici_connection_error(vici); +	} + +	return 0; +} + +static void vici_submit(struct vici_conn *vici, struct zbuf *obuf) +{ +	if (vici->fd < 0) { +		zbuf_free(obuf); +		return; +	} + +	zbufq_queue(&vici->obuf, obuf); +	THREAD_WRITE_ON(master, vici->t_write, vici_write, vici, vici->fd); +} + +static void vici_submit_request(struct vici_conn *vici, const char *name, ...) +{ +	struct zbuf *obuf; +	uint32_t *hdrlen; +	va_list va; +	size_t len; +	int type; + +	obuf = zbuf_alloc(256); +	if (!obuf) return; + +	hdrlen = zbuf_push(obuf, uint32_t); +	zbuf_put8(obuf, VICI_CMD_REQUEST); +	vici_zbuf_puts(obuf, name); + +	va_start(va, name); +	for (type = va_arg(va, int); type != VICI_END; type = va_arg(va, int)) { +		zbuf_put8(obuf, type); +		switch (type) { +		case VICI_KEY_VALUE: +			vici_zbuf_puts(obuf, va_arg(va, const char *)); +			len = va_arg(va, size_t); +			zbuf_put_be16(obuf, len); +			zbuf_put(obuf, va_arg(va, void *), len); +			break; +		case VICI_END: +			break; +		default: +			break; +		} +	} +	va_end(va); +	*hdrlen = htonl(zbuf_used(obuf) - 4); +	vici_submit(vici, obuf); +} + +static void vici_register_event(struct vici_conn *vici, const char *name) +{ +	struct zbuf *obuf; +	uint32_t *hdrlen; +	uint8_t namelen; + +	namelen = strlen(name); +	obuf = zbuf_alloc(4 + 1 + 1 + namelen); +	if (!obuf) return; + +	hdrlen = zbuf_push(obuf, uint32_t); +	zbuf_put8(obuf, VICI_EVENT_REGISTER); +	zbuf_put8(obuf, namelen); +	zbuf_put(obuf, name, namelen); +	*hdrlen = htonl(zbuf_used(obuf) - 4); + +	vici_submit(vici, obuf); +} + +static int vici_reconnect(struct thread *t) +{ +	struct vici_conn *vici = THREAD_ARG(t); +	int fd; + +	vici->t_reconnect = NULL; +	if (vici->fd >= 0) return 0; + +	fd = sock_open_unix("/var/run/charon.vici"); +	if (fd < 0) { +		zlog_warn("%s: failure connecting VICI socket: %s", +			__PRETTY_FUNCTION__, strerror(errno)); +		THREAD_TIMER_ON(master, vici->t_reconnect, vici_reconnect, vici, 2); +		return 0; +	} + +	debugf(NHRP_DEBUG_COMMON, "VICI: Connected"); +	vici->fd = fd; +	THREAD_READ_ON(master, vici->t_read, vici_read, vici, vici->fd); + +	/* Send event subscribtions */ +	//vici_register_event(vici, "child-updown"); +	//vici_register_event(vici, "child-rekey"); +	vici_register_event(vici, "child-state-installed"); +	vici_register_event(vici, "child-state-rekeyed"); +	vici_register_event(vici, "child-state-destroying"); +	vici_register_event(vici, "list-sa"); +	vici_submit_request(vici, "list-sas", VICI_END); + +	return 0; +} + +static struct vici_conn vici_connection; + +void vici_init(void) +{ +	struct vici_conn *vici = &vici_connection; + +	vici->fd = -1; +	zbuf_init(&vici->ibuf, vici->ibuf_data, sizeof(vici->ibuf_data), 0); +	zbufq_init(&vici->obuf); +	THREAD_TIMER_MSEC_ON(master, vici->t_reconnect, vici_reconnect, vici, 10); +} + +void vici_terminate(void) +{ +} + +void vici_request_vc(const char *profile, union sockunion *src, union sockunion *dst, int prio) +{ +	struct vici_conn *vici = &vici_connection; +	char buf[2][SU_ADDRSTRLEN]; + +	sockunion2str(src, buf[0], sizeof buf[0]); +	sockunion2str(dst, buf[1], sizeof buf[1]); + +	vici_submit_request( +		vici, "initiate", +		VICI_KEY_VALUE, "child", strlen(profile), profile, +		VICI_KEY_VALUE, "timeout", 2, "-1", +		VICI_KEY_VALUE, "async", 1, "1", +		VICI_KEY_VALUE, "init-limits", 1, prio ? "0" : "1", +		VICI_KEY_VALUE, "my-host", strlen(buf[0]), buf[0], +		VICI_KEY_VALUE, "other-host", strlen(buf[1]), buf[1], +		VICI_END); +} + +int sock_open_unix(const char *path) +{ +	int ret, fd; +	struct sockaddr_un addr; + +	fd = socket(AF_UNIX, SOCK_STREAM, 0); +	if (fd < 0) +		return -1; + +	memset(&addr, 0, sizeof (struct sockaddr_un)); +	addr.sun_family = AF_UNIX; +	strncpy(addr.sun_path, path, strlen (path)); + +	ret = connect(fd, (struct sockaddr *) &addr, sizeof(addr.sun_family) + strlen(addr.sun_path)); +	if (ret < 0) { +		close(fd); +		return -1; +	} + +	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + +	return fd; +}  | 
