/* $OpenBSD: mrt.c,v 1.33 2004/07/03 17:19:59 claudio Exp $ */ /* * Copyright (c) 2003, 2004 Claudio Jeker * * 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 "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 *, 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 *, time_t); #define DUMP_BYTE(x, b) \ do { \ u_char t = (b); \ if (buf_add((x), &t, sizeof(t)) == -1) { \ log_warnx("mrt_dump1: buf_add error"); \ return (-1); \ } \ } while (0) #define DUMP_SHORT(x, s) \ do { \ u_int16_t t; \ t = htons((s)); \ if (buf_add((x), &t, sizeof(t)) == -1) { \ log_warnx("mrt_dump2: buf_add error"); \ return (-1); \ } \ } while (0) #define DUMP_LONG(x, l) \ do { \ u_int32_t t; \ t = htonl((l)); \ if (buf_add((x), &t, sizeof(t)) == -1) { \ log_warnx("mrt_dump3: buf_add error"); \ return (-1); \ } \ } while (0) #define DUMP_NLONG(x, l) \ do { \ u_int32_t t = (l); \ if (buf_add((x), &t, sizeof(t)) == -1) { \ log_warnx("mrt_dump4: buf_add error"); \ return (-1); \ } \ } while (0) int mrt_dump_bgp_msg(struct mrt *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 = buf_open(len + MRT_HEADER_SIZE)) == NULL) { log_warnx("mrt_dump_bgp_msg: buf_open error"); return (-1); } if (mrt_dump_header(buf, MSG_PROTOCOL_BGP4MP, BGP4MP_MESSAGE, len) == -1) { log_warnx("mrt_dump_bgp_msg: buf_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 (buf_add(buf, pkg, pkglen) == -1) { log_warnx("mrt_dump_bgp_msg: buf_add error"); return (-1); } TAILQ_INSERT_TAIL(&mrt->bufs, buf, entry); mrt->queued++; return (len + MRT_HEADER_SIZE); } int mrt_dump_state(struct mrt *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 = buf_open(len + MRT_HEADER_SIZE)) == NULL) { log_warnx("mrt_dump_bgp_state: buf_open error"); return (-1); } if (mrt_dump_header(buf, MSG_PROTOCOL_BGP4MP, BGP4MP_STATE_CHANGE, len) == -1) { log_warnx("mrt_dump_bgp_state: buf_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); TAILQ_INSERT_TAIL(&mrt->bufs, buf, entry); mrt->queued++; 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, entry) 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, entry) { 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 *mrt, struct prefix *p, u_int16_t snum, struct peer_config *peer) { struct buf *buf; void *bptr; struct bgpd_addr addr; u_int16_t len, attr_len; attr_len = mrt_attr_length(&p->aspath->flags); len = MRT_DUMP_HEADER_SIZE + attr_len; pt_getaddr(p->prefix, &addr); if ((buf = buf_open(len + MRT_HEADER_SIZE)) == NULL) { log_warnx("mrt_dump_entry: buf_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, addr.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); } TAILQ_INSERT_TAIL(&mrt->bufs, buf, entry); mrt->queued++; 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 *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); } int mrt_write(struct mrt *mrt) { struct buf *b; int r = 0; while ((b = TAILQ_FIRST(&mrt->bufs)) && (r = buf_write(mrt->fd, b)) == 1) { TAILQ_REMOVE(&mrt->bufs, b, entry); mrt->queued--; buf_free(b); } if (r == -1) { log_warn("mrt dump write"); mrt_clean(mrt); return (-1); } return (0); } void mrt_clean(struct mrt *mrt) { struct buf *b; close(mrt->fd); while ((b = TAILQ_FIRST(&mrt->bufs))) { TAILQ_REMOVE(&mrt->bufs, b, entry); buf_free(b); } mrt->queued = 0; } static struct imsgbuf *mrt_imsgbuf[2]; void mrt_init(struct imsgbuf *rde, struct imsgbuf *se) { mrt_imsgbuf[0] = rde; mrt_imsgbuf[1] = se; } int mrt_open(struct mrt *mrt, time_t now) { enum imsg_type type; int i; mrt_close(mrt); if (strftime(MRT2MC(mrt)->file, sizeof(MRT2MC(mrt)->file), MRT2MC(mrt)->name, localtime(&now)) == 0) { log_warnx("mrt_open: strftime conversion failed"); mrt->fd = -1; return (-1); } mrt->fd = open(MRT2MC(mrt)->file, O_WRONLY|O_NONBLOCK|O_CREAT|O_TRUNC, 0644); if (mrt->fd == -1) { log_warn("mrt_open %s", MRT2MC(mrt)->file); return (1); } if (MRT2MC(mrt)->state == MRT_STATE_OPEN) type = IMSG_MRT_OPEN; else type = IMSG_MRT_REOPEN; i = mrt->type == MRT_TABLE_DUMP ? 0 : 1; if (imsg_compose_fdpass(mrt_imsgbuf[i], type, mrt->fd, mrt, sizeof(struct mrt)) == -1) log_warn("mrt_close"); return (1); } void mrt_close(struct mrt *mrt) { if (mrt == NULL) return; /* * this function is normaly called twice. First because of a imsg * form the child to inform the parent to close the fd. The second time * it is called after reconfigure when the mrt file gets removed. * In that case the parent must inform the child to close and remove * this mrt dump descriptor. */ if (MRT2MC(mrt)->state == MRT_STATE_REMOVE) if (imsg_compose( mrt_imsgbuf[mrt->type == MRT_TABLE_DUMP ? 0 : 1], IMSG_MRT_CLOSE, 0, mrt, sizeof(struct mrt)) == -1) log_warn("mrt_close"); if (mrt->fd == -1) return; close(mrt->fd); mrt->fd = -1; } int mrt_timeout(struct mrt_head *mrt) { struct mrt *m; time_t now; int timeout = MRT_MAX_TIMEOUT; now = time(NULL); LIST_FOREACH(m, mrt, entry) { if (MRT2MC(m)->state == MRT_STATE_RUNNING && MRT2MC(m)->ReopenTimerInterval != 0) { if (MRT2MC(m)->ReopenTimer <= now) { mrt_open(m, now); MRT2MC(m)->ReopenTimer = now + MRT2MC(m)->ReopenTimerInterval; } if (MRT2MC(m)->ReopenTimer - now < timeout) timeout = MRT2MC(m)->ReopenTimer - now; } } return (timeout > 0 ? timeout : 0); } void mrt_reconfigure(struct mrt_head *mrt) { struct mrt *m, *xm; time_t now; now = time(NULL); for (m = LIST_FIRST(mrt); m != NULL; m = xm) { xm = LIST_NEXT(m, entry); if (MRT2MC(m)->state == MRT_STATE_OPEN || MRT2MC(m)->state == MRT_STATE_REOPEN) { if (mrt_open(m, now) == -1) continue; if (MRT2MC(m)->ReopenTimerInterval != 0) MRT2MC(m)->ReopenTimer = now + MRT2MC(m)->ReopenTimerInterval; MRT2MC(m)->state = MRT_STATE_RUNNING; } if (MRT2MC(m)->state == MRT_STATE_REMOVE) { mrt_close(m); LIST_REMOVE(m, entry); free(m); continue; } } } void mrt_handler(struct mrt_head *mrt) { struct mrt *m; time_t now; now = time(NULL); LIST_FOREACH(m, mrt, entry) { if (MRT2MC(m)->state == MRT_STATE_RUNNING && (MRT2MC(m)->ReopenTimerInterval != 0 || m->type == MRT_TABLE_DUMP)) { if (mrt_open(m, now) == -1) continue; MRT2MC(m)->ReopenTimer = now + MRT2MC(m)->ReopenTimerInterval; } } } struct mrt * mrt_get(struct mrt_head *c, struct mrt *m) { struct mrt *t; LIST_FOREACH(t, c, entry) { if (t->type != m->type) continue; if (t->type == MRT_TABLE_DUMP) return (t); if (t->peer_id == m->peer_id && t->group_id == m->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, entry) { if ((xm = mrt_get(xconf, m)) == NULL) { /* NEW */ if ((xm = calloc(1, sizeof(struct mrt_config))) == NULL) fatal("mrt_mergeconfig"); memcpy(xm, m, sizeof(struct mrt_config)); xm->fd = -1; MRT2MC(xm)->state = MRT_STATE_OPEN; LIST_INSERT_HEAD(xconf, xm, entry); } else { /* MERGE */ if (strlcpy(MRT2MC(xm)->name, MRT2MC(xm)->name, sizeof(MRT2MC(xm)->name)) >= sizeof(MRT2MC(xm)->name)) fatalx("mrt_mergeconfig: strlcpy"); MRT2MC(xm)->ReopenTimerInterval = MRT2MC(m)->ReopenTimerInterval; MRT2MC(xm)->state = MRT_STATE_REOPEN; } } LIST_FOREACH(xm, xconf, entry) if (mrt_get(nconf, xm) == NULL) /* REMOVE */ MRT2MC(xm)->state = MRT_STATE_REMOVE; /* free config */ while ((m = LIST_FIRST(nconf)) != NULL) { LIST_REMOVE(m, entry); free(m); } return (0); }