]> git.puffer.fish Git - mirror/frr.git/commitdiff
vrrpd: read and validate vrrp advertisements
authorQuentin Young <qlyoung@cumulusnetworks.com>
Wed, 19 Dec 2018 16:48:36 +0000 (16:48 +0000)
committerQuentin Young <qlyoung@cumulusnetworks.com>
Fri, 17 May 2019 00:27:08 +0000 (00:27 +0000)
* Validate IPvX headers and packet contents
* Remove filter of non-255 TTL IPv4 packets; better to receive, log and
  drop them ourselves
* Set outgoing packet TTL / hop limit to 255
* Use existing sockopt functions

Signed-off-by: Quentin Young <qlyoung@cumulusnetworks.com>
vrrpd/vrrp.c
vrrpd/vrrp.h
vrrpd/vrrp_packet.c
vrrpd/vrrp_packet.h

index 0f70c6ddf2611046c1c122ac3b3e834115916652..285071f1c4feea4bf33da9414f797d712bd6064b 100644 (file)
@@ -24,6 +24,7 @@
 #include "lib/if.h"
 #include "lib/linklist.h"
 #include "lib/memory.h"
+#include "lib/network.h"
 #include "lib/prefix.h"
 #include "lib/sockopt.h"
 #include "lib/vrf.h"
@@ -174,8 +175,8 @@ static struct vrrp_router *vrrp_router_create(struct vrrp_vrouter *vr,
 {
        struct vrrp_router *r = XCALLOC(MTYPE_TMP, sizeof(struct vrrp_router));
 
-       r->sock = -1;
        r->family = family;
+       r->sock = -1;
        r->vr = vr;
        r->addrs = list_new();
        r->priority = vr->priority;
@@ -235,7 +236,7 @@ struct vrrp_vrouter *vrrp_lookup(uint8_t vrid)
 /* Network ----------------------------------------------------------------- */
 
 /*
- * Create and broadcast VRRP ADVERTISEMENT message.
+ * Create and multicast a VRRP ADVERTISEMENT message.
  *
  * r
  *    VRRP Router for which to send ADVERTISEMENT
@@ -272,11 +273,77 @@ static void vrrp_send_advertisement(struct vrrp_router *r)
        }
 }
 
-/* FIXME:
-static void vrrp_recv_advertisement(struct thread *thread)
+static void vrrp_recv_advertisement(struct vrrp_router *r, struct vrrp_pkt *pkt,
+                                   size_t pktsize)
 {
+       char dumpbuf[BUFSIZ];
+       vrrp_pkt_dump(dumpbuf, sizeof(dumpbuf), pkt);
+       zlog_debug("Received VRRP Advertisement:\n%s", dumpbuf);
+}
+
+/*
+ * Read and process next IPvX datagram.
+ */
+static int vrrp_read(struct thread *thread)
+{
+       struct vrrp_router *r = thread->arg;
+
+       struct vrrp_pkt *pkt;
+       ssize_t pktsize;
+       ssize_t nbytes;
+       bool resched;
+       char errbuf[BUFSIZ];
+       uint8_t control[64];
+
+       struct msghdr m;
+       struct iovec iov;
+       iov.iov_base = r->ibuf;
+       iov.iov_len = sizeof(r->ibuf);
+       m.msg_name = NULL;
+       m.msg_namelen = 0;
+       m.msg_iov = &iov;
+       m.msg_iovlen = 1;
+       m.msg_control = control;
+       m.msg_controllen = sizeof(control);
+
+       nbytes = recvmsg(r->sock, &m, MSG_DONTWAIT);
+
+       if ((nbytes < 0 && ERRNO_IO_RETRY(errno))) {
+               resched = true;
+               goto done;
+       } else if (nbytes <= 0) {
+               vrrp_event(r, VRRP_EVENT_SHUTDOWN);
+               resched = false;
+               goto done;
+       }
+
+       zlog_debug(VRRP_LOGPFX VRRP_LOGPFX_VRID "Received %s datagram: ",
+                  r->vr->vrid, family2str(r->family));
+       zlog_hexdump(r->ibuf, nbytes);
+
+       pktsize = vrrp_parse_datagram(r->family, &m, nbytes, &pkt, errbuf,
+                                     sizeof(errbuf));
+
+       if (pktsize < 0) {
+               zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID
+                         "%s datagram invalid: %s",
+                         r->vr->vrid, family2str(r->family), errbuf);
+       } else {
+               zlog_debug(VRRP_LOGPFX VRRP_LOGPFX_VRID "Packet looks good",
+                          r->vr->vrid);
+               vrrp_recv_advertisement(r, pkt, pktsize);
+       }
+
+       resched = true;
+
+done:
+       memset(r->ibuf, 0x00, sizeof(r->ibuf));
+
+       if (resched)
+               thread_add_read(master, vrrp_read, r, r->sock, &r->t_read);
+
+       return 0;
 }
-*/
 
 /*
  * Create Virtual Router listen socket and join it to the VRRP multicast group.
@@ -290,6 +357,7 @@ static void vrrp_recv_advertisement(struct thread *thread)
 static int vrrp_socket(struct vrrp_router *r)
 {
        int ret;
+       bool failed = false;
        struct connected *c;
 
        frr_elevate_privs(&vrrp_privs) {
@@ -300,44 +368,55 @@ static int vrrp_socket(struct vrrp_router *r)
                zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID
                          "Can't create %s VRRP socket",
                          r->vr->vrid, r->family == AF_INET ? "v4" : "v6");
-               return errno;
+               failed = true;
+               goto done;
        }
 
-       /*
-        * Configure V4 minimum TTL or V6 minimum Hop Limit for rx; packets not
-        * having at least these values will be dropped
-        *
-        * RFC 5798 5.1.1.3:
-        * 
-        *    The TTL MUST be set to 255.  A VRRP router receiving a packet
-        *    with the TTL not equal to 255 MUST discard the packet.
-        *
-        * RFC 5798 5.1.2.3:
-        *
-        *    The Hop Limit MUST be set to 255.  A VRRP router receiving a
-        *    packet with the Hop Limit not equal to 255 MUST discard the
-        *    packet.
-        */
-       sockopt_minttl(r->family, r->sock, 255);
-
        if (!listcount(r->vr->ifp->connected)) {
                zlog_warn(
                        VRRP_LOGPFX VRRP_LOGPFX_VRID
                        "No address on interface %s; cannot configure multicast",
                        r->vr->vrid, r->vr->ifp->name);
-               close(r->sock);
-               return -1;
+               failed = true;
+               goto done;
        }
 
-       c = listhead(r->vr->ifp->connected)->data;
-       struct in_addr v4 = c->address->u.prefix4;
-
-       /* Join the multicast group.*/
-       if (r->family == AF_INET)
+       if (r->family == AF_INET) {
+               int ttl = 255;
+               ret = setsockopt(r->sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
+                                sizeof(ttl));
+               if (ret < 0) {
+                       zlog_warn(
+                               VRRP_LOGPFX VRRP_LOGPFX_VRID
+                               "Failed to set outgoing multicast TTL count to 255; RFC 5798 compliant implementations will drop our packets",
+                               r->vr->vrid);
+               }
+
+               c = listhead(r->vr->ifp->connected)->data;
+               struct in_addr v4 = c->address->u.prefix4;
+
+               /* Join VRRP IPv4 multicast group */
                ret = setsockopt_ipv4_multicast(r->sock, IP_ADD_MEMBERSHIP, v4,
                                                htonl(VRRP_MCASTV4_GROUP),
                                                r->vr->ifp->ifindex);
-       else if (r->family == AF_INET6) {
+       } else if (r->family == AF_INET6) {
+               ret = setsockopt_ipv6_multicast_hops(r->sock, 255);
+               if (ret < 0) {
+                       zlog_warn(
+                               VRRP_LOGPFX VRRP_LOGPFX_VRID
+                               "Failed to set outgoing multicast hop count to 255; RFC 5798 compliant implementations will drop our packets",
+                               r->vr->vrid);
+               }
+               ret = setsockopt_ipv6_hoplimit(r->sock, 1);
+               if (ret < 0) {
+                       zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID
+                                 "Failed to request IPv6 Hop Limit delivery",
+                                 r->vr->vrid);
+                       failed = true;
+                       goto done;
+               }
+
+               /* Join VRRP IPv6 multicast group */
                struct ipv6_mreq mreq;
                inet_pton(AF_INET6, VRRP_MCASTV6_GROUP_STR, &mreq.ipv6mr_multiaddr);
                mreq.ipv6mr_interface = r->vr->ifp->ifindex;
@@ -347,12 +426,22 @@ static int vrrp_socket(struct vrrp_router *r)
 
        if (ret < 0) {
                zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID
-                         "Can't join VRRP multicast group",
-                         r->vr->vrid);
-               close(r->sock);
-               return -1;
+                         "Failed to join VRRP %s multicast group",
+                         r->vr->vrid, family2str(r->family));
+               failed = true;
        }
-       return 0;
+done:
+       ret = 0;
+       if (failed) {
+               zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID
+                         "Failed to initialize VRRP %s router",
+                         r->vr->vrid, family2str(r->family));
+               if (r->sock >= 0)
+                       close(r->sock);
+               ret = -1;
+       }
+
+       return ret;
 }
 
 
@@ -495,14 +584,14 @@ static int vrrp_startup(struct vrrp_router *r)
        /* Create socket */
        if (r->sock < 0) {
                int ret = vrrp_socket(r);
-               if (ret < 0)
+               if (ret < 0 || r->sock < 0)
                        return ret;
        }
 
        /* Schedule listener */
-       /* ... */
+       thread_add_read(master, vrrp_read, r, r->sock, &r->t_read);
 
-       /* configure effective priority */
+       /* Configure effective priority */
        struct ipaddr *primary = (struct ipaddr *)listhead(r->addrs)->data;
 
        char ipbuf[INET6_ADDRSTRLEN];
index 12789d51085b9417d72e6e3042d8aff6026a9957..64e29eec001bdaf23012cf81381cf3cbd6e3c58b 100644 (file)
 #define _VRRP_H
 
 #include <zebra.h>
+#include <netinet/ip.h>
 
 #include "lib/hash.h"
 #include "lib/hook.h"
 #include "lib/if.h"
 #include "lib/linklist.h"
 #include "lib/privs.h"
+#include "lib/stream.h"
 #include "lib/thread.h"
 
 /* Global definitions */
@@ -65,6 +67,9 @@ struct vrrp_router {
        /* Socket */
        int sock;
 
+       /* Socket read buffer */
+       uint8_t ibuf[IP_MAXPACKET];
+
        /*
         * Address family of this Virtual Router.
         * Either AF_INET or AF_INET6.
@@ -125,6 +130,8 @@ struct vrrp_router {
 
        struct thread *t_master_down_timer;
        struct thread *t_adver_timer;
+       struct thread *t_read;
+       struct thread *t_write;
 };
 
 /*
@@ -148,6 +155,9 @@ struct vrrp_vrouter {
        /* Interface */
        struct interface *ifp;
 
+       /* Version */
+       uint8_t version;
+
        /* Virtual Router Identifier */
        uint32_t vrid;
 
index 631b8d786553ae6e932b3fbca61cc077153a9357..d305081574ac8b2a68a619633c24b0dde5748e60 100644 (file)
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  */
 #include <zebra.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
 
-#include "lib/memory.h"
-#include "lib/ipaddr.h"
 #include "lib/checksum.h"
+#include "lib/ipaddr.h"
+#include "lib/memory.h"
 
 #include "vrrp_packet.h"
 
+/* clang-format off */
+const char *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 */
+
 ssize_t vrrp_pkt_build(struct vrrp_pkt **pkt, uint8_t vrid, uint8_t prio,
                       uint16_t max_adver_int, uint8_t numip,
                       struct ipaddr **ips)
@@ -32,7 +56,7 @@ ssize_t vrrp_pkt_build(struct vrrp_pkt **pkt, uint8_t vrid, uint8_t prio,
        bool v6 = IS_IPADDR_V6(ips[0]);
 
        size_t addrsz = v6 ? sizeof(struct in6_addr) : sizeof(struct in_addr);
-       size_t pktsize = sizeof(struct vrrp_hdr) + addrsz * numip;
+       size_t pktsize = VRRP_PKT_SIZE(v6 ? AF_INET6 : AF_INET, numip);
        *pkt = XCALLOC(MTYPE_TMP, pktsize);
 
        (*pkt)->hdr.vertype |= VRRP_VERSION << 4;
@@ -50,8 +74,144 @@ ssize_t vrrp_pkt_build(struct vrrp_pkt **pkt, uint8_t vrid, uint8_t prio,
        }
        (*pkt)->hdr.chksum = 0;
 
+       /* FIXME: v6 checksum */
        uint16_t chksum = in_cksum(*pkt, pktsize);
        (*pkt)->hdr.chksum = htons(chksum);
 
        return pktsize;
 }
+
+size_t vrrp_pkt_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\n", (hdr->vertype >> 4));
+       rs += strlcat(buf, tmpbuf, buflen);
+       snprintf(tmpbuf, sizeof(tmpbuf), "Type: %u (%s)\n",
+                (hdr->vertype & 0x0F),
+                vrrp_packet_names[(hdr->vertype & 0x0F)]);
+       rs += strlcat(buf, tmpbuf, buflen);
+       snprintf(tmpbuf, sizeof(tmpbuf), "VRID: %u\n", hdr->vrid);
+       rs += strlcat(buf, tmpbuf, buflen);
+       snprintf(tmpbuf, sizeof(tmpbuf), "Priority: %u\n", hdr->priority);
+       rs += strlcat(buf, tmpbuf, buflen);
+       snprintf(tmpbuf, sizeof(tmpbuf), "Count IPvX: %u\n", hdr->naddr);
+       rs += strlcat(buf, tmpbuf, buflen);
+       snprintf(tmpbuf, sizeof(tmpbuf), "Max Adver Int: %u\n",
+                ntohs(hdr->v3.adver_int));
+       rs += strlcat(buf, tmpbuf, buflen);
+       snprintf(tmpbuf, sizeof(tmpbuf), "Checksum: %x\n", 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)
+{
+       /* 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; %u != %lu",
+                       ntohs(ip->ip_len), read);
+
+               /* TTL check */
+               VRRP_PKT_VCHECK(ip->ip_ttl == 255, "IPv4 TTL is not 255");
+
+               *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");
+       } 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 not 255");
+
+               *pkt = (struct vrrp_pkt *)buf;
+               pktsize = read;
+       } 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 (%lu < %lu)", pktsize,
+                       VRRP_MIN_PKT_SIZE);
+       VRRP_PKT_VCHECK(pktsize <= maxsize,
+                       "VRRP packet is oversized (%lu > %lu)", pktsize,
+                       VRRP_MAX_PKT_SIZE);
+       /* 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);
+       /* Type check */
+       VRRP_PKT_VCHECK(((*pkt)->hdr.vertype & 0x0F) == 1, "Bad type %u",
+                       (*pkt)->hdr.vertype & 0x0f);
+       /* Priority check */
+       VRRP_PKT_VCHECK((*pkt)->hdr.priority == 255
+                               || (*pkt)->hdr.priority == 0,
+                       "Bad priority %u", (*pkt)->hdr.priority);
+       /* # addresses check */
+       size_t ves = VRRP_PKT_SIZE(family, (*pkt)->hdr.naddr);
+       VRRP_PKT_VCHECK(pktsize == ves, "Packet has incorrect # addresses");
+       /* FIXME: checksum check */
+       /* ... */
+
+       /* 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);
+               p += addrsz;
+       }
+
+       /* Everything checks out */
+       return pktsize;
+}
index 8d8d240bec6cb384bc45fe4841610bd878f68400..7a4a338dae2a1557f0ec37fe27ea082db7403d3b 100644 (file)
@@ -26,6 +26,8 @@
 #define VRRP_VERSION 3
 #define VRRP_TYPE_ADVERTISEMENT 1
 
+extern const char *vrrp_packet_names[16];
+
 /*
  * Shared header for VRRPv2/v3 packets.
  */
@@ -56,15 +58,37 @@ struct vrrp_hdr {
                } v3;
        };
        uint16_t chksum;
-};
+} __attribute__((packed));
+
+#define VRRP_HDR_SIZE sizeof(struct vrrp_hdr)
 
 struct vrrp_pkt {
        struct vrrp_hdr hdr;
+       /*
+        * When used, this is actually an array of one or the other, not an
+        * array of union. If N v4 addresses are stored then
+        * sizeof(addrs) == N * sizeof(struct in_addr).
+        */
        union {
                struct in_addr v4;
                struct in6_addr v6;
        } addrs[];
-} __attribute((packed, aligned(1)));
+} __attribute__((packed));
+
+#define VRRP_PKT_SIZE(_f, _naddr)                                              \
+       ({                                                                     \
+               size_t _asz = ((_f) == AF_INET) ? sizeof(struct in_addr)       \
+                                               : sizeof(struct in6_addr);     \
+               sizeof(struct vrrp_hdr) + (_asz * (_naddr));                   \
+       })
+
+#define VRRP_MIN_PKT_SIZE_V4 VRRP_PKT_SIZE(AF_INET, 1)
+#define VRRP_MAX_PKT_SIZE_V4 VRRP_PKT_SIZE(AF_INET, 255)
+#define VRRP_MIN_PKT_SIZE_V6 VRRP_PKT_SIZE(AF_INET6, 1)
+#define VRRP_MAX_PKT_SIZE_V6 VRRP_PKT_SIZE(AF_INET6, 255)
+
+#define VRRP_MIN_PKT_SIZE VRRP_MIN_PKT_SIZE_V4
+#define VRRP_MAX_PKT_SIZE VRRP_MAX_PKT_SIZE_V6
 
 /*
  * Builds a VRRP packet.
@@ -94,3 +118,55 @@ struct vrrp_pkt {
 ssize_t vrrp_pkt_build(struct vrrp_pkt **pkt, uint8_t vrid, uint8_t prio,
                       uint16_t max_adver_int, uint8_t numip,
                       struct ipaddr **ips);
+
+/*
+ * Dumps a VRRP packet to a string.
+ *
+ * Currently only dumps the header.
+ *
+ * buf
+ *    Buffer to store string representation
+ *
+ * buflen
+ *    Size of buf
+ *
+ * pkt
+ *    Packet to dump to a string
+ *
+ * Returns:
+ *    # bytes written to buf
+ */
+size_t vrrp_pkt_dump(char *buf, size_t buflen, struct vrrp_pkt *pkt);
+
+
+/*
+ * Parses a VRRP packet, checking for illegal or invalid data.
+ *
+ * This function does not check that the local router is not the IPvX owner for
+ * the addresses received; that should be done by the caller.
+ *
+ * family
+ *    Address family of received packet
+ *
+ * m
+ *    msghdr containing results of recvmsg() on VRRP router socket
+ *
+ * read
+ *    return value of recvmsg() on VRRP router socket; must be non-negative
+ *
+ * pkt
+ *    Pointer to pointer to set to location of VRRP packet within buf
+ *
+ * errmsg
+ *    Buffer to store human-readable error message in case of error; may be
+ *    NULL, in which case no message will be stored
+ *
+ * errmsg_len
+ *    Size of errmsg
+ *
+ * Returns:
+ *    Size of VRRP packet, or -1 upon error
+ */
+ssize_t vrrp_parse_datagram(int family, struct msghdr *m, size_t read,
+                           struct vrrp_pkt **pkt, char *errmsg,
+                           size_t errmsg_len);