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