]>
Commit | Line | Data |
---|---|---|
acddc0ed | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
5435a2bf | 2 | /* |
63d4bd12 QY |
3 | * VRRP packet crafting. |
4 | * Copyright (C) 2018-2019 Cumulus Networks, Inc. | |
5 | * Quentin Young | |
5435a2bf QY |
6 | */ |
7 | #include <zebra.h> | |
91188ca6 QY |
8 | #include <netinet/in.h> |
9 | #include <netinet/ip.h> | |
10 | #include <netinet/ip6.h> | |
5435a2bf | 11 | |
3eca3857 | 12 | #include "lib/checksum.h" |
91188ca6 QY |
13 | #include "lib/ipaddr.h" |
14 | #include "lib/memory.h" | |
5435a2bf | 15 | |
8071d5c3 | 16 | #include "vrrp.h" |
b637bcd4 | 17 | #include "vrrp_debug.h" |
5435a2bf QY |
18 | #include "vrrp_packet.h" |
19 | ||
bf8d3d6a | 20 | DEFINE_MTYPE_STATIC(VRRPD, VRRP_PKT, "VRRP packet"); |
7c136b08 | 21 | |
91188ca6 | 22 | /* clang-format off */ |
2b64873d | 23 | static const char *const vrrp_packet_names[16] = { |
91188ca6 QY |
24 | [0] = "Unknown", |
25 | [VRRP_TYPE_ADVERTISEMENT] = "ADVERTISEMENT", | |
26 | [2] = "Unknown", | |
27 | [3] = "Unknown", | |
28 | [4] = "Unknown", | |
29 | [5] = "Unknown", | |
30 | [6] = "Unknown", | |
31 | [7] = "Unknown", | |
32 | [8] = "Unknown", | |
33 | [9] = "Unknown", | |
34 | [10] = "Unknown", | |
35 | [11] = "Unknown", | |
36 | [12] = "Unknown", | |
37 | [13] = "Unknown", | |
38 | [14] = "Unknown", | |
39 | [15] = "Unknown", | |
40 | }; | |
41 | /* clang-format on */ | |
42 | ||
d9e01e1c QY |
43 | /* |
44 | * Compute the VRRP checksum. | |
45 | * | |
46 | * Checksum is not set in the packet, just computed. | |
47 | * | |
48 | * pkt | |
49 | * VRRP packet, fully filled out except for checksum field. | |
50 | * | |
51 | * pktsize | |
52 | * sizeof(*pkt) | |
53 | * | |
54 | * src | |
55 | * IP address that pkt will be transmitted from. | |
56 | * | |
57 | * Returns: | |
58 | * VRRP checksum in network byte order. | |
59 | */ | |
60 | static uint16_t vrrp_pkt_checksum(struct vrrp_pkt *pkt, size_t pktsize, | |
9f2379bd | 61 | struct ipaddr *src, bool ipv4_ph) |
d9e01e1c QY |
62 | { |
63 | uint16_t chksum; | |
64 | bool v6 = (src->ipa_type == IPADDR_V6); | |
65 | ||
66 | uint16_t chksum_pre = pkt->hdr.chksum; | |
2fff50ec | 67 | |
d9e01e1c QY |
68 | pkt->hdr.chksum = 0; |
69 | ||
70 | if (v6) { | |
71 | struct ipv6_ph ph = {}; | |
2fff50ec | 72 | |
d9e01e1c QY |
73 | ph.src = src->ipaddr_v6; |
74 | inet_pton(AF_INET6, VRRP_MCASTV6_GROUP_STR, &ph.dst); | |
75 | ph.ulpl = htons(pktsize); | |
723123f3 | 76 | ph.next_hdr = IPPROTO_VRRP; |
d9e01e1c | 77 | chksum = in_cksum_with_ph6(&ph, pkt, pktsize); |
99966840 | 78 | } else if (!v6 && ((pkt->hdr.vertype >> 4) == 3)) { |
9f2379bd SY |
79 | if (ipv4_ph) { |
80 | struct ipv4_ph ph = {}; | |
81 | ||
82 | ph.src = src->ipaddr_v4; | |
83 | inet_pton(AF_INET, VRRP_MCASTV4_GROUP_STR, &ph.dst); | |
84 | ph.proto = IPPROTO_VRRP; | |
85 | ph.len = htons(pktsize); | |
86 | chksum = in_cksum_with_ph4(&ph, pkt, pktsize); | |
87 | } else | |
88 | chksum = in_cksum(pkt, pktsize); | |
99966840 QY |
89 | } else if (!v6 && ((pkt->hdr.vertype >> 4) == 2)) { |
90 | chksum = in_cksum(pkt, pktsize); | |
91 | } else { | |
92 | assert(!"Invalid VRRP protocol version"); | |
d9e01e1c QY |
93 | } |
94 | ||
95 | pkt->hdr.chksum = chksum_pre; | |
96 | ||
97 | return chksum; | |
98 | } | |
99 | ||
100 | ssize_t vrrp_pkt_adver_build(struct vrrp_pkt **pkt, struct ipaddr *src, | |
101 | uint8_t version, uint8_t vrid, uint8_t prio, | |
102 | uint16_t max_adver_int, uint8_t numip, | |
9f2379bd | 103 | struct ipaddr **ips, bool ipv4_ph) |
5435a2bf | 104 | { |
3d55d467 QY |
105 | bool v6 = false; |
106 | size_t addrsz = 0; | |
3eca3857 | 107 | |
99966840 | 108 | assert(version >= 2 && version <= 3); |
99966840 | 109 | |
3d55d467 QY |
110 | if (numip > 0) { |
111 | v6 = IS_IPADDR_V6(ips[0]); | |
112 | addrsz = IPADDRSZ(ips[0]); | |
113 | } | |
114 | ||
e1a32d76 QY |
115 | assert(!(version == 2 && v6)); |
116 | ||
c2034b25 | 117 | size_t pktsize = VRRP_PKT_SIZE(v6 ? AF_INET6 : AF_INET, version, numip); |
2fff50ec | 118 | |
72df9d93 | 119 | *pkt = XCALLOC(MTYPE_VRRP_PKT, pktsize); |
3eca3857 | 120 | |
d9e01e1c | 121 | (*pkt)->hdr.vertype |= version << 4; |
3eca3857 QY |
122 | (*pkt)->hdr.vertype |= VRRP_TYPE_ADVERTISEMENT; |
123 | (*pkt)->hdr.vrid = vrid; | |
124 | (*pkt)->hdr.priority = prio; | |
125 | (*pkt)->hdr.naddr = numip; | |
99966840 QY |
126 | if (version == 3) |
127 | (*pkt)->hdr.v3.adver_int = htons(max_adver_int); | |
128 | else if (version == 2) { | |
129 | (*pkt)->hdr.v2.auth_type = 0; | |
130 | (*pkt)->hdr.v2.adver_int = MAX(max_adver_int / 100, 1); | |
131 | } | |
3eca3857 | 132 | |
862f2f37 QY |
133 | uint8_t *aptr = (void *)(*pkt)->addrs; |
134 | ||
3eca3857 | 135 | for (int i = 0; i < numip; i++) { |
862f2f37 QY |
136 | memcpy(aptr, &ips[i]->ip.addr, addrsz); |
137 | aptr += addrsz; | |
3eca3857 | 138 | } |
8071d5c3 | 139 | |
9f2379bd | 140 | (*pkt)->hdr.chksum = vrrp_pkt_checksum(*pkt, pktsize, src, ipv4_ph); |
3eca3857 QY |
141 | |
142 | return pktsize; | |
5435a2bf | 143 | } |
91188ca6 | 144 | |
7c136b08 DL |
145 | void vrrp_pkt_free(struct vrrp_pkt *pkt) |
146 | { | |
147 | XFREE(MTYPE_VRRP_PKT, pkt); | |
148 | } | |
149 | ||
d9e01e1c | 150 | size_t vrrp_pkt_adver_dump(char *buf, size_t buflen, struct vrrp_pkt *pkt) |
91188ca6 QY |
151 | { |
152 | if (buflen < 1) | |
153 | return 0; | |
154 | ||
155 | char tmpbuf[BUFSIZ]; | |
156 | size_t rs = 0; | |
157 | struct vrrp_hdr *hdr = &pkt->hdr; | |
158 | ||
159 | buf[0] = 0x00; | |
613b45b0 | 160 | snprintf(tmpbuf, sizeof(tmpbuf), "version %u, ", (hdr->vertype >> 4)); |
91188ca6 | 161 | rs += strlcat(buf, tmpbuf, buflen); |
613b45b0 | 162 | snprintf(tmpbuf, sizeof(tmpbuf), "type %u (%s), ", |
91188ca6 QY |
163 | (hdr->vertype & 0x0F), |
164 | vrrp_packet_names[(hdr->vertype & 0x0F)]); | |
165 | rs += strlcat(buf, tmpbuf, buflen); | |
613b45b0 | 166 | snprintf(tmpbuf, sizeof(tmpbuf), "vrid %u, ", hdr->vrid); |
91188ca6 | 167 | rs += strlcat(buf, tmpbuf, buflen); |
613b45b0 | 168 | snprintf(tmpbuf, sizeof(tmpbuf), "priority %u, ", hdr->priority); |
91188ca6 | 169 | rs += strlcat(buf, tmpbuf, buflen); |
613b45b0 | 170 | snprintf(tmpbuf, sizeof(tmpbuf), "#%u addresses, ", hdr->naddr); |
91188ca6 | 171 | rs += strlcat(buf, tmpbuf, buflen); |
613b45b0 | 172 | snprintf(tmpbuf, sizeof(tmpbuf), "max adver int %u, ", |
91188ca6 QY |
173 | ntohs(hdr->v3.adver_int)); |
174 | rs += strlcat(buf, tmpbuf, buflen); | |
613b45b0 | 175 | snprintf(tmpbuf, sizeof(tmpbuf), "checksum %x", ntohs(hdr->chksum)); |
91188ca6 QY |
176 | rs += strlcat(buf, tmpbuf, buflen); |
177 | ||
178 | return rs; | |
179 | } | |
180 | ||
9f2379bd SY |
181 | ssize_t vrrp_pkt_parse_datagram(int family, int version, bool ipv4_ph, |
182 | struct msghdr *m, size_t read, | |
183 | struct ipaddr *src, struct vrrp_pkt **pkt, | |
184 | char *errmsg, size_t errmsg_len) | |
91188ca6 QY |
185 | { |
186 | /* Source (MAC & IP), Dest (MAC & IP) TTL validation done by kernel */ | |
187 | size_t addrsz = (family == AF_INET) ? sizeof(struct in_addr) | |
188 | : sizeof(struct in6_addr); | |
189 | ||
190 | size_t pktsize; | |
191 | uint8_t *buf = m->msg_iov->iov_base; | |
192 | ||
193 | #define VRRP_PKT_VCHECK(cond, _f, ...) \ | |
194 | do { \ | |
195 | if (!(cond)) { \ | |
196 | if (errmsg) \ | |
197 | snprintf(errmsg, errmsg_len, (_f), \ | |
198 | ##__VA_ARGS__); \ | |
199 | return -1; \ | |
200 | } \ | |
201 | } while (0) | |
202 | ||
203 | /* IPvX header check */ | |
204 | ||
205 | if (family == AF_INET) { | |
206 | VRRP_PKT_VCHECK( | |
207 | read >= sizeof(struct ip), | |
208 | "Datagram not large enough to contain IP header"); | |
209 | ||
210 | struct ip *ip = (struct ip *)buf; | |
211 | ||
212 | /* IP total length check */ | |
213 | VRRP_PKT_VCHECK( | |
214 | ntohs(ip->ip_len) == read, | |
6cde4b45 | 215 | "IPv4 packet length field does not match # received bytes; %hu!= %zu", |
91188ca6 QY |
216 | ntohs(ip->ip_len), read); |
217 | ||
218 | /* TTL check */ | |
dad18a2f | 219 | VRRP_PKT_VCHECK(ip->ip_ttl == 255, |
6cde4b45 | 220 | "IPv4 TTL is %hhu; should be 255", |
dad18a2f | 221 | ip->ip_ttl); |
91188ca6 QY |
222 | |
223 | *pkt = (struct vrrp_pkt *)(buf + (ip->ip_hl << 2)); | |
224 | pktsize = read - (ip->ip_hl << 2); | |
225 | ||
226 | /* IP empty packet check */ | |
227 | VRRP_PKT_VCHECK(pktsize > 0, "IPv4 packet has no payload"); | |
d04bb25a QY |
228 | |
229 | /* Extract source address */ | |
fa211f1c | 230 | struct sockaddr_in *sa = m->msg_name; |
2fff50ec | 231 | |
d04bb25a | 232 | src->ipa_type = IPADDR_V4; |
fa211f1c | 233 | src->ipaddr_v4 = sa->sin_addr; |
91188ca6 QY |
234 | } else if (family == AF_INET6) { |
235 | struct cmsghdr *c; | |
2fff50ec | 236 | |
91188ca6 QY |
237 | for (c = CMSG_FIRSTHDR(m); c != NULL; CMSG_NXTHDR(m, c)) { |
238 | if (c->cmsg_level == IPPROTO_IPV6 | |
239 | && c->cmsg_type == IPV6_HOPLIMIT) | |
240 | break; | |
241 | } | |
242 | ||
243 | VRRP_PKT_VCHECK(!!c, "IPv6 Hop Limit not received"); | |
244 | ||
245 | uint8_t *hoplimit = CMSG_DATA(c); | |
2fff50ec | 246 | |
dad18a2f | 247 | VRRP_PKT_VCHECK(*hoplimit == 255, |
6cde4b45 | 248 | "IPv6 Hop Limit is %hhu; should be 255", |
dad18a2f | 249 | *hoplimit); |
91188ca6 QY |
250 | |
251 | *pkt = (struct vrrp_pkt *)buf; | |
252 | pktsize = read; | |
d04bb25a QY |
253 | |
254 | /* Extract source address */ | |
d04bb25a | 255 | struct sockaddr_in6 *sa = m->msg_name; |
2fff50ec | 256 | |
fa211f1c | 257 | src->ipa_type = IPADDR_V6; |
d04bb25a QY |
258 | memcpy(&src->ipaddr_v6, &sa->sin6_addr, |
259 | sizeof(struct in6_addr)); | |
91188ca6 QY |
260 | } else { |
261 | assert(!"Unknown address family"); | |
262 | } | |
263 | ||
264 | /* Size check */ | |
265 | size_t minsize = (family == AF_INET) ? VRRP_MIN_PKT_SIZE_V4 | |
266 | : VRRP_MIN_PKT_SIZE_V6; | |
267 | size_t maxsize = (family == AF_INET) ? VRRP_MAX_PKT_SIZE_V4 | |
268 | : VRRP_MAX_PKT_SIZE_V6; | |
269 | VRRP_PKT_VCHECK(pktsize >= minsize, | |
bb95fd82 QY |
270 | "VRRP packet is undersized (%zu < %zu)", pktsize, |
271 | minsize); | |
91188ca6 | 272 | VRRP_PKT_VCHECK(pktsize <= maxsize, |
bb95fd82 QY |
273 | "VRRP packet is oversized (%zu > %zu)", pktsize, |
274 | maxsize); | |
d04bb25a | 275 | |
8cb3d803 QY |
276 | /* Version check */ |
277 | uint8_t pktver = (*pkt)->hdr.vertype >> 4; | |
2fff50ec | 278 | |
8cb3d803 QY |
279 | VRRP_PKT_VCHECK(pktver == version, "Bad version %u", pktver); |
280 | ||
d04bb25a | 281 | /* Checksum check */ |
9f2379bd | 282 | uint16_t chksum = vrrp_pkt_checksum(*pkt, pktsize, src, ipv4_ph); |
2fff50ec | 283 | |
d04bb25a | 284 | VRRP_PKT_VCHECK((*pkt)->hdr.chksum == chksum, |
6cde4b45 | 285 | "Bad VRRP checksum %hx; should be %hx", |
d04bb25a QY |
286 | (*pkt)->hdr.chksum, chksum); |
287 | ||
91188ca6 | 288 | /* Type check */ |
0b1321e2 | 289 | VRRP_PKT_VCHECK(((*pkt)->hdr.vertype & 0x0F) == 1, "Bad type %u", |
91188ca6 | 290 | (*pkt)->hdr.vertype & 0x0f); |
8cb3d803 | 291 | |
c2034b25 QY |
292 | /* Exact size check */ |
293 | size_t ves = VRRP_PKT_SIZE(family, pktver, (*pkt)->hdr.naddr); | |
2fff50ec | 294 | |
c2034b25 QY |
295 | VRRP_PKT_VCHECK(pktsize == ves, "Packet has incorrect # addresses%s", |
296 | pktver == 2 ? " or missing auth fields" : ""); | |
91188ca6 | 297 | |
99966840 QY |
298 | /* auth type check */ |
299 | if (version == 2) | |
300 | VRRP_PKT_VCHECK((*pkt)->hdr.v2.auth_type == 0, | |
6cde4b45 | 301 | "Bad authentication type %hhu", |
99966840 QY |
302 | (*pkt)->hdr.v2.auth_type); |
303 | ||
91188ca6 QY |
304 | /* Addresses check */ |
305 | char vbuf[INET6_ADDRSTRLEN]; | |
306 | uint8_t *p = (uint8_t *)(*pkt)->addrs; | |
2fff50ec | 307 | |
91188ca6 QY |
308 | for (uint8_t i = 0; i < (*pkt)->hdr.naddr; i++) { |
309 | VRRP_PKT_VCHECK(inet_ntop(family, p, vbuf, sizeof(vbuf)), | |
6cde4b45 | 310 | "Bad IP address, #%hhu", i); |
91188ca6 QY |
311 | p += addrsz; |
312 | } | |
313 | ||
314 | /* Everything checks out */ | |
315 | return pktsize; | |
316 | } |