/* $OpenBSD: packet.c,v 1.23 2023/12/14 10:02:27 claudio Exp $ */ /* * Copyright (c) 2015 Renato Westphal * Copyright (c) 2004, 2005 Esben Norby * * 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 #include #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(buf, &ebuf); 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) > 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); }