Coverage Report

Created: 2026-10-18 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
};