[BACK]Return to mrt.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / bgpd

File: [local] / src / usr.sbin / bgpd / mrt.c (download)

Revision 1.28, Fri Feb 27 20:53:56 2004 UTC (20 years, 3 months ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_3_5_BASE, OPENBSD_3_5
Changes since 1.27: +3 -2 lines

remove unneded peer pointer in struct prefix and change a in_addr_t to
struct in_addr. OK henning@

/*	$OpenBSD: mrt.c,v 1.28 2004/02/27 20:53:56 claudio Exp $ */

/*
 * Copyright (c) 2003, 2004 Claudio Jeker <claudio@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/queue.h>

#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "bgpd.h"
#include "rde.h"
#include "session.h"

#include "mrt.h"

static u_int16_t	mrt_attr_length(struct attr_flags *);
static int		mrt_attr_dump(void *, u_int16_t, struct attr_flags *);
static int		mrt_dump_entry(struct mrt_config *, struct prefix *,
			    u_int16_t, struct peer_config *);
static int		mrt_dump_header(struct buf *, u_int16_t, u_int16_t,
			    u_int32_t);
static int		mrt_open(struct mrt *);

#define DUMP_BYTE(x, b)							\
	do {								\
		u_char		t = (b);				\
		if (imsg_add((x), &t, sizeof(t)) == -1) {		\
			log_warnx("mrt_dump1: imsg_add error");		\
			return (-1);					\
		}							\
	} while (0)

#define DUMP_SHORT(x, s)						\
	do {								\
		u_int16_t	t;					\
		t = htons((s));						\
		if (imsg_add((x), &t, sizeof(t)) == -1) {		\
			log_warnx("mrt_dump2: imsg_add error");		\
			return (-1);					\
		}							\
	} while (0)

#define DUMP_LONG(x, l)							\
	do {								\
		u_int32_t	t;					\
		t = htonl((l));						\
		if (imsg_add((x), &t, sizeof(t)) == -1) {		\
			log_warnx("mrt_dump3: imsg_add error");		\
			return (-1);					\
		}							\
	} while (0)

#define DUMP_NLONG(x, l)						\
	do {								\
		u_int32_t	t = (l);				\
		if (imsg_add((x), &t, sizeof(t)) == -1) {		\
			log_warnx("mrt_dump4: imsg_add error");		\
			return (-1);					\
		}							\
	} while (0)

int
mrt_dump_bgp_msg(struct mrt_config *mrt, void *pkg, u_int16_t pkglen,
    struct peer_config *peer, struct bgpd_config *bgp)
{
	struct buf	*buf;
	u_int16_t	 len;

	len = pkglen + MRT_BGP4MP_HEADER_SIZE;

	if ((buf = imsg_create(mrt->ibuf, IMSG_MRT_MSG, mrt->id,
	    len + MRT_HEADER_SIZE)) == NULL) {
		log_warnx("mrt_dump_bgp_msg: imsg_open error");
		return (-1);
	}

	if (mrt_dump_header(buf, MSG_PROTOCOL_BGP4MP, BGP4MP_MESSAGE,
	    len) == -1) {
		log_warnx("mrt_dump_bgp_msg: imsg_add error");
		return (-1);
	}

	DUMP_SHORT(buf, bgp->as);
	DUMP_SHORT(buf, peer->remote_as);
	DUMP_SHORT(buf, /* ifindex */ 0);
	DUMP_SHORT(buf, 4);
	DUMP_NLONG(buf, peer->local_addr.v4.s_addr);
	DUMP_NLONG(buf, peer->remote_addr.v4.s_addr);

	if (imsg_add(buf, pkg, pkglen) == -1) {
		log_warnx("mrt_dump_bgp_msg: imsg_add error");
		return (-1);
	}

	if ((imsg_close(mrt->ibuf, buf)) == -1) {
		log_warnx("mrt_dump_bgp_msg: imsg_close error");
		return (-1);
	}

	return (len + MRT_HEADER_SIZE);
}

int
mrt_dump_state(struct mrt_config *mrt, u_int16_t old_state, u_int16_t new_state,
    struct peer_config *peer, struct bgpd_config *bgp)
{
	struct buf	*buf;
	u_int16_t	 len;

	len = 4 + MRT_BGP4MP_HEADER_SIZE;

	if ((buf = imsg_create(mrt->ibuf, IMSG_MRT_MSG, mrt->id,
	    len + MRT_HEADER_SIZE)) == NULL) {
		log_warnx("mrt_dump_bgp_state: imsg_open error");
		return (-1);
	}

	if (mrt_dump_header(buf, MSG_PROTOCOL_BGP4MP, BGP4MP_STATE_CHANGE,
	    len) == -1) {
		log_warnx("mrt_dump_bgp_state: imsg_add error");
		return (-1);
	}

	DUMP_SHORT(buf, bgp->as);
	DUMP_SHORT(buf, peer->remote_as);
	DUMP_SHORT(buf, /* ifindex */ 0);
	DUMP_SHORT(buf, 4);
	DUMP_NLONG(buf, peer->local_addr.v4.s_addr);
	DUMP_NLONG(buf, peer->remote_addr.v4.s_addr);

	DUMP_SHORT(buf, old_state);
	DUMP_SHORT(buf, new_state);

	if ((imsg_close(mrt->ibuf, buf)) == -1) {
		log_warnx("mrt_dump_bgp_state: imsg_close error");
		return (-1);
	}

	return (len + MRT_HEADER_SIZE);
}

static u_int16_t
mrt_attr_length(struct attr_flags *a)
{
	struct attr	*oa;
	u_int16_t	 alen, plen;

	alen = 4 /* origin */ + 7 /* nexthop */ + 7 /* lpref */;
	plen = aspath_length(a->aspath);
	alen += 2 + plen + (plen > 255 ? 2 : 1);
	if (a->med != 0)
		alen += 7;

	TAILQ_FOREACH(oa, &a->others, attr_l)
		alen += 2 + oa->len + (oa->len > 255 ? 2 : 1);

	return alen;
}

static int
mrt_attr_dump(void *p, u_int16_t len, struct attr_flags *a)
{
	struct attr	*oa;
	u_char		*buf = p;
	u_int32_t	 tmp32;
	int		 r;
	u_int16_t	 aslen, wlen = 0;

	/* origin */
	if ((r = attr_write(buf + wlen, len, ATTR_WELL_KNOWN, ATTR_ORIGIN,
	    &a->origin, 1)) == -1)
		return (-1);
	wlen += r; len -= r;

	/* aspath */
	aslen = aspath_length(a->aspath);
	if ((r = attr_write(buf + wlen, len, ATTR_WELL_KNOWN, ATTR_ASPATH,
	    aspath_dump(a->aspath), aslen)) == -1)
		return (-1);
	wlen += r; len -= r;

	/* nexthop, already network byte order */
	if ((r = attr_write(buf + wlen, len, ATTR_WELL_KNOWN, ATTR_NEXTHOP,
	    &a->nexthop, 4)) ==	-1)
		return (-1);
	wlen += r; len -= r;

	/* MED, non transitive */
	if (a->med != 0) {
		tmp32 = htonl(a->med);
		if ((r = attr_write(buf + wlen, len, ATTR_OPTIONAL, ATTR_MED,
		    &tmp32, 4)) == -1)
			return (-1);
		wlen += r; len -= r;
	}

	/* local preference, only valid for ibgp */
	tmp32 = htonl(a->lpref);
	if ((r = attr_write(buf + wlen, len, ATTR_WELL_KNOWN, ATTR_LOCALPREF,
	    &tmp32, 4)) == -1)
		return (-1);
	wlen += r; len -= r;

	/* dump all other path attributes without modification */
	TAILQ_FOREACH(oa, &a->others, attr_l) {
		if ((r = attr_write(buf + wlen, len, oa->flags, oa->type,
		    oa->data, oa->len)) == -1)
			return (-1);
		wlen += r; len -= r;
	}

	return (wlen);
}

static int
mrt_dump_entry(struct mrt_config *mrt, struct prefix *p, u_int16_t snum,
    struct peer_config *peer)
{
	struct buf	*buf;
	void		*bptr;
	u_int16_t	 len, attr_len;

	attr_len = mrt_attr_length(&p->aspath->flags);
	len = MRT_DUMP_HEADER_SIZE + attr_len;

	if ((buf = imsg_create(mrt->ibuf, IMSG_MRT_MSG, mrt->id,
	    len + MRT_HEADER_SIZE)) == NULL) {
		log_warnx("mrt_dump_entry: imsg_open error");
		return (-1);
	}

	if (mrt_dump_header(buf, MSG_TABLE_DUMP, AFI_IPv4, len) == -1) {
		log_warnx("mrt_dump_bgp_msg: buf_add error");
		return (-1);
	}

	DUMP_SHORT(buf, 0);
	DUMP_SHORT(buf, snum);
	DUMP_NLONG(buf, p->prefix->prefix.v4.s_addr);
	DUMP_BYTE(buf, p->prefix->prefixlen);
	DUMP_BYTE(buf, 1);		/* state */
	DUMP_LONG(buf, p->lastchange);	/* originated */
	DUMP_NLONG(buf, peer->remote_addr.v4.s_addr);
	DUMP_SHORT(buf, peer->remote_as);
	DUMP_SHORT(buf, attr_len);

	if ((bptr = buf_reserve(buf, attr_len)) == NULL) {
		log_warnx("mrt_dump_entry: buf_reserve error");
		buf_free(buf);
		return (-1);
	}

	if (mrt_attr_dump(bptr, attr_len, &p->aspath->flags) == -1) {
		log_warnx("mrt_dump_entry: mrt_attr_dump error");
		buf_free(buf);
		return (-1);
	}

	if ((imsg_close(mrt->ibuf, buf)) == -1) {
		log_warnx("mrt_dump_bgp_state: imsg_close error");
		return (-1);
	}

	return (len + MRT_HEADER_SIZE);
}

static u_int16_t sequencenum = 0;

void
mrt_clear_seq(void)
{
	sequencenum = 0;
}

void
mrt_dump_upcall(struct pt_entry *pt, void *ptr)
{
	struct mrt_config	*mrtbuf = ptr;
	struct prefix		*p;

	/*
	 * dump all prefixes even the inactive ones. That is the way zebra
	 * dumps the table so we do the same. If only the active route should
	 * be dumped p should be set to p = pt->active.
	 */
	LIST_FOREACH(p, &pt->prefix_h, prefix_l)
		mrt_dump_entry(mrtbuf, p, sequencenum++,
		    &p->aspath->peer->conf);
}

static int
mrt_dump_header(struct buf *buf, u_int16_t type, u_int16_t subtype,
    u_int32_t len)
{
	time_t			now;

	now = time(NULL);

	DUMP_LONG(buf, now);
	DUMP_SHORT(buf, type);
	DUMP_SHORT(buf, subtype);
	DUMP_LONG(buf, len);

	return (0);
}

static struct imsgbuf	*mrt_imsgbuf[2];

void
mrt_init(struct imsgbuf *rde, struct imsgbuf *se)
{
	mrt_imsgbuf[0] = rde;
	mrt_imsgbuf[1] = se;
}

static int
mrt_open(struct mrt *mrt)
{
	time_t	now;

	now = time(NULL);
	if (strftime(mrt->file, sizeof(mrt->file), mrt->name,
		    localtime(&now)) == 0) {
		log_warnx("mrt_open: strftime conversion failed");
		mrt->msgbuf.sock = -1;
		return (0);
	}

	mrt->msgbuf.sock = open(mrt->file,
	    O_WRONLY|O_NONBLOCK|O_CREAT|O_TRUNC, 0644);
	if (mrt->msgbuf.sock == -1) {
		log_warnx("mrt_open %s: %s",
		    mrt->file, strerror(errno));
		return (0);
	}
	return (1);
}

static int
mrt_close(struct mrt *mrt)
{
	/*
	 * close the mrt filedescriptor but first ensure that the last
	 * mrt message was written correctly. If not mrt_write needs to do
	 * that the next time called.
	 * To ensure this we need to fiddle around with internal msgbuf stuff.
	 */
	if (msgbuf_unbounded(&mrt->msgbuf))
		return (0);

	if (mrt->msgbuf.sock != -1) {
		close(mrt->msgbuf.sock);
		mrt->msgbuf.sock = -1;
	}

	return (1);
}

void
mrt_abort(struct mrt *mrt)
{
	/*
	 * something failed horribly. Stop all dumping and go back to start
	 * position. Retry after MRT_MIN_RETRY or ReopenTimerInterval. Which-
	 * ever is bigger.
	 */
	msgbuf_clear(&mrt->msgbuf);
	mrt_close(mrt);
	mrt->state = MRT_STATE_STOPPED;

	if (MRT_MIN_RETRY > mrt->ReopenTimerInterval)
		mrt->ReopenTimer = MRT_MIN_RETRY + time(NULL);
	else
		mrt->ReopenTimer = mrt->ReopenTimerInterval + time(NULL);
}

int
mrt_queue(struct mrt_head *mrtc, struct imsg *imsg)
{
	struct buf	*wbuf;
	struct mrt	*m;
	ssize_t		 len;
	int		 n;

	if (imsg->hdr.type != IMSG_MRT_MSG && imsg->hdr.type != IMSG_MRT_END)
		return (-1);

	LIST_FOREACH(m, mrtc, list) {
		if (m->conf.id != imsg->hdr.peerid)
			continue;
		if (m->state != MRT_STATE_RUNNING &&
		    m->state != MRT_STATE_REOPEN)
			return (0);

		if (imsg->hdr.type == IMSG_MRT_END) {
			m->state = MRT_STATE_CLOSE;
			return (0);
		}

		len = imsg->hdr.len - IMSG_HEADER_SIZE;
		wbuf = buf_open(len);
		if (wbuf == NULL)
			return (-1);
		if (buf_add(wbuf, imsg->data, len) == -1) {
			buf_free(wbuf);
			return (-1);
		}
		if ((n = buf_close(&m->msgbuf, wbuf)) < 0) {
			buf_free(wbuf);
			return (-1);
		}
		return (n);
	}
	return (0);
}

int
mrt_write(struct mrt *mrt)
{
	int	r;

	if (mrt->state == MRT_STATE_REOPEN ||
	    mrt->state == MRT_STATE_REMOVE)
		r = msgbuf_writebound(&mrt->msgbuf);
	else
		r = msgbuf_write(&mrt->msgbuf);

	switch (r) {
	case 1:
		/* only msgbuf_writebound returns 1 */
		break;
	case 0:
		if (mrt->state == MRT_STATE_CLOSE && mrt->msgbuf.queued == 0) {
			if (mrt_close(mrt) != 1) {
				log_warnx("mrt_write: mrt_close failed");
				mrt_abort(mrt);
				return (0);
			}
			mrt->state = MRT_STATE_STOPPED;
		}
		return (0);
	case -1:
		log_warnx("mrt_write: msgbuf_write: %s",
		    strerror(errno));
		mrt_abort(mrt);
		return (0);
	case -2:
		log_warnx("mrt_write: msgbuf_write: %s",
		    "connection closed");
		mrt_abort(mrt);
		return (0);
	default:
		fatalx("mrt_write: unexpected retval from msgbuf_write");
	}

	if (mrt_close(mrt) != 1) {
		log_warnx("mrt_write: mrt_close failed");
		mrt_abort(mrt);
		return (0);
	}

	switch (mrt->state) {
	case MRT_STATE_REMOVE:
		/*
		 * Remove request: free all left buffers and
		 * remove the descriptor.
		 */
		msgbuf_clear(&mrt->msgbuf);
		LIST_REMOVE(mrt, list);
		free(mrt);
		return (0);
	case MRT_STATE_REOPEN:
		if (mrt_open(mrt) == 0) {
			mrt_abort(mrt);
			return (0);
		} else {
			if (mrt->ReopenTimerInterval != 0)
				mrt->ReopenTimer = time(NULL) +
				    mrt->ReopenTimerInterval;
			mrt->state = MRT_STATE_RUNNING;
		}
		break;
	default:
		break;
	}
	return (1);
}

int
mrt_select(struct mrt_head *mc, struct pollfd *pfd, struct mrt **mrt,
    int start, int size, int *timeout)
{
	struct mrt	*m, *xm;
	time_t		 now;
	int		 t;

	now = time(NULL);
	for (m = LIST_FIRST(mc); m != NULL; m = xm) {
		xm = LIST_NEXT(m, list);
		if (m->state == MRT_STATE_TOREMOVE) {
			imsg_compose(m->ibuf, IMSG_MRT_END, 0,
					&m->conf, sizeof(m->conf));
			if (mrt_close(m) == 0) {
				m->state = MRT_STATE_REMOVE;
				m->ReopenTimer = 0;
			} else {
				msgbuf_clear(&m->msgbuf);
				LIST_REMOVE(m, list);
				free(m);
				continue;
			}
		}
		if (m->state == MRT_STATE_OPEN) {
			switch (m->conf.type) {
			case MRT_TABLE_DUMP:
				m->ibuf = mrt_imsgbuf[0];
				break;
			case MRT_ALL_IN:
			case MRT_ALL_OUT:
			case MRT_UPDATE_IN:
			case MRT_UPDATE_OUT:
				m->ibuf = mrt_imsgbuf[1];
				break;
			default:
				continue;
			}
			if (mrt_open(m) == 0) {
				mrt_abort(m);
				t = m->ReopenTimer - now;
				if (*timeout > t)
					*timeout = t;
				continue;
			}
			if (m->ReopenTimerInterval != 0)
				m->ReopenTimer = now + m->ReopenTimerInterval;
			m->state = MRT_STATE_RUNNING;
			imsg_compose(m->ibuf, IMSG_MRT_REQ, 0,
					&m->conf, sizeof(m->conf));
		}
		if (m->state == MRT_STATE_REOPEN) {
			if (mrt_close(m) == 0) {
				m->state = MRT_STATE_REOPEN;
				continue;
			}
			if (mrt_open(m) == 0) {
				mrt_abort(m);
				t = m->ReopenTimer - now;
				if (*timeout > t)
					*timeout = t;
				continue;
			}
			if (m->ReopenTimerInterval != 0)
				m->ReopenTimer = now + m->ReopenTimerInterval;
			m->state = MRT_STATE_RUNNING;
		}
		if (m->ReopenTimer != 0) {
			t = m->ReopenTimer - now;
			if (t <= 0 && (m->state == MRT_STATE_RUNNING ||
			    m->state == MRT_STATE_STOPPED)) {
				if (m->state == MRT_STATE_RUNNING) {
					/* reopen file */
					if (mrt_close(m) == 0) {
						m->state = MRT_STATE_REOPEN;
						continue;
					}
				}
				if (mrt_open(m) == 0) {
					mrt_abort(m);
					t = m->ReopenTimer - now;
					if (*timeout > t)
						*timeout = t;
					continue;
				}
				if (m->conf.type == MRT_TABLE_DUMP &&
				    m->state == MRT_STATE_STOPPED) {
					imsg_compose(mrt_imsgbuf[0],
					    IMSG_MRT_REQ, 0,
					    &m->conf, sizeof(m->conf));
				}

				m->state = MRT_STATE_RUNNING;
				if (m->ReopenTimerInterval != 0) {
					m->ReopenTimer = now +
					    m->ReopenTimerInterval;
					if (*timeout > m->ReopenTimerInterval)
						*timeout = t;
				}
			}
		}
		if (m->msgbuf.queued > 0) {
			if (m->msgbuf.sock == -1 ||
			    m->state == MRT_STATE_STOPPED) {
				log_warnx("mrt_select: orphaned buffer");
				mrt_abort(m);
				continue;
			}
			if (start < size) {
				pfd[start].fd = m->msgbuf.sock;
				pfd[start].events = POLLOUT;
				mrt[start++] = m;
			}
		}
	}
	return (start);
}

int
mrt_handler(struct mrt_head *mrt)
{
	struct mrt	*m;
	time_t		 now;

	now = time(NULL);
	LIST_FOREACH(m, mrt, list) {
		if (m->state == MRT_STATE_RUNNING)
			m->state = MRT_STATE_REOPEN;
		if (m->conf.type == MRT_TABLE_DUMP) {
			if (m->state == MRT_STATE_STOPPED) {
				if (mrt_open(m) == 0) {
					mrt_abort(m);
					break;
				}
				imsg_compose(mrt_imsgbuf[0], IMSG_MRT_REQ, 0,
				    &m->conf, sizeof(m->conf));
				m->state = MRT_STATE_RUNNING;
			}
		}
		if (m->ReopenTimerInterval != 0)
			m->ReopenTimer = now + m->ReopenTimerInterval;
	}
	return (0);
}

static u_int32_t	 max_id = 1;

static struct mrt *
getconf(struct mrt_head *c, struct mrt *m)
{
	struct mrt	*t;

	LIST_FOREACH(t, c, list) {
		if (t->conf.type != m->conf.type)
			continue;
		if (t->conf.type == MRT_TABLE_DUMP)
			return t;
		if (t->conf.peer_id == m->conf.peer_id &&
		    t->conf.group_id == m->conf.group_id)
			return t;
	}
	return (NULL);
}

int
mrt_mergeconfig(struct mrt_head *xconf, struct mrt_head *nconf)
{
	struct mrt	*m, *xm;

	LIST_FOREACH(m, nconf, list)
		if ((xm = getconf(xconf, m)) == NULL) {
			/* NEW */
			if ((xm = calloc(1, sizeof(struct mrt))) == NULL)
				fatal("mrt_mergeconfig");
			memcpy(xm, m, sizeof(struct mrt));
			msgbuf_init(&xm->msgbuf);
			xm->conf.id = max_id++;
			xm->state = MRT_STATE_OPEN;
			LIST_INSERT_HEAD(xconf, xm, list);
		} else {
			/* MERGE */
			if (strlcpy(xm->name, m->name, sizeof(xm->name)) >=
			    sizeof(xm->name))
				fatalx("mrt_mergeconfig: strlcpy");
			xm->ReopenTimerInterval = m->ReopenTimerInterval;
			xm->state = MRT_STATE_REOPEN;
		}

	LIST_FOREACH(xm, xconf, list)
		if (getconf(nconf, xm) == NULL)
			/* REMOVE */
			xm->state = MRT_STATE_TOREMOVE;

	/* free config */
	while ((m = LIST_FIRST(nconf)) != NULL) {
		LIST_REMOVE(m, list);
		free(m);
	}

	return (0);
}