#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"
{
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;
/* Network ----------------------------------------------------------------- */
/*
- * Create and broadcast VRRP ADVERTISEMENT message.
+ * Create and multicast a VRRP ADVERTISEMENT message.
*
* r
* VRRP Router for which to send ADVERTISEMENT
}
}
-/* 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.
static int vrrp_socket(struct vrrp_router *r)
{
int ret;
+ bool failed = false;
struct connected *c;
frr_elevate_privs(&vrrp_privs) {
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;
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;
}
/* 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];
#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 */
/* Socket */
int sock;
+ /* Socket read buffer */
+ uint8_t ibuf[IP_MAXPACKET];
+
/*
* Address family of this Virtual Router.
* Either AF_INET or AF_INET6.
struct thread *t_master_down_timer;
struct thread *t_adver_timer;
+ struct thread *t_read;
+ struct thread *t_write;
};
/*
/* Interface */
struct interface *ifp;
+ /* Version */
+ uint8_t version;
+
/* Virtual Router Identifier */
uint32_t vrid;
* 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)
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;
}
(*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;
+}
#define VRRP_VERSION 3
#define VRRP_TYPE_ADVERTISEMENT 1
+extern const char *vrrp_packet_names[16];
+
/*
* Shared header for VRRPv2/v3 packets.
*/
} 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.
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);