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

File: [local] / src / usr.sbin / ldpd / l2vpn.c (download)

Revision 1.24, Sat Mar 4 00:21:48 2017 UTC (7 years, 3 months ago) by renato
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, OPENBSD_6_7_BASE, OPENBSD_6_7, OPENBSD_6_6_BASE, OPENBSD_6_6, OPENBSD_6_5_BASE, OPENBSD_6_5, OPENBSD_6_4_BASE, OPENBSD_6_4, OPENBSD_6_3_BASE, OPENBSD_6_3, OPENBSD_6_2_BASE, OPENBSD_6_2, OPENBSD_6_1_BASE, OPENBSD_6_1, HEAD
Changes since 1.23: +29 -2 lines

Send VPLS MAC withdrawals.

RFC 4762 says that MAC address withdrawal messages can be used to
improve convergence time in VPLS networks. This patch makes ldpd send
MAC withdrawals whenever a non-pseudowire interface pertaining to a
VPLS goes down. The processing of received MAC withdrawals will be
implemented later.

/*	$OpenBSD: l2vpn.c,v 1.24 2017/03/04 00:21:48 renato Exp $ */

/*
 * Copyright (c) 2015 Renato Westphal <renato@openbsd.org>
 * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
 * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
 * Copyright (c) 2004, 2005, 2008 Esben Norby <norby@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 <stdlib.h>
#include <string.h>
#include <limits.h>

#include "ldpd.h"
#include "ldpe.h"
#include "lde.h"
#include "log.h"

static void	 l2vpn_pw_fec(struct l2vpn_pw *, struct fec *);

struct l2vpn *
l2vpn_new(const char *name)
{
	struct l2vpn	*l2vpn;

	if ((l2vpn = calloc(1, sizeof(*l2vpn))) == NULL)
		fatal("l2vpn_new: calloc");

	strlcpy(l2vpn->name, name, sizeof(l2vpn->name));

	/* set default values */
	l2vpn->mtu = DEFAULT_L2VPN_MTU;
	l2vpn->pw_type = DEFAULT_PW_TYPE;

	LIST_INIT(&l2vpn->if_list);
	LIST_INIT(&l2vpn->pw_list);

	return (l2vpn);
}

struct l2vpn *
l2vpn_find(struct ldpd_conf *xconf, const char *name)
{
	struct l2vpn	*l2vpn;

	LIST_FOREACH(l2vpn, &xconf->l2vpn_list, entry)
		if (strcmp(l2vpn->name, name) == 0)
			return (l2vpn);

	return (NULL);
}

void
l2vpn_del(struct l2vpn *l2vpn)
{
	struct l2vpn_if		*lif;
	struct l2vpn_pw		*pw;

	while ((lif = LIST_FIRST(&l2vpn->if_list)) != NULL) {
		LIST_REMOVE(lif, entry);
		free(lif);
	}
	while ((pw = LIST_FIRST(&l2vpn->pw_list)) != NULL) {
		LIST_REMOVE(pw, entry);
		free(pw);
	}

	free(l2vpn);
}

void
l2vpn_init(struct l2vpn *l2vpn)
{
	struct l2vpn_pw	*pw;

	LIST_FOREACH(pw, &l2vpn->pw_list, entry)
		l2vpn_pw_init(pw);
}

void
l2vpn_exit(struct l2vpn *l2vpn)
{
	struct l2vpn_pw		*pw;

	LIST_FOREACH(pw, &l2vpn->pw_list, entry)
		l2vpn_pw_exit(pw);
}

struct l2vpn_if *
l2vpn_if_new(struct l2vpn *l2vpn, struct kif *kif)
{
	struct l2vpn_if	*lif;

	if ((lif = calloc(1, sizeof(*lif))) == NULL)
		fatal("l2vpn_if_new: calloc");

	lif->l2vpn = l2vpn;
	strlcpy(lif->ifname, kif->ifname, sizeof(lif->ifname));
	lif->ifindex = kif->ifindex;
	lif->flags = kif->flags;
	lif->linkstate = kif->link_state;

	return (lif);
}

struct l2vpn_if *
l2vpn_if_find(struct l2vpn *l2vpn, unsigned int ifindex)
{
	struct l2vpn_if	*lif;

	LIST_FOREACH(lif, &l2vpn->if_list, entry)
		if (lif->ifindex == ifindex)
			return (lif);

	return (NULL);
}

void
l2vpn_if_update(struct l2vpn_if *lif)
{
	struct l2vpn	*l2vpn = lif->l2vpn;
	struct l2vpn_pw	*pw;
	struct map	 fec;
	struct nbr	*nbr;

	if ((lif->flags & IFF_UP) && LINK_STATE_IS_UP(lif->linkstate))
		return;

	LIST_FOREACH(pw, &l2vpn->pw_list, entry) {
		nbr = nbr_find_ldpid(pw->lsr_id.s_addr);
		if (nbr == NULL)
			continue;

		memset(&fec, 0, sizeof(fec));
		fec.type = MAP_TYPE_PWID;
		fec.fec.pwid.type = l2vpn->pw_type;
		fec.fec.pwid.group_id = 0;
		fec.flags |= F_MAP_PW_ID;
		fec.fec.pwid.pwid = pw->pwid;

		send_mac_withdrawal(nbr, &fec, lif->mac);
	}
}

struct l2vpn_pw *
l2vpn_pw_new(struct l2vpn *l2vpn, struct kif *kif)
{
	struct l2vpn_pw	*pw;

	if ((pw = calloc(1, sizeof(*pw))) == NULL)
		fatal("l2vpn_pw_new: calloc");

	pw->l2vpn = l2vpn;
	strlcpy(pw->ifname, kif->ifname, sizeof(pw->ifname));
	pw->ifindex = kif->ifindex;

	return (pw);
}

struct l2vpn_pw *
l2vpn_pw_find(struct l2vpn *l2vpn, unsigned int ifindex)
{
	struct l2vpn_pw	*pw;

	LIST_FOREACH(pw, &l2vpn->pw_list, entry)
		if (pw->ifindex == ifindex)
			return (pw);

	return (NULL);
}

void
l2vpn_pw_init(struct l2vpn_pw *pw)
{
	struct fec	 fec;

	l2vpn_pw_reset(pw);

	l2vpn_pw_fec(pw, &fec);
	lde_kernel_insert(&fec, AF_INET, (union ldpd_addr*)&pw->lsr_id, 0,
	    0, (void *)pw);
}

void
l2vpn_pw_exit(struct l2vpn_pw *pw)
{
	struct fec	 fec;

	l2vpn_pw_fec(pw, &fec);
	lde_kernel_remove(&fec, AF_INET, (union ldpd_addr*)&pw->lsr_id, 0);
}

static void
l2vpn_pw_fec(struct l2vpn_pw *pw, struct fec *fec)
{
	memset(fec, 0, sizeof(*fec));
	fec->type = FEC_TYPE_PWID;
	fec->u.pwid.type = pw->l2vpn->pw_type;
	fec->u.pwid.pwid = pw->pwid;
	fec->u.pwid.lsr_id = pw->lsr_id;
}

void
l2vpn_pw_reset(struct l2vpn_pw *pw)
{
	pw->remote_group = 0;
	pw->remote_mtu = 0;
	pw->remote_status = 0;

	if (pw->flags & F_PW_CWORD_CONF)
		pw->flags |= F_PW_CWORD;
	else
		pw->flags &= ~F_PW_CWORD;

	if (pw->flags & F_PW_STATUSTLV_CONF)
		pw->flags |= F_PW_STATUSTLV;
	else
		pw->flags &= ~F_PW_STATUSTLV;
}

int
l2vpn_pw_ok(struct l2vpn_pw *pw, struct fec_nh *fnh)
{
	struct fec		 fec;
	struct fec_node		*fn;

	/* check for a remote label */
	if (fnh->remote_label == NO_LABEL)
		return (0);

	/* MTUs must match */
	if (pw->l2vpn->mtu != pw->remote_mtu)
		return (0);

	/* check pw status if applicable */
	if ((pw->flags & F_PW_STATUSTLV) &&
	    pw->remote_status != PW_FORWARDING)
		return (0);

	/* check for a working lsp to the nexthop */
	memset(&fec, 0, sizeof(fec));
	switch (pw->af) {
	case AF_INET:
		fec.type = FEC_TYPE_IPV4;
		fec.u.ipv4.prefix = pw->addr.v4;
		fec.u.ipv4.prefixlen = 32;
		break;
	case AF_INET6:
		fec.type = FEC_TYPE_IPV6;
		fec.u.ipv6.prefix = pw->addr.v6;
		fec.u.ipv6.prefixlen = 128;
		break;
	default:
		fatalx("l2vpn_pw_ok: unknown af");
	}

	fn = (struct fec_node *)fec_find(&ft, &fec);
	if (fn == NULL || fn->local_label == NO_LABEL)
		return (0);
	/*
	 * Need to ensure that there's a label binding for all nexthops.
	 * Otherwise, ECMP for this route could render the pseudowire unusable.
	 */
	LIST_FOREACH(fnh, &fn->nexthops, entry)
		if (fnh->remote_label == NO_LABEL)
			return (0);

	return (1);
}

int
l2vpn_pw_negotiate(struct lde_nbr *ln, struct fec_node *fn, struct map *map)
{
	struct l2vpn_pw		*pw;
	struct status_tlv	 st;

	/* NOTE: thanks martini & friends for all this mess */

	pw = (struct l2vpn_pw *) fn->data;
	if (pw == NULL)
		/*
		 * pseudowire not configured, return and record
		 * the mapping later
		 */
		return (0);

	/* RFC4447 - Section 6.2: control word negotiation */
	if (fec_find(&ln->sent_map, &fn->fec)) {
		if ((map->flags & F_MAP_PW_CWORD) &&
		    !(pw->flags & F_PW_CWORD_CONF)) {
			/* ignore the received label mapping */
			return (1);
		} else if (!(map->flags & F_MAP_PW_CWORD) &&
		    (pw->flags & F_PW_CWORD_CONF)) {
			/* append a "Wrong C-bit" status code */
			st.status_code = S_WRONG_CBIT;
			st.msg_id = map->msg_id;
			st.msg_type = htons(MSG_TYPE_LABELMAPPING);
			lde_send_labelwithdraw(ln, fn, NULL, &st);

			pw->flags &= ~F_PW_CWORD;
			lde_send_labelmapping(ln, fn, 1);
		}
	} else if (map->flags & F_MAP_PW_CWORD) {
		if (pw->flags & F_PW_CWORD_CONF)
			pw->flags |= F_PW_CWORD;
		else
			/* act as if no label mapping had been received */
			return (1);
	} else
		pw->flags &= ~F_PW_CWORD;

	/* RFC4447 - Section 5.4.3: pseudowire status negotiation */
	if (fec_find(&ln->recv_map, &fn->fec) == NULL &&
	    !(map->flags & F_MAP_PW_STATUS))
		pw->flags &= ~F_PW_STATUSTLV;

	return (0);
}

void
l2vpn_send_pw_status(struct lde_nbr *ln, uint32_t status, struct fec *fec)
{
	struct notify_msg	 nm;

	memset(&nm, 0, sizeof(nm));
	nm.status_code = S_PW_STATUS;
	nm.pw_status = status;
	nm.flags |= F_NOTIF_PW_STATUS;
	lde_fec2map(fec, &nm.fec);
	nm.flags |= F_NOTIF_FEC;

	lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm,
	    sizeof(nm));
}

void
l2vpn_send_pw_status_wcard(struct lde_nbr *ln, uint32_t status,
    uint16_t pw_type, uint32_t group_id)
{
	struct notify_msg	 nm;

	memset(&nm, 0, sizeof(nm));
	nm.status_code = S_PW_STATUS;
	nm.pw_status = status;
	nm.flags |= F_NOTIF_PW_STATUS;
	nm.fec.type = MAP_TYPE_PWID;
	nm.fec.fec.pwid.type = pw_type;
	nm.fec.fec.pwid.group_id = group_id;
	nm.flags |= F_NOTIF_FEC;

	lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm,
	    sizeof(nm));
}

void
l2vpn_recv_pw_status(struct lde_nbr *ln, struct notify_msg *nm)
{
	struct fec		 fec;
	struct fec_node		*fn;
	struct fec_nh		*fnh;
	struct l2vpn_pw		*pw;

	if (nm->fec.type == MAP_TYPE_TYPED_WCARD ||
	    !(nm->fec.flags & F_MAP_PW_ID)) {
		l2vpn_recv_pw_status_wcard(ln, nm);
		return;
	}

	lde_map2fec(&nm->fec, ln->id, &fec);
	fn = (struct fec_node *)fec_find(&ft, &fec);
	if (fn == NULL)
		/* unknown fec */
		return;

	pw = (struct l2vpn_pw *) fn->data;
	if (pw == NULL)
		return;

	fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)&ln->id, 0);
	if (fnh == NULL)
		return;

	/* remote status didn't change */
	if (pw->remote_status == nm->pw_status)
		return;
	pw->remote_status = nm->pw_status;

	if (l2vpn_pw_ok(pw, fnh))
		lde_send_change_klabel(fn, fnh);
	else
		lde_send_delete_klabel(fn, fnh);
}

/* RFC4447 PWid group wildcard */
void
l2vpn_recv_pw_status_wcard(struct lde_nbr *ln, struct notify_msg *nm)
{
	struct fec		*f;
	struct fec_node		*fn;
	struct fec_nh		*fnh;
	struct l2vpn_pw		*pw;
	struct map		*wcard = &nm->fec;

	RB_FOREACH(f, fec_tree, &ft) {
		fn = (struct fec_node *)f;
		if (fn->fec.type != FEC_TYPE_PWID)
			continue;

		pw = (struct l2vpn_pw *) fn->data;
		if (pw == NULL)
			continue;

		switch (wcard->type) {
		case MAP_TYPE_TYPED_WCARD:
			if (wcard->fec.twcard.u.pw_type != PW_TYPE_WILDCARD &&
			    wcard->fec.twcard.u.pw_type != fn->fec.u.pwid.type)
				continue;
			break;
		case MAP_TYPE_PWID:
			if (wcard->fec.pwid.type != fn->fec.u.pwid.type)
				continue;
			if (wcard->fec.pwid.group_id != pw->remote_group)
				continue;
			break;
		}

		fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)&ln->id, 0);
		if (fnh == NULL)
			continue;

		/* remote status didn't change */
		if (pw->remote_status == nm->pw_status)
			continue;
		pw->remote_status = nm->pw_status;

		if (l2vpn_pw_ok(pw, fnh))
			lde_send_change_klabel(fn, fnh);
		else
			lde_send_delete_klabel(fn, fnh);
	}
}

void
l2vpn_sync_pws(int af, union ldpd_addr *addr)
{
	struct l2vpn		*l2vpn;
	struct l2vpn_pw		*pw;
	struct fec		 fec;
	struct fec_node		*fn;
	struct fec_nh		*fnh;

	LIST_FOREACH(l2vpn, &ldeconf->l2vpn_list, entry) {
		LIST_FOREACH(pw, &l2vpn->pw_list, entry) {
			if (af != pw->af || ldp_addrcmp(af, &pw->addr, addr))
				continue;

			l2vpn_pw_fec(pw, &fec);
			fn = (struct fec_node *)fec_find(&ft, &fec);
			if (fn == NULL)
				continue;
			fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)
			    &pw->lsr_id, 0);
			if (fnh == NULL)
				continue;

			if (l2vpn_pw_ok(pw, fnh))
				lde_send_change_klabel(fn, fnh);
			else
				lde_send_delete_klabel(fn, fnh);
		}
	}
}

void
l2vpn_pw_ctl(pid_t pid)
{
	struct l2vpn		*l2vpn;
	struct l2vpn_pw		*pw;
	static struct ctl_pw	 pwctl;

	LIST_FOREACH(l2vpn, &ldeconf->l2vpn_list, entry)
		LIST_FOREACH(pw, &l2vpn->pw_list, entry) {
			memset(&pwctl, 0, sizeof(pwctl));
			strlcpy(pwctl.ifname, pw->ifname,
			    sizeof(pwctl.ifname));
			pwctl.pwid = pw->pwid;
			pwctl.lsr_id = pw->lsr_id;
			pwctl.status = pw->flags & F_PW_STATUS_UP;

			lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_PW, 0,
			    pid, &pwctl, sizeof(pwctl));
		}
}

void
l2vpn_binding_ctl(pid_t pid)
{
	struct fec		*f;
	struct fec_node		*fn;
	struct lde_map		*me;
	struct l2vpn_pw		*pw;
	static struct ctl_pw	 pwctl;

	RB_FOREACH(f, fec_tree, &ft) {
		if (f->type != FEC_TYPE_PWID)
			continue;

		fn = (struct fec_node *)f;
		if (fn->local_label == NO_LABEL &&
		    LIST_EMPTY(&fn->downstream))
			continue;

		memset(&pwctl, 0, sizeof(pwctl));
		pwctl.type = f->u.pwid.type;
		pwctl.pwid = f->u.pwid.pwid;
		pwctl.lsr_id = f->u.pwid.lsr_id;

		pw = (struct l2vpn_pw *) fn->data;
		if (pw) {
			pwctl.local_label = fn->local_label;
			pwctl.local_gid = 0;
			pwctl.local_ifmtu = pw->l2vpn->mtu;
		} else
			pwctl.local_label = NO_LABEL;

		LIST_FOREACH(me, &fn->downstream, entry)
			if (f->u.pwid.lsr_id.s_addr == me->nexthop->id.s_addr)
				break;

		if (me) {
			pwctl.remote_label = me->map.label;
			pwctl.remote_gid = me->map.fec.pwid.group_id;
			if (me->map.flags & F_MAP_PW_IFMTU)
				pwctl.remote_ifmtu = me->map.fec.pwid.ifmtu;

			lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_BINDING,
			    0, pid, &pwctl, sizeof(pwctl));
		} else if (pw) {
			pwctl.remote_label = NO_LABEL;

			lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_BINDING,
			    0, pid, &pwctl, sizeof(pwctl));
		}
	}
}

/* ldpe */

void
ldpe_l2vpn_init(struct l2vpn *l2vpn)
{
	struct l2vpn_pw		*pw;

	LIST_FOREACH(pw, &l2vpn->pw_list, entry)
		ldpe_l2vpn_pw_init(pw);
}

void
ldpe_l2vpn_exit(struct l2vpn *l2vpn)
{
	struct l2vpn_pw		*pw;

	LIST_FOREACH(pw, &l2vpn->pw_list, entry)
		ldpe_l2vpn_pw_exit(pw);
}

void
ldpe_l2vpn_pw_init(struct l2vpn_pw *pw)
{
	struct tnbr		*tnbr;

	tnbr = tnbr_find(leconf, pw->af, &pw->addr);
	if (tnbr == NULL) {
		tnbr = tnbr_new(leconf, pw->af, &pw->addr);
		tnbr_update(tnbr);
		LIST_INSERT_HEAD(&leconf->tnbr_list, tnbr, entry);
	}

	tnbr->pw_count++;
}

void
ldpe_l2vpn_pw_exit(struct l2vpn_pw *pw)
{
	struct tnbr		*tnbr;

	tnbr = tnbr_find(leconf, pw->af, &pw->addr);
	if (tnbr) {
		tnbr->pw_count--;
		tnbr_check(tnbr);
	}
}