diff options
Diffstat (limited to 'vrrpd/vrrp_packet.c')
-rw-r--r-- | vrrpd/vrrp_packet.c | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/vrrpd/vrrp_packet.c b/vrrpd/vrrp_packet.c new file mode 100644 index 0000000..36494c7 --- /dev/null +++ b/vrrpd/vrrp_packet.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * VRRP packet crafting. + * Copyright (C) 2018-2019 Cumulus Networks, Inc. + * Quentin Young + */ +#include <zebra.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> + +#include "lib/checksum.h" +#include "lib/ipaddr.h" +#include "lib/memory.h" + +#include "vrrp.h" +#include "vrrp_debug.h" +#include "vrrp_packet.h" + +DEFINE_MTYPE_STATIC(VRRPD, VRRP_PKT, "VRRP packet"); + +/* clang-format off */ +static const char *const vrrp_packet_names[16] = { + [0] = "Unknown", + [VRRP_TYPE_ADVERTISEMENT] = "ADVERTISEMENT", + [2] = "Unknown", + [3] = "Unknown", + [4] = "Unknown", + [5] = "Unknown", + [6] = "Unknown", + [7] = "Unknown", + [8] = "Unknown", + [9] = "Unknown", + [10] = "Unknown", + [11] = "Unknown", + [12] = "Unknown", + [13] = "Unknown", + [14] = "Unknown", + [15] = "Unknown", +}; +/* clang-format on */ + +/* + * Compute the VRRP checksum. + * + * Checksum is not set in the packet, just computed. + * + * pkt + * VRRP packet, fully filled out except for checksum field. + * + * pktsize + * sizeof(*pkt) + * + * src + * IP address that pkt will be transmitted from. + * + * Returns: + * VRRP checksum in network byte order. + */ +static uint16_t vrrp_pkt_checksum(struct vrrp_pkt *pkt, size_t pktsize, + struct ipaddr *src, bool ipv4_ph) +{ + uint16_t chksum; + bool v6 = (src->ipa_type == IPADDR_V6); + + uint16_t chksum_pre = pkt->hdr.chksum; + + pkt->hdr.chksum = 0; + + if (v6) { + struct ipv6_ph ph = {}; + + ph.src = src->ipaddr_v6; + inet_pton(AF_INET6, VRRP_MCASTV6_GROUP_STR, &ph.dst); + ph.ulpl = htons(pktsize); + ph.next_hdr = IPPROTO_VRRP; + chksum = in_cksum_with_ph6(&ph, pkt, pktsize); + } else if (!v6 && ((pkt->hdr.vertype >> 4) == 3)) { + if (ipv4_ph) { + struct ipv4_ph ph = {}; + + ph.src = src->ipaddr_v4; + inet_pton(AF_INET, VRRP_MCASTV4_GROUP_STR, &ph.dst); + ph.proto = IPPROTO_VRRP; + ph.len = htons(pktsize); + chksum = in_cksum_with_ph4(&ph, pkt, pktsize); + } else + chksum = in_cksum(pkt, pktsize); + } else if (!v6 && ((pkt->hdr.vertype >> 4) == 2)) { + chksum = in_cksum(pkt, pktsize); + } else { + assert(!"Invalid VRRP protocol version"); + } + + pkt->hdr.chksum = chksum_pre; + + return chksum; +} + +ssize_t vrrp_pkt_adver_build(struct vrrp_pkt **pkt, struct ipaddr *src, + uint8_t version, uint8_t vrid, uint8_t prio, + uint16_t max_adver_int, uint8_t numip, + struct ipaddr **ips, bool ipv4_ph) +{ + bool v6 = false; + size_t addrsz = 0; + + assert(version >= 2 && version <= 3); + + if (numip > 0) { + v6 = IS_IPADDR_V6(ips[0]); + addrsz = IPADDRSZ(ips[0]); + } + + assert(!(version == 2 && v6)); + + size_t pktsize = VRRP_PKT_SIZE(v6 ? AF_INET6 : AF_INET, version, numip); + + *pkt = XCALLOC(MTYPE_VRRP_PKT, pktsize); + + (*pkt)->hdr.vertype |= version << 4; + (*pkt)->hdr.vertype |= VRRP_TYPE_ADVERTISEMENT; + (*pkt)->hdr.vrid = vrid; + (*pkt)->hdr.priority = prio; + (*pkt)->hdr.naddr = numip; + if (version == 3) + (*pkt)->hdr.v3.adver_int = htons(max_adver_int); + else if (version == 2) { + (*pkt)->hdr.v2.auth_type = 0; + (*pkt)->hdr.v2.adver_int = MAX(max_adver_int / 100, 1); + } + + uint8_t *aptr = (void *)(*pkt)->addrs; + + for (int i = 0; i < numip; i++) { + memcpy(aptr, &ips[i]->ip.addr, addrsz); + aptr += addrsz; + } + + (*pkt)->hdr.chksum = vrrp_pkt_checksum(*pkt, pktsize, src, ipv4_ph); + + return pktsize; +} + +void vrrp_pkt_free(struct vrrp_pkt *pkt) +{ + XFREE(MTYPE_VRRP_PKT, pkt); +} + +size_t vrrp_pkt_adver_dump(char *buf, size_t buflen, struct vrrp_pkt *pkt) +{ + if (buflen < 1) + return 0; + + char tmpbuf[BUFSIZ]; + size_t rs = 0; + struct vrrp_hdr *hdr = &pkt->hdr; + + buf[0] = 0x00; + snprintf(tmpbuf, sizeof(tmpbuf), "version %u, ", (hdr->vertype >> 4)); + rs += strlcat(buf, tmpbuf, buflen); + snprintf(tmpbuf, sizeof(tmpbuf), "type %u (%s), ", + (hdr->vertype & 0x0F), + vrrp_packet_names[(hdr->vertype & 0x0F)]); + rs += strlcat(buf, tmpbuf, buflen); + snprintf(tmpbuf, sizeof(tmpbuf), "vrid %u, ", hdr->vrid); + rs += strlcat(buf, tmpbuf, buflen); + snprintf(tmpbuf, sizeof(tmpbuf), "priority %u, ", hdr->priority); + rs += strlcat(buf, tmpbuf, buflen); + snprintf(tmpbuf, sizeof(tmpbuf), "#%u addresses, ", hdr->naddr); + rs += strlcat(buf, tmpbuf, buflen); + snprintf(tmpbuf, sizeof(tmpbuf), "max adver int %u, ", + ntohs(hdr->v3.adver_int)); + rs += strlcat(buf, tmpbuf, buflen); + snprintf(tmpbuf, sizeof(tmpbuf), "checksum %x", ntohs(hdr->chksum)); + rs += strlcat(buf, tmpbuf, buflen); + + return rs; +} + +ssize_t vrrp_pkt_parse_datagram(int family, int version, bool ipv4_ph, + struct msghdr *m, size_t read, + struct ipaddr *src, struct vrrp_pkt **pkt, + char *errmsg, size_t errmsg_len) +{ + /* Source (MAC & IP), Dest (MAC & IP) TTL validation done by kernel */ + size_t addrsz = (family == AF_INET) ? sizeof(struct in_addr) + : sizeof(struct in6_addr); + + size_t pktsize; + uint8_t *buf = m->msg_iov->iov_base; + +#define VRRP_PKT_VCHECK(cond, _f, ...) \ + do { \ + if (!(cond)) { \ + if (errmsg) \ + snprintf(errmsg, errmsg_len, (_f), \ + ##__VA_ARGS__); \ + return -1; \ + } \ + } while (0) + + /* IPvX header check */ + + if (family == AF_INET) { + VRRP_PKT_VCHECK( + read >= sizeof(struct ip), + "Datagram not large enough to contain IP header"); + + struct ip *ip = (struct ip *)buf; + + /* IP total length check */ + VRRP_PKT_VCHECK( + ntohs(ip->ip_len) == read, + "IPv4 packet length field does not match # received bytes; %hu!= %zu", + ntohs(ip->ip_len), read); + + /* TTL check */ + VRRP_PKT_VCHECK(ip->ip_ttl == 255, + "IPv4 TTL is %hhu; should be 255", + ip->ip_ttl); + + *pkt = (struct vrrp_pkt *)(buf + (ip->ip_hl << 2)); + pktsize = read - (ip->ip_hl << 2); + + /* IP empty packet check */ + VRRP_PKT_VCHECK(pktsize > 0, "IPv4 packet has no payload"); + + /* Extract source address */ + struct sockaddr_in *sa = m->msg_name; + + src->ipa_type = IPADDR_V4; + src->ipaddr_v4 = sa->sin_addr; + } else if (family == AF_INET6) { + struct cmsghdr *c; + + for (c = CMSG_FIRSTHDR(m); c != NULL; CMSG_NXTHDR(m, c)) { + if (c->cmsg_level == IPPROTO_IPV6 + && c->cmsg_type == IPV6_HOPLIMIT) + break; + } + + VRRP_PKT_VCHECK(!!c, "IPv6 Hop Limit not received"); + + uint8_t *hoplimit = CMSG_DATA(c); + + VRRP_PKT_VCHECK(*hoplimit == 255, + "IPv6 Hop Limit is %hhu; should be 255", + *hoplimit); + + *pkt = (struct vrrp_pkt *)buf; + pktsize = read; + + /* Extract source address */ + struct sockaddr_in6 *sa = m->msg_name; + + src->ipa_type = IPADDR_V6; + memcpy(&src->ipaddr_v6, &sa->sin6_addr, + sizeof(struct in6_addr)); + } else { + assert(!"Unknown address family"); + } + + /* Size check */ + size_t minsize = (family == AF_INET) ? VRRP_MIN_PKT_SIZE_V4 + : VRRP_MIN_PKT_SIZE_V6; + size_t maxsize = (family == AF_INET) ? VRRP_MAX_PKT_SIZE_V4 + : VRRP_MAX_PKT_SIZE_V6; + VRRP_PKT_VCHECK(pktsize >= minsize, + "VRRP packet is undersized (%zu < %zu)", pktsize, + minsize); + VRRP_PKT_VCHECK(pktsize <= maxsize, + "VRRP packet is oversized (%zu > %zu)", pktsize, + maxsize); + + /* Version check */ + uint8_t pktver = (*pkt)->hdr.vertype >> 4; + + VRRP_PKT_VCHECK(pktver == version, "Bad version %u", pktver); + + /* Checksum check */ + uint16_t chksum = vrrp_pkt_checksum(*pkt, pktsize, src, ipv4_ph); + + VRRP_PKT_VCHECK((*pkt)->hdr.chksum == chksum, + "Bad VRRP checksum %hx; should be %hx", + (*pkt)->hdr.chksum, chksum); + + /* Type check */ + VRRP_PKT_VCHECK(((*pkt)->hdr.vertype & 0x0F) == 1, "Bad type %u", + (*pkt)->hdr.vertype & 0x0f); + + /* Exact size check */ + size_t ves = VRRP_PKT_SIZE(family, pktver, (*pkt)->hdr.naddr); + + VRRP_PKT_VCHECK(pktsize == ves, "Packet has incorrect # addresses%s", + pktver == 2 ? " or missing auth fields" : ""); + + /* auth type check */ + if (version == 2) + VRRP_PKT_VCHECK((*pkt)->hdr.v2.auth_type == 0, + "Bad authentication type %hhu", + (*pkt)->hdr.v2.auth_type); + + /* Addresses check */ + char vbuf[INET6_ADDRSTRLEN]; + uint8_t *p = (uint8_t *)(*pkt)->addrs; + + for (uint8_t i = 0; i < (*pkt)->hdr.naddr; i++) { + VRRP_PKT_VCHECK(inet_ntop(family, p, vbuf, sizeof(vbuf)), + "Bad IP address, #%hhu", i); + p += addrsz; + } + + /* Everything checks out */ + return pktsize; +} |