root/usr.sbin/ldpd/interface.c
/*      $OpenBSD: interface.c,v 1.52 2023/03/08 04:43:13 guenther Exp $ */

/*
 * Copyright (c) 2013, 2016 Renato Westphal <renato@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 <sys/time.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>

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

static struct if_addr   *if_addr_new(struct kaddr *);
static struct if_addr   *if_addr_lookup(struct if_addr_head *, struct kaddr *);
static int               if_start(struct iface *, int);
static int               if_reset(struct iface *, int);
static void              if_update_af(struct iface_af *, int);
static void              if_hello_timer(int, short, void *);
static void              if_start_hello_timer(struct iface_af *);
static void              if_stop_hello_timer(struct iface_af *);
static int               if_join_ipv4_group(struct iface *, struct in_addr *);
static int               if_leave_ipv4_group(struct iface *, struct in_addr *);
static int               if_join_ipv6_group(struct iface *, struct in6_addr *);
static int               if_leave_ipv6_group(struct iface *, struct in6_addr *);

struct iface *
if_new(struct kif *kif)
{
        struct iface            *iface;

        if ((iface = calloc(1, sizeof(*iface))) == NULL)
                fatal("if_new: calloc");

        strlcpy(iface->name, kif->ifname, sizeof(iface->name));

        /* get type */
        if (kif->flags & IFF_POINTOPOINT)
                iface->type = IF_TYPE_POINTOPOINT;
        if (kif->flags & IFF_BROADCAST &&
            kif->flags & IFF_MULTICAST)
                iface->type = IF_TYPE_BROADCAST;

        /* get index and flags */
        LIST_INIT(&iface->addr_list);
        iface->ifindex = kif->ifindex;
        iface->rdomain = kif->rdomain;
        iface->flags = kif->flags;
        iface->linkstate = kif->link_state;
        iface->if_type = kif->if_type;

        /* ipv4 */
        iface->ipv4.af = AF_INET;
        iface->ipv4.iface = iface;
        iface->ipv4.enabled = 0;
        iface->ipv4.state = IF_STA_DOWN;
        LIST_INIT(&iface->ipv4.adj_list);

        /* ipv6 */
        iface->ipv6.af = AF_INET6;
        iface->ipv6.iface = iface;
        iface->ipv6.enabled = 0;
        iface->ipv6.state = IF_STA_DOWN;
        LIST_INIT(&iface->ipv6.adj_list);

        return (iface);
}

void
if_exit(struct iface *iface)
{
        struct if_addr          *if_addr;

        log_debug("%s: interface %s", __func__, iface->name);

        if (iface->ipv4.state == IF_STA_ACTIVE)
                if_reset(iface, AF_INET);
        if (iface->ipv6.state == IF_STA_ACTIVE)
                if_reset(iface, AF_INET6);

        while ((if_addr = LIST_FIRST(&iface->addr_list)) != NULL) {
                LIST_REMOVE(if_addr, entry);
                free(if_addr);
        }
}

struct iface *
if_lookup(struct ldpd_conf *xconf, unsigned short ifindex)
{
        struct iface *iface;

        LIST_FOREACH(iface, &xconf->iface_list, entry)
                if (iface->ifindex == ifindex)
                        return (iface);

        return (NULL);
}

struct iface_af *
iface_af_get(struct iface *iface, int af)
{
        switch (af) {
        case AF_INET:
                return (&iface->ipv4);
        case AF_INET6:
                return (&iface->ipv6);
        default:
                fatalx("iface_af_get: unknown af");
        }
}

static struct if_addr *
if_addr_new(struct kaddr *ka)
{
        struct if_addr  *if_addr;

        if ((if_addr = calloc(1, sizeof(*if_addr))) == NULL)
                fatal(__func__);

        if_addr->af = ka->af;
        if_addr->addr = ka->addr;
        if_addr->prefixlen = ka->prefixlen;
        if_addr->dstbrd = ka->dstbrd;

        return (if_addr);
}

static struct if_addr *
if_addr_lookup(struct if_addr_head *addr_list, struct kaddr *ka)
{
        struct if_addr  *if_addr;
        int              af = ka->af;

        LIST_FOREACH(if_addr, addr_list, entry)
                if (!ldp_addrcmp(af, &if_addr->addr, &ka->addr) &&
                    if_addr->prefixlen == ka->prefixlen &&
                    !ldp_addrcmp(af, &if_addr->dstbrd, &ka->dstbrd))
                        return (if_addr);

        return (NULL);
}

void
if_addr_add(struct kaddr *ka)
{
        struct iface            *iface;
        struct if_addr          *if_addr;
        struct nbr              *nbr;

        if (if_addr_lookup(&global.addr_list, ka) == NULL) {
                if_addr = if_addr_new(ka);

                LIST_INSERT_HEAD(&global.addr_list, if_addr, entry);
                RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) {
                        if (nbr->state != NBR_STA_OPER)
                                continue;
                        if (if_addr->af == AF_INET && !nbr->v4_enabled)
                                continue;
                        if (if_addr->af == AF_INET6 && !nbr->v6_enabled)
                                continue;

                        send_address_single(nbr, if_addr, 0);
                }
        }

        iface = if_lookup(leconf, ka->ifindex);
        if (iface) {
                if (ka->af == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&ka->addr.v6))
                        iface->linklocal = ka->addr.v6;

                if (if_addr_lookup(&iface->addr_list, ka) == NULL) {
                        if_addr = if_addr_new(ka);
                        LIST_INSERT_HEAD(&iface->addr_list, if_addr, entry);
                        if_update(iface, if_addr->af);
                }
        }
}

void
if_addr_del(struct kaddr *ka)
{
        struct iface            *iface;
        struct if_addr          *if_addr;
        struct nbr              *nbr;

        iface = if_lookup(leconf, ka->ifindex);
        if (iface) {
                if (ka->af == AF_INET6 &&
                    IN6_ARE_ADDR_EQUAL(&iface->linklocal, &ka->addr.v6))
                        memset(&iface->linklocal, 0, sizeof(iface->linklocal));

                if_addr = if_addr_lookup(&iface->addr_list, ka);
                if (if_addr) {
                        LIST_REMOVE(if_addr, entry);
                        if_update(iface, if_addr->af);
                        free(if_addr);
                }
        }

        if_addr = if_addr_lookup(&global.addr_list, ka);
        if (if_addr) {
                RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) {
                        if (nbr->state != NBR_STA_OPER)
                                continue;
                        if (if_addr->af == AF_INET && !nbr->v4_enabled)
                                continue;
                        if (if_addr->af == AF_INET6 && !nbr->v6_enabled)
                                continue;
                        send_address_single(nbr, if_addr, 1);
                }
                LIST_REMOVE(if_addr, entry);
                free(if_addr);
        }
}

static int
if_start(struct iface *iface, int af)
{
        struct iface_af         *ia;
        struct timeval           now;

        log_debug("%s: %s address-family %s", __func__, iface->name,
            af_name(af));

        ia = iface_af_get(iface, af);

        gettimeofday(&now, NULL);
        ia->uptime = now.tv_sec;

        switch (af) {
        case AF_INET:
                if (if_join_ipv4_group(iface, &global.mcast_addr_v4))
                        return (-1);
                break;
        case AF_INET6:
                if (if_join_ipv6_group(iface, &global.mcast_addr_v6))
                        return (-1);
                break;
        default:
                fatalx("if_start: unknown af");
        }

        send_hello(HELLO_LINK, ia, NULL);

        evtimer_set(&ia->hello_timer, if_hello_timer, ia);
        if_start_hello_timer(ia);
        return (0);
}

static int
if_reset(struct iface *iface, int af)
{
        struct iface_af         *ia;
        struct adj              *adj;

        log_debug("%s: %s address-family %s", __func__, iface->name,
            af_name(af));

        ia = iface_af_get(iface, af);
        if_stop_hello_timer(ia);

        while ((adj = LIST_FIRST(&ia->adj_list)) != NULL)
                adj_del(adj, S_SHUTDOWN);

        /* try to cleanup */
        switch (af) {
        case AF_INET:
                if (global.ipv4.ldp_disc_socket != -1)
                        if_leave_ipv4_group(iface, &global.mcast_addr_v4);
                break;
        case AF_INET6:
                if (global.ipv6.ldp_disc_socket != -1)
                        if_leave_ipv6_group(iface, &global.mcast_addr_v6);
                break;
        default:
                fatalx("if_start: unknown af");
        }

        return (0);
}

static void
if_update_af(struct iface_af *ia, int link_ok)
{
        int                      addr_ok = 0, socket_ok, rtr_id_ok;
        struct if_addr          *if_addr;

        switch (ia->af) {
        case AF_INET:
                /*
                 * NOTE: for LDPv4, each interface should have at least one
                 * valid IP address otherwise they can not be enabled.
                 */
                LIST_FOREACH(if_addr, &ia->iface->addr_list, entry) {
                        if (if_addr->af == AF_INET) {
                                addr_ok = 1;
                                break;
                        }
                }
                break;
        case AF_INET6:
                /* for IPv6 the link-local address is enough. */
                if (IN6_IS_ADDR_LINKLOCAL(&ia->iface->linklocal))
                        addr_ok = 1;
                break;
        default:
                fatalx("if_update_af: unknown af");
        }

        if ((ldp_af_global_get(&global, ia->af))->ldp_disc_socket != -1)
                socket_ok = 1;
        else
                socket_ok = 0;

        if (leconf->rtr_id.s_addr != INADDR_ANY)
                rtr_id_ok = 1;
        else
                rtr_id_ok = 0;

        if (ia->state == IF_STA_DOWN) {
                if (!ia->enabled || !link_ok || !addr_ok || !socket_ok ||
                    !rtr_id_ok)
                        return;

                ia->state = IF_STA_ACTIVE;
                if_start(ia->iface, ia->af);
        } else if (ia->state == IF_STA_ACTIVE) {
                if (ia->enabled && link_ok && addr_ok && socket_ok && rtr_id_ok)
                        return;

                ia->state = IF_STA_DOWN;
                if_reset(ia->iface, ia->af);
        }
}

void
if_update(struct iface *iface, int af)
{
        int                      link_ok;

        link_ok = (iface->flags & IFF_UP) &&
            LINK_STATE_IS_UP(iface->linkstate);

        if (af == AF_INET || af == AF_UNSPEC)
                if_update_af(&iface->ipv4, link_ok);
        if (af == AF_INET6 || af == AF_UNSPEC)
                if_update_af(&iface->ipv6, link_ok);
}

void
if_update_all(int af)
{
        struct iface            *iface;

        LIST_FOREACH(iface, &leconf->iface_list, entry)
                if_update(iface, af);
}

/* timers */
static void
if_hello_timer(int fd, short event, void *arg)
{
        struct iface_af         *ia = arg;

        send_hello(HELLO_LINK, ia, NULL);
        if_start_hello_timer(ia);
}

static void
if_start_hello_timer(struct iface_af *ia)
{
        struct timeval           tv;

        timerclear(&tv);
        tv.tv_sec = ia->hello_interval;
        if (evtimer_add(&ia->hello_timer, &tv) == -1)
                fatal(__func__);
}

static void
if_stop_hello_timer(struct iface_af *ia)
{
        if (evtimer_pending(&ia->hello_timer, NULL) &&
            evtimer_del(&ia->hello_timer) == -1)
                fatal(__func__);
}

struct ctl_iface *
if_to_ctl(struct iface_af *ia)
{
        static struct ctl_iface  ictl;
        struct timeval           now;
        struct adj              *adj;

        ictl.af = ia->af;
        memcpy(ictl.name, ia->iface->name, sizeof(ictl.name));
        ictl.ifindex = ia->iface->ifindex;
        ictl.state = ia->state;
        ictl.flags = ia->iface->flags;
        ictl.linkstate = ia->iface->linkstate;
        ictl.type = ia->iface->type;
        ictl.if_type = ia->iface->if_type;
        ictl.hello_holdtime = ia->hello_holdtime;
        ictl.hello_interval = ia->hello_interval;

        gettimeofday(&now, NULL);
        if (ia->state != IF_STA_DOWN &&
            ia->uptime != 0) {
                ictl.uptime = now.tv_sec - ia->uptime;
        } else
                ictl.uptime = 0;

        ictl.adj_cnt = 0;
        LIST_FOREACH(adj, &ia->adj_list, ia_entry)
                ictl.adj_cnt++;

        return (&ictl);
}

/* multicast membership sockopts */
in_addr_t
if_get_ipv4_addr(struct iface *iface)
{
        struct if_addr          *if_addr;

        LIST_FOREACH(if_addr, &iface->addr_list, entry)
                if (if_addr->af == AF_INET)
                        return (if_addr->addr.v4.s_addr);

        return (INADDR_ANY);
}

static int
if_join_ipv4_group(struct iface *iface, struct in_addr *addr)
{
        struct ip_mreq           mreq;

        log_debug("%s: interface %s addr %s", __func__, iface->name,
            inet_ntoa(*addr));

        mreq.imr_multiaddr = *addr;
        mreq.imr_interface.s_addr = if_get_ipv4_addr(iface);

        if (setsockopt(global.ipv4.ldp_disc_socket, IPPROTO_IP,
            IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq)) == -1) {
                log_warn("%s: error IP_ADD_MEMBERSHIP, interface %s address %s",
                     __func__, iface->name, inet_ntoa(*addr));
                return (-1);
        }
        return (0);
}

static int
if_leave_ipv4_group(struct iface *iface, struct in_addr *addr)
{
        struct ip_mreq           mreq;

        log_debug("%s: interface %s addr %s", __func__, iface->name,
            inet_ntoa(*addr));

        mreq.imr_multiaddr = *addr;
        mreq.imr_interface.s_addr = if_get_ipv4_addr(iface);

        if (setsockopt(global.ipv4.ldp_disc_socket, IPPROTO_IP,
            IP_DROP_MEMBERSHIP, (void *)&mreq, sizeof(mreq)) == -1) {
                log_warn("%s: error IP_DROP_MEMBERSHIP, interface %s "
                    "address %s", __func__, iface->name, inet_ntoa(*addr));
                return (-1);
        }

        return (0);
}

static int
if_join_ipv6_group(struct iface *iface, struct in6_addr *addr)
{
        struct ipv6_mreq         mreq;

        log_debug("%s: interface %s addr %s", __func__, iface->name,
            log_in6addr(addr));

        mreq.ipv6mr_multiaddr = *addr;
        mreq.ipv6mr_interface = iface->ifindex;

        if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6,
            IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) {
                log_warn("%s: error IPV6_JOIN_GROUP, interface %s address %s",
                    __func__, iface->name, log_in6addr(addr));
                return (-1);
        }

        return (0);
}

static int
if_leave_ipv6_group(struct iface *iface, struct in6_addr *addr)
{
        struct ipv6_mreq         mreq;

        log_debug("%s: interface %s addr %s", __func__, iface->name,
            log_in6addr(addr));

        mreq.ipv6mr_multiaddr = *addr;
        mreq.ipv6mr_interface = iface->ifindex;

        if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6,
            IPV6_LEAVE_GROUP, (void *)&mreq, sizeof(mreq)) == -1) {
                log_warn("%s: error IPV6_LEAVE_GROUP, interface %s address %s",
                    __func__, iface->name, log_in6addr(addr));
                return (-1);
        }

        return (0);
}