#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",
};
/* 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;
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;
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)
/* 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 */
/* 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)
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");
}
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;
}