]> git.proxmox.com Git - mirror_frr.git/blobdiff - vrrpd/vrrp_packet.c
Merge pull request #5793 from ton31337/fix/formatting_show_bgp_summary_failed
[mirror_frr.git] / vrrpd / vrrp_packet.c
index fd0256d6e3871f4200cf6d11f3842f3462173f76..e4fee2d792cec4b98ce0ea5facd30f48226119c5 100644 (file)
 #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 */
-const char *vrrp_packet_names[16] = {
+static const char *const vrrp_packet_names[16] = {
        [0] = "Unknown",
        [VRRP_TYPE_ADVERTISEMENT] = "ADVERTISEMENT",
        [2] = "Unknown",
@@ -50,22 +53,92 @@ const char *vrrp_packet_names[16] = {
 };
 /* clang-format on */
 
-ssize_t vrrp_pkt_build(struct vrrp_pkt **pkt, struct ipaddr *src, uint8_t vrid,
-                      uint8_t prio, uint16_t max_adver_int, uint8_t numip,
-                      struct ipaddr **ips)
+/*
+ * 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 v6 = IS_IPADDR_V6(ips[0]);
+       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)) {
+               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 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 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 addrsz = v6 ? sizeof(struct in6_addr) : sizeof(struct in_addr);
-       size_t pktsize = VRRP_PKT_SIZE(v6 ? AF_INET6 : AF_INET, numip);
-       *pkt = XCALLOC(MTYPE_TMP, pktsize);
+       size_t pktsize = VRRP_PKT_SIZE(v6 ? AF_INET6 : AF_INET, version, numip);
 
-       (*pkt)->hdr.vertype |= VRRP_VERSION << 4;
+       *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;
-       (*pkt)->hdr.v3.adver_int = htons(max_adver_int);
+       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;
 
@@ -74,28 +147,17 @@ ssize_t vrrp_pkt_build(struct vrrp_pkt **pkt, struct ipaddr *src, uint8_t vrid,
                aptr += addrsz;
        }
 
-       (*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 = 112;
-               (*pkt)->hdr.chksum = in_cksum_with_ph6(&ph, *pkt, pktsize);
-       } else {
-               struct ipv4_ph ph = {};
-               ph.src = src->ipaddr_v4;
-               inet_pton(AF_INET, VRRP_MCASTV4_GROUP_STR, &ph.dst);
-               ph.proto = 112;
-               ph.len = htons(pktsize);
-               (*pkt)->hdr.chksum = in_cksum_with_ph4(&ph, *pkt, pktsize);
-       }
+       (*pkt)->hdr.chksum = vrrp_pkt_checksum(*pkt, pktsize, src);
 
        return pktsize;
 }
 
-size_t vrrp_pkt_dump(char *buf, size_t buflen, struct vrrp_pkt *pkt)
+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;
@@ -105,30 +167,31 @@ size_t vrrp_pkt_dump(char *buf, size_t buflen, struct vrrp_pkt *pkt)
        struct vrrp_hdr *hdr = &pkt->hdr;
 
        buf[0] = 0x00;
-       snprintf(tmpbuf, sizeof(tmpbuf), "Version: %u\n", (hdr->vertype >> 4));
+       snprintf(tmpbuf, sizeof(tmpbuf), "version %u, ", (hdr->vertype >> 4));
        rs += strlcat(buf, tmpbuf, buflen);
-       snprintf(tmpbuf, sizeof(tmpbuf), "Type: %u (%s)\n",
+       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\n", hdr->vrid);
+       snprintf(tmpbuf, sizeof(tmpbuf), "vrid %u, ", hdr->vrid);
        rs += strlcat(buf, tmpbuf, buflen);
-       snprintf(tmpbuf, sizeof(tmpbuf), "Priority: %u\n", hdr->priority);
+       snprintf(tmpbuf, sizeof(tmpbuf), "priority %u, ", hdr->priority);
        rs += strlcat(buf, tmpbuf, buflen);
-       snprintf(tmpbuf, sizeof(tmpbuf), "Count IPvX: %u\n", hdr->naddr);
+       snprintf(tmpbuf, sizeof(tmpbuf), "#%u addresses, ", hdr->naddr);
        rs += strlcat(buf, tmpbuf, buflen);
-       snprintf(tmpbuf, sizeof(tmpbuf), "Max Adver Int: %u\n",
+       snprintf(tmpbuf, sizeof(tmpbuf), "max adver int %u, ",
                 ntohs(hdr->v3.adver_int));
        rs += strlcat(buf, tmpbuf, buflen);
-       snprintf(tmpbuf, sizeof(tmpbuf), "Checksum: %x\n", ntohs(hdr->chksum));
+       snprintf(tmpbuf, sizeof(tmpbuf), "checksum %x", ntohs(hdr->chksum));
        rs += strlcat(buf, tmpbuf, buflen);
 
        return rs;
 }
 
-ssize_t vrrp_parse_datagram(int family, struct msghdr *m, size_t read,
-                           struct vrrp_pkt **pkt, char *errmsg,
-                           size_t errmsg_len)
+ssize_t vrrp_pkt_parse_datagram(int family, int version, 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)
@@ -159,7 +222,8 @@ ssize_t vrrp_parse_datagram(int family, struct msghdr *m, size_t read,
                /* IP total length check */
                VRRP_PKT_VCHECK(
                        ntohs(ip->ip_len) == read,
-                       "IPv4 packet length field does not match # received bytes; %u != %lu",
+                       "IPv4 packet length field does not match # received bytes; %" PRIu16
+                       "!= %zu",
                        ntohs(ip->ip_len), read);
 
                /* TTL check */
@@ -172,8 +236,15 @@ ssize_t vrrp_parse_datagram(int family, struct msghdr *m, size_t read,
 
                /* 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)
@@ -183,12 +254,20 @@ ssize_t vrrp_parse_datagram(int family, struct msghdr *m, size_t read,
                VRRP_PKT_VCHECK(!!c, "IPv6 Hop Limit not received");
 
                uint8_t *hoplimit = CMSG_DATA(c);
+
                VRRP_PKT_VCHECK(*hoplimit == 255,
                                "IPv6 Hop Limit is %" PRIu8 "; 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");
        }
@@ -199,30 +278,47 @@ ssize_t vrrp_parse_datagram(int family, struct msghdr *m, size_t read,
        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 (%lu < %lu)", pktsize,
-                       VRRP_MIN_PKT_SIZE);
+                       "VRRP packet is undersized (%zu < %zu)", pktsize,
+                       minsize);
        VRRP_PKT_VCHECK(pktsize <= maxsize,
-                       "VRRP packet is oversized (%lu > %lu)", pktsize,
-                       VRRP_MAX_PKT_SIZE);
+                       "VRRP packet is oversized (%zu > %zu)", pktsize,
+                       maxsize);
+
        /* Version check */
-       VRRP_PKT_VCHECK(((*pkt)->hdr.vertype >> 4) != 2, "VRPPv2 unsupported");
-       VRRP_PKT_VCHECK(((*pkt)->hdr.vertype >> 4) == 3, "Bad version %u",
-                       (*pkt)->hdr.vertype >> 4);
+       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);
+
+       VRRP_PKT_VCHECK((*pkt)->hdr.chksum == chksum,
+                       "Bad VRRP checksum %" PRIx16 "; should be %" PRIx16 "",
+                       (*pkt)->hdr.chksum, chksum);
+
        /* Type check */
-       VRRP_PKT_VCHECK(((*pkt)->hdr.vertype & 0x0F) == 1, "Bad type %u",
+       VRRP_PKT_VCHECK(((*pkt)->hdr.vertype & 0x0F) == 1, "Bad type %" PRIu8,
                        (*pkt)->hdr.vertype & 0x0f);
-       /* # addresses check */
-       size_t ves = VRRP_PKT_SIZE(family, (*pkt)->hdr.naddr);
-       VRRP_PKT_VCHECK(pktsize == ves, "Packet has incorrect # addresses");
-       /* FIXME: checksum check */
-       /* ... */
+
+       /* 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 %" PRIu8,
+                               (*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, #%u", i);
+                               "Bad IP address, #%" PRIu8, i);
                p += addrsz;
        }