// SPDX-License-Identifier: GPL-2.0-or-later /* * VRRP packet crafting. * Copyright (C) 2018-2019 Cumulus Networks, Inc. * Quentin Young */ #include #include #include #include #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; }