From 91188ca6c19868cfbf45df693cc599c3850f6526 Mon Sep 17 00:00:00 2001 From: Quentin Young Date: Wed, 19 Dec 2018 16:48:36 +0000 Subject: [PATCH] vrrpd: read and validate vrrp advertisements * 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 --- vrrpd/vrrp.c | 167 +++++++++++++++++++++++++++++++++----------- vrrpd/vrrp.h | 10 +++ vrrpd/vrrp_packet.c | 166 ++++++++++++++++++++++++++++++++++++++++++- vrrpd/vrrp_packet.h | 80 ++++++++++++++++++++- 4 files changed, 379 insertions(+), 44 deletions(-) diff --git a/vrrpd/vrrp.c b/vrrpd/vrrp.c index 0f70c6ddf2..285071f1c4 100644 --- a/vrrpd/vrrp.c +++ b/vrrpd/vrrp.c @@ -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]; diff --git a/vrrpd/vrrp.h b/vrrpd/vrrp.h index 12789d5108..64e29eec00 100644 --- a/vrrpd/vrrp.h +++ b/vrrpd/vrrp.h @@ -21,12 +21,14 @@ #define _VRRP_H #include +#include #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; diff --git a/vrrpd/vrrp_packet.c b/vrrpd/vrrp_packet.c index 631b8d7865..d305081574 100644 --- a/vrrpd/vrrp_packet.c +++ b/vrrpd/vrrp_packet.c @@ -18,13 +18,37 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include +#include +#include +#include -#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; +} diff --git a/vrrpd/vrrp_packet.h b/vrrpd/vrrp_packet.h index 8d8d240bec..7a4a338dae 100644 --- a/vrrpd/vrrp_packet.h +++ b/vrrpd/vrrp_packet.h @@ -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); -- 2.39.5