root/usr.sbin/eigrpd/packet.c
/*      $OpenBSD: packet.c,v 1.25 2026/05/17 06:07:51 jsg Exp $ */

/*
 * Copyright (c) 2015 Renato Westphal <renato@openbsd.org>
 * Copyright (c) 2004, 2005 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 <net/if_dl.h>
#include <netinet/in.h>
#include <netinet/ip.h>

#include <arpa/inet.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "eigrpd.h"
#include "eigrpe.h"
#include "log.h"

static int       send_packet_v4(struct iface *, struct nbr *, struct ibuf *);
static int       send_packet_v6(struct iface *, struct nbr *, struct ibuf *);
static int       recv_packet_nbr(struct nbr *, struct eigrp_hdr *,
                    struct seq_addr_head *, struct tlv_mcast_seq *);
static void      recv_packet_eigrp(int, union eigrpd_addr *,
                    union eigrpd_addr *, struct iface *, struct eigrp_hdr *,
                    char *, uint16_t);
static int       eigrp_hdr_sanity_check(int, union eigrpd_addr *,
                    struct eigrp_hdr *, uint16_t, const struct iface *);
static struct iface *find_iface(unsigned int, int, union eigrpd_addr *);

int
gen_eigrp_hdr(struct ibuf *buf, uint16_t opcode, uint8_t flags,
    uint32_t seq_num, uint16_t as)
{
        struct eigrp_hdr        eigrp_hdr;

        memset(&eigrp_hdr, 0, sizeof(eigrp_hdr));
        eigrp_hdr.version = EIGRP_VERSION;
        eigrp_hdr.opcode = opcode;
        /* chksum will be set later */
        eigrp_hdr.flags = htonl(flags);
        eigrp_hdr.seq_num = htonl(seq_num);
        /* ack_num will be set later */
        eigrp_hdr.vrid = htons(EIGRP_VRID_UNICAST_AF);
        eigrp_hdr.as = htons(as);

        return (ibuf_add(buf, &eigrp_hdr, sizeof(eigrp_hdr)));
}

/* send and receive packets */
static int
send_packet_v4(struct iface *iface, struct nbr *nbr, struct ibuf *buf)
{
        struct sockaddr_in       dst;
        struct msghdr            msg;
        struct iovec             iov[2];
        struct ip                ip_hdr;

        /* setup sockaddr */
        dst.sin_family = AF_INET;
        dst.sin_len = sizeof(struct sockaddr_in);
        if (nbr)
                dst.sin_addr = nbr->addr.v4;
        else
                dst.sin_addr = global.mcast_addr_v4;

        /* setup IP hdr */
        memset(&ip_hdr, 0, sizeof(ip_hdr));
        ip_hdr.ip_v = IPVERSION;
        ip_hdr.ip_hl = sizeof(ip_hdr) >> 2;
        ip_hdr.ip_tos = IPTOS_PREC_INTERNETCONTROL;
        ip_hdr.ip_len = htons(ibuf_size(buf) + sizeof(ip_hdr));
        ip_hdr.ip_id = 0;  /* 0 means kernel set appropriate value */
        ip_hdr.ip_off = 0;
        ip_hdr.ip_ttl = EIGRP_IP_TTL;
        ip_hdr.ip_p = IPPROTO_EIGRP;
        ip_hdr.ip_sum = 0;
        ip_hdr.ip_src.s_addr = if_primary_addr(iface);
        ip_hdr.ip_dst = dst.sin_addr;

        /* setup buffer */
        memset(&msg, 0, sizeof(msg));
        iov[0].iov_base = &ip_hdr;
        iov[0].iov_len = sizeof(ip_hdr);
        iov[1].iov_base = ibuf_data(buf);
        iov[1].iov_len = ibuf_size(buf);
        msg.msg_name = &dst;
        msg.msg_namelen = sizeof(dst);
        msg.msg_iov = iov;
        msg.msg_iovlen = 2;

        /* set outgoing interface for multicast traffic */
        if (IN_MULTICAST(ntohl(dst.sin_addr.s_addr)))
                if (if_set_ipv4_mcast(iface) == -1) {
                        log_warn("%s: error setting multicast interface, %s",
                            __func__, iface->name);
                        return (-1);
                }

        if (sendmsg(global.eigrp_socket_v4, &msg, 0) == -1) {
                log_warn("%s: error sending packet on interface %s",
                    __func__, iface->name);
                return (-1);
        }

        return (0);
}

static int
send_packet_v6(struct iface *iface, struct nbr *nbr, struct ibuf *buf)
{
        struct sockaddr_in6      sa6;

        /* setup sockaddr */
        memset(&sa6, 0, sizeof(sa6));
        sa6.sin6_family = AF_INET6;
        sa6.sin6_len = sizeof(struct sockaddr_in6);
        if (nbr) {
                sa6.sin6_addr = nbr->addr.v6;
                addscope(&sa6, iface->ifindex);
        } else
                sa6.sin6_addr = global.mcast_addr_v6;

        /* set outgoing interface for multicast traffic */
        if (IN6_IS_ADDR_MULTICAST(&sa6.sin6_addr))
                if (if_set_ipv6_mcast(iface) == -1) {
                        log_warn("%s: error setting multicast interface, %s",
                            __func__, iface->name);
                        return (-1);
                }

        if (sendto(global.eigrp_socket_v6, ibuf_data(buf), ibuf_size(buf), 0,
            (struct sockaddr *)&sa6, sizeof(sa6)) == -1) {
                log_warn("%s: error sending packet on interface %s",
                    __func__, iface->name);
                return (-1);
        }

        return (0);
}

int
send_packet(struct eigrp_iface *ei, struct nbr *nbr, uint32_t flags,
    struct ibuf *buf)
{
        struct eigrp            *eigrp = ei->eigrp;
        struct iface            *iface = ei->iface;
        struct ibuf              ebuf;
        struct eigrp_hdr         eigrp_hdr;

        if (!(iface->flags & IFF_UP) || !LINK_STATE_IS_UP(iface->linkstate))
                return (-1);

        /* update ack number, flags and checksum */
        if (nbr) {
                if (ibuf_set_n32(buf, offsetof(struct eigrp_hdr, ack_num),
                    nbr->recv_seq) == -1)
                        fatalx("send_packet: set of ack_num failed");
                rtp_ack_stop_timer(nbr);
        }

        ibuf_from_ibuf(&ebuf, buf);
        if (ibuf_get(&ebuf, &eigrp_hdr, sizeof(eigrp_hdr)) == -1)
                fatalx("send_packet: get hdr failed");

        if (flags) {
                flags |= ntohl(eigrp_hdr.flags);
                if (ibuf_set_n32(buf, offsetof(struct eigrp_hdr, flags),
                    flags) == -1)
                        fatalx("send_packet: set of flags failed");
        }

        if (ibuf_set_n16(buf, offsetof(struct eigrp_hdr, chksum), 0) == -1)
                fatalx("send_packet: set of chksum failed");
        if (ibuf_set_n16(buf, offsetof(struct eigrp_hdr, chksum),
            in_cksum(ibuf_data(buf), ibuf_size(buf))) == -1)
                fatalx("send_packet: set of chksum failed");

        /* log packet being sent */
        if (eigrp_hdr.opcode != EIGRP_OPC_HELLO) {
                char    buffer[64];

                if (nbr)
                        snprintf(buffer, sizeof(buffer), "nbr %s",
                            log_addr(eigrp->af, &nbr->addr));
                else
                        snprintf(buffer, sizeof(buffer), "(multicast)");

                log_debug("%s: type %s iface %s %s AS %u seq %u ack %u",
                    __func__, opcode_name(eigrp_hdr.opcode), iface->name,
                    buffer, ntohs(eigrp_hdr.as), ntohl(eigrp_hdr.seq_num),
                    ntohl(eigrp_hdr.ack_num));
        }

        switch (eigrp->af) {
        case AF_INET:
                if (send_packet_v4(iface, nbr, buf) < 0)
                        return (-1);
                break;
        case AF_INET6:
                if (send_packet_v6(iface, nbr, buf) < 0)
                        return (-1);
                break;
        default:
                fatalx("send_packet: unknown af");
        }

        switch (eigrp_hdr.opcode) {
        case EIGRP_OPC_HELLO:
                if (ntohl(eigrp_hdr.ack_num) == 0)
                        ei->eigrp->stats.hellos_sent++;
                else
                        ei->eigrp->stats.acks_sent++;
                break;
        case EIGRP_OPC_UPDATE:
                ei->eigrp->stats.updates_sent++;
                break;
        case EIGRP_OPC_QUERY:
                ei->eigrp->stats.queries_sent++;
                break;
        case EIGRP_OPC_REPLY:
                ei->eigrp->stats.replies_sent++;
                break;
        case EIGRP_OPC_SIAQUERY:
                ei->eigrp->stats.squeries_sent++;
                break;
        case EIGRP_OPC_SIAREPLY:
                ei->eigrp->stats.sreplies_sent++;
                break;
        default:
                break;
        }

        return (0);
}

static int
recv_packet_nbr(struct nbr *nbr, struct eigrp_hdr *eigrp_hdr,
    struct seq_addr_head *seq_addr_list, struct tlv_mcast_seq *tm)
{
        uint32_t                 seq, ack;
        struct seq_addr_entry   *sa;

        seq = ntohl(eigrp_hdr->seq_num);
        ack = ntohl(eigrp_hdr->ack_num);

        /*
         * draft-savage-eigrp-04 - Section 5.3.1:
         * "In addition to the HELLO packet, if any packet is received within
         * the hold time period, then the Hold Time period will be reset."
         */
        nbr_start_timeout(nbr);

        /* handle the sequence tlv */
        if (eigrp_hdr->opcode == EIGRP_OPC_HELLO &&
            !TAILQ_EMPTY(seq_addr_list)) {
                nbr->flags |= F_EIGRP_NBR_CR_MODE;

                TAILQ_FOREACH(sa, seq_addr_list, entry) {
                        switch (sa->af) {
                        case AF_INET:
                                if (sa->addr.v4.s_addr ==
                                    if_primary_addr(nbr->ei->iface)) {
                                        nbr->flags &= ~F_EIGRP_NBR_CR_MODE;
                                        break;
                                }
                                break;
                        case AF_INET6:
                                if (IN6_ARE_ADDR_EQUAL(&sa->addr.v6,
                                    &nbr->ei->iface->linklocal)) {
                                        nbr->flags &= ~F_EIGRP_NBR_CR_MODE;
                                        break;
                                }
                                break;
                        default:
                                break;
                        }
                }
                if (tm)
                        nbr->next_mcast_seq = ntohl(tm->seq);
        }

        if ((ntohl(eigrp_hdr->flags) & EIGRP_HDR_FLAG_CR)) {
                if (!(nbr->flags & F_EIGRP_NBR_CR_MODE))
                        return (-1);
                nbr->flags &= ~F_EIGRP_NBR_CR_MODE;
                if (ntohl(eigrp_hdr->seq_num) != nbr->next_mcast_seq)
                        return (-1);
        }

        /* ack processing */
        if (ack != 0)
                rtp_process_ack(nbr, ack);
        if (seq != 0) {
                /* check for sequence wraparound */
                if (nbr->recv_seq >= seq &&
                   !(nbr->recv_seq == UINT32_MAX && seq == 1)) {
                        log_debug("%s: duplicate packet", __func__);
                        rtp_send_ack(nbr);
                        return (-1);
                }
                nbr->recv_seq = seq;
        }

        return (0);
}

static void
recv_packet_eigrp(int af, union eigrpd_addr *src, union eigrpd_addr *dest,
    struct iface *iface, struct eigrp_hdr *eigrp_hdr, char *buf, uint16_t len)
{
        struct eigrp_iface      *ei;
        struct nbr              *nbr;
        struct tlv_parameter    *tp = NULL;
        struct tlv_sw_version   *tv = NULL;
        struct tlv_mcast_seq    *tm = NULL;
        struct rinfo             ri;
        struct rinfo_entry      *re;
        struct seq_addr_head     seq_addr_list;
        struct rinfo_head        rinfo_list;

        /* EIGRP header sanity checks */
        if (eigrp_hdr_sanity_check(af, dest, eigrp_hdr, len, iface) == -1)
                return;

        buf += sizeof(*eigrp_hdr);
        len -= sizeof(*eigrp_hdr);

        TAILQ_INIT(&seq_addr_list);
        TAILQ_INIT(&rinfo_list);
        while (len > 0) {
                struct tlv      tlv;
                uint16_t        tlv_type;

                if (len < sizeof(tlv)) {
                        log_debug("%s: malformed packet (bad length)",
                            __func__);
                        goto error;
                }

                memcpy(&tlv, buf, sizeof(tlv));
                if (ntohs(tlv.length) < sizeof(tlv) ||
                    ntohs(tlv.length) > len) {
                        log_debug("%s: malformed packet (bad length)",
                            __func__);
                        goto error;
                }

                tlv_type = ntohs(tlv.type);
                switch (tlv_type) {
                case TLV_TYPE_PARAMETER:
                        if ((tp = tlv_decode_parameter(&tlv, buf)) == NULL)
                                goto error;
                        break;
                case TLV_TYPE_SEQ:
                        if (tlv_decode_seq(af, &tlv, buf, &seq_addr_list) < 0)
                                goto error;
                        break;
                case TLV_TYPE_SW_VERSION:
                        if ((tv = tlv_decode_sw_version(&tlv, buf)) == NULL)
                                goto error;
                        break;
                case TLV_TYPE_MCAST_SEQ:
                        if ((tm = tlv_decode_mcast_seq(&tlv, buf)) == NULL)
                                goto error;
                        break;
                case TLV_TYPE_IPV4_INTERNAL:
                case TLV_TYPE_IPV4_EXTERNAL:
                case TLV_TYPE_IPV6_INTERNAL:
                case TLV_TYPE_IPV6_EXTERNAL:
                        /* silently ignore TLV from different address-family */
                        if ((tlv_type & TLV_PROTO_MASK) == TLV_PROTO_IPV4 &&
                            af != AF_INET)
                                break;
                        if ((tlv_type & TLV_PROTO_MASK) == TLV_PROTO_IPV6 &&
                            af != AF_INET6)
                                break;

                        if (tlv_decode_route(af, &tlv, buf, &ri) < 0)
                                goto error;
                        if ((re = calloc(1, sizeof(*re))) == NULL)
                                fatal("recv_packet_eigrp");
                        re->rinfo = ri;
                        TAILQ_INSERT_TAIL(&rinfo_list, re, entry);
                        break;
                case TLV_TYPE_AUTH:
                case TLV_TYPE_PEER_TERM:
                        /*
                         * XXX There is no enough information in the draft
                         * to implement these TLVs properly.
                         */
                case TLV_TYPE_IPV4_COMMUNITY:
                case TLV_TYPE_IPV6_COMMUNITY:
                        /* TODO */
                default:
                        /* ignore unknown tlv */
                        break;
                }
                buf += ntohs(tlv.length);
                len -= ntohs(tlv.length);
        }

        ei = eigrp_if_lookup(iface, af, ntohs(eigrp_hdr->as));
        if (ei == NULL || ei->passive)
                goto error;

        nbr = nbr_find(ei, src);
        if (nbr == NULL && (eigrp_hdr->opcode != EIGRP_OPC_HELLO ||
            ntohl(eigrp_hdr->ack_num) != 0)) {
                log_debug("%s: unknown neighbor", __func__);
                goto error;
        } else if (nbr && recv_packet_nbr(nbr, eigrp_hdr, &seq_addr_list,
            tm) < 0)
                goto error;

        /* log packet being received */
        if (eigrp_hdr->opcode != EIGRP_OPC_HELLO)
                log_debug("%s: type %s nbr %s AS %u seq %u ack %u", __func__,
                    opcode_name(eigrp_hdr->opcode), log_addr(af, &nbr->addr),
                    ei->eigrp->as, ntohl(eigrp_hdr->seq_num),
                    ntohl(eigrp_hdr->ack_num));

        /* switch EIGRP packet type */
        switch (eigrp_hdr->opcode) {
        case EIGRP_OPC_HELLO:
                if (ntohl(eigrp_hdr->ack_num) == 0) {
                        recv_hello(ei, src, nbr, tp);
                        ei->eigrp->stats.hellos_recv++;
                } else
                        ei->eigrp->stats.acks_recv++;
                break;
        case EIGRP_OPC_UPDATE:
                recv_update(nbr, &rinfo_list, ntohl(eigrp_hdr->flags));
                ei->eigrp->stats.updates_recv++;
                break;
        case EIGRP_OPC_QUERY:
                recv_query(nbr, &rinfo_list, 0);
                ei->eigrp->stats.queries_recv++;
                break;
        case EIGRP_OPC_REPLY:
                recv_reply(nbr, &rinfo_list, 0);
                ei->eigrp->stats.replies_recv++;
                break;
        case EIGRP_OPC_SIAQUERY:
                recv_query(nbr, &rinfo_list, 1);
                ei->eigrp->stats.squeries_recv++;
                break;
        case EIGRP_OPC_SIAREPLY:
                recv_reply(nbr, &rinfo_list, 1);
                ei->eigrp->stats.sreplies_recv++;
                break;
        default:
                log_debug("%s: unknown EIGRP packet type, interface %s",
                    __func__, iface->name);
        }

error:
        /* free rinfo tlvs */
        message_list_clr(&rinfo_list);
        /* free seq addresses tlvs */
        seq_addr_list_clr(&seq_addr_list);
}

#define CMSG_MAXLEN max(sizeof(struct sockaddr_dl), sizeof(struct in6_pktinfo))
void
recv_packet(int fd, short event, void *bula)
{
        union {
                struct  cmsghdr hdr;
                char    buf[CMSG_SPACE(CMSG_MAXLEN)];
        } cmsgbuf;
        struct msghdr            msg;
        struct sockaddr_storage  from;
        struct iovec             iov;
        struct ip                ip_hdr;
        char                     pkt[READ_BUF_SIZE];
        char                    *buf;
        struct cmsghdr          *cmsg;
        ssize_t                  r;
        uint16_t                 len;
        int                      af;
        union eigrpd_addr        src, dest;
        unsigned int             ifindex = 0;
        struct iface            *iface;
        struct eigrp_hdr        *eigrp_hdr;

        if (event != EV_READ)
                return;

        /* setup buffer */
        memset(&msg, 0, sizeof(msg));
        iov.iov_base = buf = pkt;
        iov.iov_len = READ_BUF_SIZE;
        msg.msg_name = &from;
        msg.msg_namelen = sizeof(from);
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = &cmsgbuf.buf;
        msg.msg_controllen = sizeof(cmsgbuf.buf);

        if ((r = recvmsg(fd, &msg, 0)) == -1) {
                if (errno != EAGAIN && errno != EINTR)
                        log_debug("%s: read error: %s", __func__,
                            strerror(errno));
                return;
        }
        len = (uint16_t)r;

        sa2addr((struct sockaddr *)&from, &af, &src);
        if (bad_addr(af, &src)) {
                log_debug("%s: invalid source address: %s", __func__,
                    log_addr(af, &src));
                return;
        }

        for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
            cmsg = CMSG_NXTHDR(&msg, cmsg)) {
                if (af == AF_INET && cmsg->cmsg_level == IPPROTO_IP &&
                    cmsg->cmsg_type == IP_RECVIF) {
                        ifindex = ((struct sockaddr_dl *)
                            CMSG_DATA(cmsg))->sdl_index;
                        break;
                }
                if (af == AF_INET6 && cmsg->cmsg_level == IPPROTO_IPV6 &&
                    cmsg->cmsg_type == IPV6_PKTINFO) {
                        ifindex = ((struct in6_pktinfo *)
                            CMSG_DATA(cmsg))->ipi6_ifindex;
                        dest.v6 = ((struct in6_pktinfo *)
                            CMSG_DATA(cmsg))->ipi6_addr;
                        break;
                }
        }

        /* find a matching interface */
        if ((iface = find_iface(ifindex, af, &src)) == NULL)
                return;

        /* the IPv4 raw sockets API gives us direct access to the IP header */
        if (af == AF_INET) {
                if (len < sizeof(ip_hdr)) {
                        log_debug("%s: bad packet size", __func__);
                        return;
                }
                memcpy(&ip_hdr, buf, sizeof(ip_hdr));
                if (ntohs(ip_hdr.ip_len) != len) {
                        log_debug("%s: invalid IP packet length %u", __func__,
                            ntohs(ip_hdr.ip_len));
                        return;
                }
                buf += ip_hdr.ip_hl << 2;
                len -= ip_hdr.ip_hl << 2;
                dest.v4 = ip_hdr.ip_dst;
        }

        /* validate destination address */
        switch (af) {
        case AF_INET:
                /*
                 * Packet needs to be sent to 224.0.0.10 or to one of the
                 * interface addresses.
                 */
                if (dest.v4.s_addr != global.mcast_addr_v4.s_addr) {
                        struct if_addr  *if_addr;
                        int              found = 0;

                        TAILQ_FOREACH(if_addr, &iface->addr_list, entry)
                                if (if_addr->af == AF_INET &&
                                    dest.v4.s_addr == if_addr->addr.v4.s_addr) {
                                        found = 1;
                                        break;
                                }
                        if (found == 0) {
                                log_debug("%s: packet sent to wrong address "
                                    "%s, interface %s", __func__,
                                    inet_ntoa(dest.v4), iface->name);
                                return;
                        }
                }
                break;
        case AF_INET6:
                /*
                 * Packet needs to be sent to ff02::a or to the link local
                 * address of the interface.
                 */
                if (!IN6_ARE_ADDR_EQUAL(&dest.v6, &global.mcast_addr_v6) &&
                    !IN6_ARE_ADDR_EQUAL(&dest.v6, &iface->linklocal)) {
                        log_debug("%s: packet sent to wrong address %s, "
                            "interface %s", __func__, log_in6addr(&dest.v6),
                            iface->name);
                        return;
                }
                break;
        default:
                fatalx("recv_packet: unknown af");
                break;
        }

        if (len < sizeof(*eigrp_hdr)) {
                log_debug("%s: bad packet size", __func__);
                return;
        }
        eigrp_hdr = (struct eigrp_hdr *)buf;

        recv_packet_eigrp(af, &src, &dest, iface, eigrp_hdr, buf, len);
}

static int
eigrp_hdr_sanity_check(int af, union eigrpd_addr *addr,
    struct eigrp_hdr *eigrp_hdr, uint16_t len, const struct iface *iface)
{
        if (in_cksum(eigrp_hdr, len)) {
                log_debug("%s: invalid checksum, interface %s", __func__,
                    iface->name);
                return (-1);
        }

        if (eigrp_hdr->version != EIGRP_HEADER_VERSION) {
                log_debug("%s: invalid EIGRP version %d, interface %s",
                    __func__, eigrp_hdr->version, iface->name);
                return (-1);
        }

        if (ntohs(eigrp_hdr->vrid) != EIGRP_VRID_UNICAST_AF) {
                log_debug("%s: unknown or unsupported vrid %u, interface %s",
                    __func__, ntohs(eigrp_hdr->vrid), iface->name);
                return (-1);
        }

        if (eigrp_hdr->opcode == EIGRP_OPC_HELLO &&
            eigrp_hdr->ack_num != 0) {
                switch (af) {
                case AF_INET:
                        if (IN_MULTICAST(addr->v4.s_addr)) {
                                log_debug("%s: multicast ack (ipv4), "
                                    "interface %s", __func__, iface->name);
                                return (-1);
                        }
                        break;
                case AF_INET6:
                        if (IN6_IS_ADDR_MULTICAST(&addr->v6)) {
                                log_debug("%s: multicast ack (ipv6), "
                                    "interface %s", __func__, iface->name);
                                return (-1);
                        }
                        break;
                default:
                        fatalx("eigrp_hdr_sanity_check: unknown af");
                }
        }

        return (0);
}

static struct iface *
find_iface(unsigned int ifindex, int af, union eigrpd_addr *src)
{
        struct iface    *iface;
        struct if_addr  *if_addr;
        in_addr_t        mask;

        iface = if_lookup(econf, ifindex);
        if (iface == NULL)
                return (NULL);

        switch (af) {
        case AF_INET:
                /*
                 * From CCNP ROUTE 642-902 OCG:
                 * "EIGRP's rules about neighbor IP addresses being in the same
                 * subnet are less exact than OSPF. OSPF requires matching
                 * subnet numbers and masks. EIGRP just asks the question of
                 * whether the neighbor's IP address is in the range of
                 * addresses for the subnet as known to the local router."
                 */
                TAILQ_FOREACH(if_addr, &iface->addr_list, entry) {
                        if (if_addr->af == AF_INET) {
                                mask = prefixlen2mask(if_addr->prefixlen);

                                if ((if_addr->addr.v4.s_addr & mask) ==
                                    (src->v4.s_addr & mask))
                                        return (iface);
                        }
                }
                break;
        case AF_INET6:
                /*
                 * draft-savage-eigrp-04 - Section 10.1:
                 * "EIGRP IPv6 will check that a received HELLO contains a valid
                 * IPv6 link-local source address."
                 */
                if (IN6_IS_ADDR_LINKLOCAL(&src->v6))
                        return (iface);
                break;
        default:
                fatalx("find_iface: unknown af");
        }

        return (NULL);
}