/src/freeradius-server/src/protocols/arp/base.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 7778cc7ff60b50a31fa735f3a0042e6d2414cf36 $ |
19 | | * |
20 | | * @file protocols/arp/base.c |
21 | | * @brief Functions to send/receive ARP packets. |
22 | | * |
23 | | * @copyright 2020 Network RADIUS SAS <legal@networkradius.com? |
24 | | */ |
25 | | |
26 | | RCSID("$Id: 7778cc7ff60b50a31fa735f3a0042e6d2414cf36 $") |
27 | | |
28 | | #include <freeradius-devel/util/net.h> |
29 | | #include <freeradius-devel/util/struct.h> |
30 | | #include <freeradius-devel/util/syserror.h> |
31 | | #include <freeradius-devel/io/test_point.h> |
32 | | #include "attrs.h" |
33 | | |
34 | | #ifdef HAVE_LINUX_IF_PACKET_H |
35 | | # include <linux/if_packet.h> |
36 | | # include <linux/if_ether.h> |
37 | | #endif |
38 | | |
39 | | #include <net/if_arp.h> |
40 | | |
41 | | #ifdef __FreeBSD__ |
42 | | #include <net/ethernet.h> |
43 | | #include <net/if_dl.h> |
44 | | #include <netlink/netlink.h> |
45 | | #include <netlink/netlink_route.h> |
46 | | #include <netlink/netlink_snl.h> |
47 | | #include <netlink/netlink_snl_route.h> |
48 | | #include <sys/param.h> |
49 | | #include <sys/linker.h> |
50 | | #include <sys/module.h> |
51 | | #endif |
52 | | |
53 | | static uint32_t instance_count = 0; |
54 | | |
55 | | fr_dict_t const *dict_arp; |
56 | | |
57 | | extern fr_dict_autoload_t libfreeradius_arp_dict[]; |
58 | | fr_dict_autoload_t libfreeradius_arp_dict[] = { |
59 | | { .out = &dict_arp, .proto = "arp" }, |
60 | | DICT_AUTOLOAD_TERMINATOR |
61 | | }; |
62 | | |
63 | | fr_dict_attr_t const *attr_arp_packet; |
64 | | |
65 | | extern fr_dict_attr_autoload_t libfreeradius_arp_dict_attr[]; |
66 | | fr_dict_attr_autoload_t libfreeradius_arp_dict_attr[] = { |
67 | | { .out = &attr_arp_packet, .name = "arp", .type = FR_TYPE_STRUCT, .dict = &dict_arp }, |
68 | | DICT_AUTOLOAD_TERMINATOR |
69 | | }; |
70 | | |
71 | | |
72 | | /* |
73 | | * grep VALUE share/dictionary/arp/dictionary.rfc826 | grep Op | awk '{print "[" $4 "] = \"" $3 "\","}' |
74 | | */ |
75 | | char const *fr_arp_packet_codes[FR_ARP_CODE_MAX] = { |
76 | | [1] = "Request", |
77 | | [2] = "Reply", |
78 | | [3] = "Reverse-Request", |
79 | | [4] = "Reverse-Reply", |
80 | | #if 0 |
81 | | [5] = "DRARP-Request", |
82 | | [6] = "DRARP-Reply", |
83 | | [7] = "DRARP-Error", |
84 | | [8] = "InARP-Request", |
85 | | [9] = "InARP-Reply", |
86 | | [10] = "ARP-NAK", |
87 | | [11] = "MARS-Request", |
88 | | [12] = "MARS-Multi", |
89 | | [13] = "MARS-MServ", |
90 | | [14] = "MARS-Join", |
91 | | [15] = "MARS-Leave", |
92 | | [16] = "MARS-NAK", |
93 | | [17] = "MARS-Unserv", |
94 | | [18] = "MARS-SJoin", |
95 | | [19] = "MARS-SLeave", |
96 | | [20] = "MARS-Grouplist-Request", |
97 | | [21] = "MARS-Grouplist-Reply", |
98 | | [22] = "MARS-Redirect-MAP", |
99 | | [23] = "MAPOS-UNARP", |
100 | | [24] = "OP_EXP1", |
101 | | [25] = "OP_EXP2", |
102 | | #endif |
103 | | }; |
104 | | |
105 | | static uint8_t const zeros[6] = { 0 }; |
106 | | |
107 | | #ifdef SIOCSARP |
108 | | /** Forcibly add an ARP entry so we can send unicast packets to hosts that don't have IP addresses yet |
109 | | * |
110 | | * @param[in] fd to add arp entry on. |
111 | | * @param[in] interface to add arp entry on. |
112 | | * @param[in] ipaddr to insert into ARP table. |
113 | | * @param[in] macaddr to insert into ARP table. |
114 | | * @return |
115 | | * - 0 on success. |
116 | | * - -1 on failure. |
117 | | */ |
118 | | int fr_arp_entry_add(int fd, char const *interface, uint8_t ipaddr[static 4], uint8_t macaddr[static 6]) |
119 | 0 | { |
120 | 0 | struct sockaddr_in *sin; |
121 | 0 | struct arpreq req; |
122 | |
|
123 | 0 | if (!interface) { |
124 | 0 | fr_strerror_const("No interface specified. Cannot update ARP table"); |
125 | 0 | return -1; |
126 | 0 | } |
127 | | |
128 | 0 | memset(&req, 0, sizeof(req)); |
129 | 0 | sin = (struct sockaddr_in *) &req.arp_pa; |
130 | 0 | sin->sin_family = AF_INET; |
131 | 0 | memcpy(&sin->sin_addr.s_addr, ipaddr, 4); |
132 | |
|
133 | 0 | strlcpy(req.arp_dev, interface, sizeof(req.arp_dev)); |
134 | |
|
135 | 0 | memcpy(&req.arp_ha.sa_data, macaddr, 6); |
136 | |
|
137 | 0 | req.arp_flags = ATF_COM; |
138 | 0 | if (ioctl(fd, SIOCSARP, &req) < 0) { |
139 | 0 | fr_strerror_printf("Failed to add entry in ARP cache: %s (%d)", fr_syserror(errno), errno); |
140 | 0 | return -1; |
141 | 0 | } |
142 | | |
143 | 0 | return 0; |
144 | 0 | } |
145 | | #else |
146 | | int fr_arp_entry_add(UNUSED int fd, UNUSED char const *interface, |
147 | | UNUSED uint8_t ipaddr[static 4], UNUSED uint8_t macaddr[static 6]) |
148 | | { |
149 | | fr_strerror_const("Adding ARP entry is unsupported on this system"); |
150 | | return -1; |
151 | | } |
152 | | #endif |
153 | | |
154 | | #ifdef __FreeBSD__ |
155 | | /** Use FreeBSD netlink API to add ARP entries |
156 | | */ |
157 | | int fr_bsd_arp_entry_add(uint32_t ifindex, uint8_t ipaddr[static 4], uint8_t macaddr[static 6]) |
158 | | { |
159 | | struct sockaddr_in sin; |
160 | | struct sockaddr_dl sdl; |
161 | | struct snl_state state; |
162 | | struct snl_writer nw; |
163 | | struct nlmsghdr *msghdr; |
164 | | struct ndmsg *msg; |
165 | | struct snl_errmsg_data errmsg = {}; |
166 | | |
167 | | if (ifindex == 0) { |
168 | | fr_strerror_const("Missing interface index"); |
169 | | return -1; |
170 | | } |
171 | | |
172 | | sin.sin_family = AF_INET; |
173 | | memcpy(&sin.sin_addr.s_addr, ipaddr, 4); |
174 | | |
175 | | sdl = (struct sockaddr_dl){ |
176 | | .sdl_len = sizeof(sdl), |
177 | | .sdl_family = AF_LINK, |
178 | | .sdl_alen = ETHER_ADDR_LEN |
179 | | }; |
180 | | memcpy(LLADDR(&sdl), macaddr, ETHER_ADDR_LEN); |
181 | | |
182 | | if (!snl_init(&state, NETLINK_ROUTE)) { |
183 | | if (modfind("netlink") == -1 && errno == ENOENT) { |
184 | | if (kldload("netlink") == -1) { |
185 | | fr_strerror_const("netlink is not loaded and load attempt failed"); |
186 | | return -1; |
187 | | } |
188 | | } else { |
189 | | open_error: |
190 | | fr_strerror_const("Unable to open netlink socket"); |
191 | | return -1; |
192 | | } |
193 | | if (!snl_init(&state, NETLINK_ROUTE)) goto open_error; |
194 | | } |
195 | | |
196 | | snl_init_writer(&state, &nw); |
197 | | msghdr = snl_create_msg_request(&nw, RTM_NEWNEIGH); |
198 | | msghdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; |
199 | | |
200 | | msg = snl_reserve_msg_object(&nw, struct ndmsg); |
201 | | if (!msg) { |
202 | | fr_strerror_const("Failed reserving message"); |
203 | | error: |
204 | | snl_free(&state); |
205 | | return -1; |
206 | | } |
207 | | msg->ndm_family = AF_INET; |
208 | | msg->ndm_ifindex = ifindex; |
209 | | msg->ndm_state = NUD_PERMANENT; |
210 | | msg->ndm_flags = NTF_STICKY; |
211 | | |
212 | | snl_add_msg_attr_ip(&nw, NDA_DST, (struct sockaddr *)&sin); |
213 | | snl_add_msg_attr(&nw, NDA_LLADDR, sdl.sdl_alen, LLADDR(&sdl)); |
214 | | |
215 | | if (!(msghdr = snl_finalize_msg(&nw)) || !snl_send_message(&state, msghdr)) { |
216 | | fr_strerror_const("Failed sending netlink message."); |
217 | | goto error; |
218 | | } |
219 | | |
220 | | snl_read_reply_code(&state, msghdr->nlmsg_seq, &errmsg); |
221 | | if (errmsg.error != 0) { |
222 | | fr_strerror_printf("Failed adding ARP table entry: %s (%s)", strerror(errmsg.error), errmsg.error_str); |
223 | | goto error; |
224 | | } |
225 | | |
226 | | snl_free(&state); |
227 | | |
228 | | return 0; |
229 | | } |
230 | | #endif |
231 | | |
232 | | /** Encode VPS into a raw ARP packet. |
233 | | * |
234 | | */ |
235 | | ssize_t fr_arp_encode(fr_dbuff_t *dbuff, uint8_t const *original, fr_pair_list_t *vps) |
236 | 0 | { |
237 | 0 | ssize_t slen; |
238 | 0 | fr_pair_t *vp; |
239 | 0 | fr_arp_packet_t *arp; |
240 | 0 | fr_dcursor_t cursor; |
241 | 0 | fr_da_stack_t da_stack; |
242 | 0 | fr_dbuff_t work_dbuff = FR_DBUFF(dbuff); |
243 | |
|
244 | 0 | if (fr_pair_list_empty(vps)) { |
245 | 0 | fr_strerror_const("Cannot encode empty packet"); |
246 | 0 | return -1; |
247 | 0 | } |
248 | | |
249 | | /* |
250 | | * Get a cursor over the ARP attributes. |
251 | | */ |
252 | 0 | vp = fr_pair_dcursor_by_ancestor_init(&cursor, vps, attr_arp_packet); |
253 | | |
254 | | /* |
255 | | * For simplicity, we allow the caller to omit things |
256 | | * that they don't care about. |
257 | | */ |
258 | 0 | if (!vp) { |
259 | 0 | fr_strerror_const("No ARP attributes in the attribute list"); |
260 | 0 | return -1; |
261 | 0 | } |
262 | | |
263 | 0 | fr_proto_da_stack_build(&da_stack, attr_arp_packet); |
264 | 0 | FR_PROTO_STACK_PRINT(&da_stack, 0); |
265 | | |
266 | | /* |
267 | | * Call the struct encoder to do the actual work. |
268 | | */ |
269 | 0 | slen = fr_struct_to_network(&work_dbuff, &da_stack, 0, &cursor, NULL, NULL, NULL); |
270 | 0 | if (slen <= 0) return slen; |
271 | | |
272 | 0 | if (slen != FR_ARP_PACKET_SIZE) return slen; |
273 | | |
274 | | /* |
275 | | * Hard-code fields which can be omitted. |
276 | | */ |
277 | 0 | arp = (fr_arp_packet_t *)fr_dbuff_start(&work_dbuff); |
278 | |
|
279 | 0 | if ((arp->htype[0] == 0) && (arp->htype[1] == 0)) arp->htype[1] = FR_HARDWARE_FORMAT_VALUE_ETHERNET; |
280 | 0 | if ((arp->ptype[0] == 0) && (arp->ptype[1] == 0)) arp->ptype[0] = (FR_PROTOCOL_FORMAT_VALUE_IPV4 >> 8); /* 0x0800 */ |
281 | 0 | if (arp->hlen == 0) arp->hlen = 6; |
282 | 0 | if (arp->plen == 0) arp->plen = 4; |
283 | | |
284 | | /* |
285 | | * The reply generally swaps Sender / Target addresses, |
286 | | * BUT fills out the Sender-Hardware-Address with the |
287 | | * queried MAC address. Ensure that the admin doesn't |
288 | | * have to fill out all of the fields. |
289 | | */ |
290 | 0 | if (original) { |
291 | 0 | fr_arp_packet_t const *our_original = (fr_arp_packet_t const *) original; |
292 | |
|
293 | 0 | #define COPY(_a, _b) do { \ |
294 | 0 | if (memcmp(arp->_a, zeros, sizeof(arp->_a)) == 0) { \ |
295 | 0 | memcpy(arp->_a, our_original->_b, sizeof(arp->_a)); \ |
296 | 0 | } \ |
297 | 0 | } while (0) |
298 | |
|
299 | 0 | COPY(spa, tpa); /* answer is about the asked-for target */ |
300 | 0 | COPY(tha, sha); /* answer is sent to the requestor hardware address */ |
301 | 0 | COPY(tpa, spa); /* answer is sent to the requestor protocol address */ |
302 | 0 | } |
303 | |
|
304 | 0 | return fr_dbuff_set(dbuff, &work_dbuff); |
305 | 0 | } |
306 | | |
307 | | /** Decode a raw ARP packet into VPs |
308 | | * |
309 | | */ |
310 | | ssize_t fr_arp_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *packet, size_t packet_len) |
311 | 82 | { |
312 | 82 | fr_arp_packet_t const *arp; |
313 | | |
314 | 82 | if (packet_len < FR_ARP_PACKET_SIZE) { |
315 | 10 | fr_strerror_printf("Packet is too small (%d) to be ARP", (int) packet_len); |
316 | 10 | return -1; |
317 | 10 | } |
318 | | |
319 | | /* |
320 | | * Check that the fields have the desired values. |
321 | | */ |
322 | 72 | arp = (fr_arp_packet_t const *) packet; |
323 | 72 | if ((arp->htype[0] != 0) || (arp->htype[1] != 1)) { |
324 | 22 | fr_strerror_const("Hardware-Format != Ethernet"); |
325 | 22 | return -1; |
326 | 22 | } |
327 | | |
328 | 50 | if ((arp->ptype[0] != 8) || (arp->ptype[1] != 0)) { |
329 | 20 | fr_strerror_const("Protocol-Format != IPv4"); |
330 | 20 | return -1; |
331 | 20 | } |
332 | | |
333 | 30 | if (arp->hlen != 6) { |
334 | 13 | fr_strerror_const("Hardware-Length != 6"); |
335 | 13 | return -1; |
336 | 13 | } |
337 | | |
338 | 17 | if (arp->plen != 4) { |
339 | 15 | fr_strerror_const("Protocol-Length != 4"); |
340 | 15 | return -1; |
341 | 15 | } |
342 | | |
343 | | /* |
344 | | * If the packet is too long, we discard any extra data. |
345 | | */ |
346 | 2 | return fr_struct_from_network(ctx, out, attr_arp_packet, packet, FR_ARP_PACKET_SIZE, |
347 | 2 | NULL, NULL, NULL); |
348 | 17 | } |
349 | | |
350 | | int fr_arp_global_init(void) |
351 | 4 | { |
352 | 4 | if (instance_count > 0) { |
353 | 2 | instance_count++; |
354 | 2 | return 0; |
355 | 2 | } |
356 | | |
357 | 2 | instance_count++; |
358 | | |
359 | 2 | if (fr_dict_autoload(libfreeradius_arp_dict) < 0) { |
360 | 0 | fail: |
361 | 0 | instance_count--; |
362 | 0 | return -1; |
363 | 0 | } |
364 | | |
365 | 2 | if (fr_dict_attr_autoload(libfreeradius_arp_dict_attr) < 0) { |
366 | 0 | fr_dict_autofree(libfreeradius_arp_dict); |
367 | 0 | goto fail; |
368 | 0 | } |
369 | | |
370 | 2 | return 0; |
371 | 2 | } |
372 | | |
373 | | void fr_arp_global_free(void) |
374 | 4 | { |
375 | 4 | fr_assert(instance_count > 0); |
376 | | |
377 | 4 | if (--instance_count > 0) return; |
378 | | |
379 | 2 | fr_dict_autofree(libfreeradius_arp_dict); |
380 | 2 | } |
381 | | |
382 | | extern fr_dict_protocol_t libfreeradius_arp_dict_protocol; |
383 | | fr_dict_protocol_t libfreeradius_arp_dict_protocol = { |
384 | | .name = "arp", |
385 | | .default_type_size = 4, |
386 | | .default_type_length = 0, |
387 | | |
388 | | .init = fr_arp_global_init, |
389 | | .free = fr_arp_global_free, |
390 | | }; |
391 | | |
392 | | |
393 | | typedef struct { |
394 | | bool tmp; |
395 | | } fr_arp_ctx_t; |
396 | | |
397 | | static int encode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict, |
398 | | UNUSED fr_dict_attr_t const *root_da) |
399 | 82 | { |
400 | 82 | fr_arp_ctx_t *test_ctx; |
401 | | |
402 | 82 | fr_assert(instance_count > 0); |
403 | | |
404 | 82 | test_ctx = talloc_zero(ctx, fr_arp_ctx_t); |
405 | 82 | if (!test_ctx) return -1; |
406 | | |
407 | 82 | *out = test_ctx; |
408 | | |
409 | 82 | return 0; |
410 | 82 | } |
411 | | |
412 | | |
413 | | /* |
414 | | * Test points for protocol encode / decode |
415 | | * |
416 | | * Because ARP has no TLVs, we don't have test points for pair |
417 | | * encode / decode. |
418 | | */ |
419 | | static ssize_t fr_arp_encode_proto(UNUSED TALLOC_CTX *ctx, fr_pair_list_t *vps, uint8_t *data, size_t data_len, UNUSED void *proto_ctx) |
420 | 0 | { |
421 | 0 | return fr_arp_encode(&FR_DBUFF_TMP(data, data_len), NULL, vps); |
422 | 0 | } |
423 | | |
424 | | extern fr_test_point_proto_encode_t arp_tp_encode_proto; |
425 | | fr_test_point_proto_encode_t arp_tp_encode_proto = { |
426 | | .test_ctx = encode_test_ctx, |
427 | | .func = fr_arp_encode_proto |
428 | | }; |
429 | | |
430 | | static ssize_t fr_arp_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, |
431 | | uint8_t const *data, size_t data_len, UNUSED void *proto_ctx) |
432 | 82 | { |
433 | 82 | return fr_arp_decode(ctx, out, data, data_len); |
434 | 82 | } |
435 | | |
436 | | extern fr_test_point_proto_decode_t arp_tp_decode_proto; |
437 | | fr_test_point_proto_decode_t arp_tp_decode_proto = { |
438 | | .test_ctx = encode_test_ctx, |
439 | | .func = fr_arp_decode_proto |
440 | | }; |