Coverage Report

Created: 2026-04-12 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openvswitch/lib/tnl-neigh-cache.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2014, 2015, 2016 Nicira, Inc.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at:
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include <config.h>
18
19
#include "tnl-neigh-cache.h"
20
21
#include <inttypes.h>
22
#include <sys/types.h>
23
#include <netinet/in.h>
24
#include <netinet/icmp6.h>
25
#include <stdlib.h>
26
27
#include "bitmap.h"
28
#include "cmap.h"
29
#include "coverage.h"
30
#include "dpif-netdev.h"
31
#include "openvswitch/dynamic-string.h"
32
#include "errno.h"
33
#include "flow.h"
34
#include "netdev.h"
35
#include "ovs-atomic.h"
36
#include "ovs-thread.h"
37
#include "packets.h"
38
#include "openvswitch/poll-loop.h"
39
#include "seq.h"
40
#include "socket-util.h"
41
#include "timeval.h"
42
#include "unaligned.h"
43
#include "unixctl.h"
44
#include "util.h"
45
#include "openvswitch/vlog.h"
46
47
48
#define NEIGH_ENTRY_DEFAULT_IDLE_TIME_MS (15 * 60 * 1000)
49
0
#define NEIGH_ENTRY_MAX_AGING_TIME_S     3600
50
#define NEIGH_ENTRY_LOOKUP_RETRANS_TIME  1000
51
52
struct tnl_neigh_entry {
53
    struct cmap_node cmap_node;
54
    struct in6_addr ip;
55
    struct eth_addr mac;
56
    atomic_llong expires;       /* Expiration time in ms. */
57
    char br_name[IFNAMSIZ];
58
    atomic_bool complete;
59
};
60
61
static struct cmap table = CMAP_INITIALIZER;
62
static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER;
63
static atomic_uint32_t neigh_aging;
64
static atomic_uint32_t neigh_retrans_time;
65
66
static uint32_t
67
tnl_neigh_hash(const struct in6_addr *ip)
68
0
{
69
0
    return hash_bytes(ip->s6_addr, 16, 0);
70
0
}
71
72
static bool
73
tnl_neigh_expired(struct tnl_neigh_entry *neigh)
74
0
{
75
0
    long long expires;
76
77
0
    atomic_read_explicit(&neigh->expires, &expires, memory_order_acquire);
78
79
0
    return expires <= time_msec();
80
0
}
81
82
static uint32_t
83
tnl_neigh_get_aging(void)
84
0
{
85
0
    unsigned int aging;
86
87
0
    atomic_read_explicit(&neigh_aging, &aging, memory_order_acquire);
88
0
    return aging;
89
0
}
90
91
static uint32_t
92
tnl_neigh_get_retrans_time(void)
93
0
{
94
0
    unsigned int retrans_time;
95
96
0
    atomic_read_explicit(&neigh_retrans_time, &retrans_time,
97
0
                         memory_order_acquire);
98
0
    return retrans_time;
99
0
}
100
101
static bool
102
tnl_neigh_is_complete(struct tnl_neigh_entry *neigh)
103
0
{
104
0
    bool complete;
105
106
0
    atomic_read_explicit(&neigh->complete, &complete, memory_order_acquire);
107
0
    return complete;
108
0
}
109
110
static struct tnl_neigh_entry *
111
tnl_neigh_lookup__(const char br_name[IFNAMSIZ], const struct in6_addr *dst)
112
0
{
113
0
    struct tnl_neigh_entry *neigh;
114
0
    uint32_t hash;
115
116
0
    hash = tnl_neigh_hash(dst);
117
0
    CMAP_FOR_EACH_WITH_HASH (neigh, cmap_node, hash, &table) {
118
0
        if (ipv6_addr_equals(&neigh->ip, dst) && !strcmp(neigh->br_name, br_name)) {
119
0
            if (tnl_neigh_expired(neigh)) {
120
0
                return NULL;
121
0
            }
122
123
0
            if (tnl_neigh_is_complete(neigh)) {
124
0
                atomic_store_explicit(&neigh->expires,
125
0
                                      time_msec() + tnl_neigh_get_aging(),
126
0
                                      memory_order_release);
127
0
            }
128
129
0
            return neigh;
130
0
        }
131
0
    }
132
0
    return NULL;
133
0
}
134
135
static void
136
tnl_neigh_set_partial(const char name[IFNAMSIZ], const struct in6_addr *dst)
137
0
{
138
0
    ovs_mutex_lock(&mutex);
139
0
    struct tnl_neigh_entry *neigh = tnl_neigh_lookup__(name, dst);
140
0
    if (neigh) {
141
        /* Entry inserted before lock taken. */
142
143
0
        ovs_mutex_unlock(&mutex);
144
0
        return;
145
0
    }
146
0
    neigh = xmalloc(sizeof *neigh);
147
148
0
    neigh->ip = *dst;
149
0
    atomic_store_relaxed(&neigh->complete, false);
150
0
    atomic_store_relaxed(&neigh->expires,
151
0
                         time_msec() + tnl_neigh_get_retrans_time());
152
0
    ovs_strlcpy(neigh->br_name, name, sizeof neigh->br_name);
153
0
    cmap_insert(&table, &neigh->cmap_node, tnl_neigh_hash(&neigh->ip));
154
155
0
    ovs_mutex_unlock(&mutex);
156
0
    seq_change(tnl_conf_seq);
157
0
}
158
159
int
160
tnl_neigh_lookup(const char br_name[IFNAMSIZ], const struct in6_addr *dst,
161
                 struct eth_addr *mac, bool insert_partial)
162
0
{
163
0
    struct tnl_neigh_entry *neigh;
164
0
    int res = ENOENT;
165
166
0
    neigh = tnl_neigh_lookup__(br_name, dst);
167
0
    if (neigh) {
168
0
        if (tnl_neigh_is_complete(neigh)) {
169
0
            *mac = neigh->mac;
170
0
            res = 0;
171
0
        } else {
172
0
            res = EINPROGRESS;
173
0
        }
174
0
    } else if (insert_partial && tnl_neigh_get_retrans_time()) {
175
        /* Insert a partial entry only if there is a retransmit timer set. */
176
0
        tnl_neigh_set_partial(br_name, dst);
177
0
    }
178
179
0
    return res;
180
0
}
181
182
static void
183
neigh_entry_free(struct tnl_neigh_entry *neigh)
184
0
{
185
0
    free(neigh);
186
0
}
187
188
static void
189
tnl_neigh_delete(struct tnl_neigh_entry *neigh)
190
0
{
191
0
    uint32_t hash = tnl_neigh_hash(&neigh->ip);
192
0
    cmap_remove(&table, &neigh->cmap_node, hash);
193
0
    ovsrcu_postpone(neigh_entry_free, neigh);
194
0
}
195
196
void
197
tnl_neigh_set(const char name[IFNAMSIZ], const struct in6_addr *dst,
198
              const struct eth_addr mac)
199
0
{
200
0
    ovs_mutex_lock(&mutex);
201
0
    struct tnl_neigh_entry *neigh = tnl_neigh_lookup__(name, dst);
202
0
    bool insert = true;
203
204
0
    if (neigh) {
205
0
        if (!tnl_neigh_is_complete(neigh)) {
206
0
            insert = false;
207
0
        } else if (eth_addr_equals(neigh->mac, mac)) {
208
0
            atomic_store_relaxed(&neigh->expires,
209
0
                                 time_msec() + tnl_neigh_get_aging());
210
0
            ovs_mutex_unlock(&mutex);
211
0
            return;
212
0
        } else {
213
0
            tnl_neigh_delete(neigh);
214
0
        }
215
0
    }
216
217
0
    if (insert) {
218
0
        neigh = xmalloc(sizeof *neigh);
219
220
0
        neigh->ip = *dst;
221
0
        ovs_strlcpy(neigh->br_name, name, sizeof neigh->br_name);
222
0
    }
223
224
0
    neigh->mac = mac;
225
0
    atomic_store_explicit(&neigh->expires,
226
0
                          time_msec() + tnl_neigh_get_aging(),
227
0
                          memory_order_release);
228
0
    atomic_store_explicit(&neigh->complete, true, memory_order_release);
229
230
0
    if (insert) {
231
0
        cmap_insert(&table, &neigh->cmap_node, tnl_neigh_hash(&neigh->ip));
232
0
    }
233
234
0
    ovs_mutex_unlock(&mutex);
235
0
    seq_change(tnl_conf_seq);
236
0
}
237
238
static void
239
tnl_arp_set(const char name[IFNAMSIZ], ovs_be32 dst,
240
            const struct eth_addr mac)
241
0
{
242
0
    struct in6_addr dst6 = in6_addr_mapped_ipv4(dst);
243
0
    tnl_neigh_set(name, &dst6, mac);
244
0
}
245
246
static int
247
tnl_arp_snoop(const struct flow *flow, struct flow_wildcards *wc,
248
              const char name[IFNAMSIZ], bool allow_update)
249
0
{
250
    /* Snoop normal ARP replies and gratuitous ARP requests/replies only */
251
0
    if (!is_arp(flow)
252
0
        || (!is_garp(flow, wc) &&
253
0
            FLOW_WC_GET_AND_MASK_WC(flow, wc, nw_proto) != ARP_OP_REPLY)
254
0
        || eth_addr_is_zero(FLOW_WC_GET_AND_MASK_WC(flow, wc, arp_sha))) {
255
0
        return EINVAL;
256
0
    }
257
258
0
    memset(&wc->masks.nw_src, 0xff, sizeof wc->masks.nw_src);
259
260
0
    if (allow_update) {
261
0
        tnl_arp_set(name, flow->nw_src, flow->arp_sha);
262
0
    }
263
0
    return 0;
264
0
}
265
266
static int
267
tnl_nd_snoop(const struct flow *flow, struct flow_wildcards *wc,
268
             const char name[IFNAMSIZ], bool allow_update)
269
0
{
270
0
    if (!is_nd(flow, wc) || flow->tp_src != htons(ND_NEIGHBOR_ADVERT)) {
271
0
        return EINVAL;
272
0
    }
273
    /* - RFC4861 says Neighbor Advertisements sent in response to unicast Neighbor
274
     *   Solicitations SHOULD include the Target link-layer address. However, Linux
275
     *   doesn't. So, the response to Solicitations sent by OVS will include the
276
     *   TLL address and other Advertisements not including it can be ignored.
277
     * - OVS flow extract can set this field to zero in case of packet parsing errors.
278
     *   For details refer miniflow_extract()*/
279
0
    if (eth_addr_is_zero(FLOW_WC_GET_AND_MASK_WC(flow, wc, arp_tha))) {
280
0
        return EINVAL;
281
0
    }
282
283
0
    memset(&wc->masks.ipv6_src, 0xff, sizeof wc->masks.ipv6_src);
284
0
    memset(&wc->masks.ipv6_dst, 0xff, sizeof wc->masks.ipv6_dst);
285
0
    memset(&wc->masks.nd_target, 0xff, sizeof wc->masks.nd_target);
286
287
0
    if (allow_update) {
288
0
        tnl_neigh_set(name, &flow->nd_target, flow->arp_tha);
289
0
    }
290
0
    return 0;
291
0
}
292
293
int
294
tnl_neigh_snoop(const struct flow *flow, struct flow_wildcards *wc,
295
                const char name[IFNAMSIZ], bool allow_update)
296
0
{
297
0
    int res;
298
0
    res = tnl_arp_snoop(flow, wc, name, allow_update);
299
0
    if (res != EINVAL) {
300
0
        return res;
301
0
    }
302
0
    return tnl_nd_snoop(flow, wc, name, allow_update);
303
0
}
304
305
void
306
tnl_neigh_cache_run(void)
307
0
{
308
0
    struct tnl_neigh_entry *neigh;
309
0
    bool changed = false;
310
311
0
    ovs_mutex_lock(&mutex);
312
0
    CMAP_FOR_EACH(neigh, cmap_node, &table) {
313
0
        if (tnl_neigh_expired(neigh)) {
314
0
            tnl_neigh_delete(neigh);
315
0
            changed = true;
316
0
        }
317
0
    }
318
0
    ovs_mutex_unlock(&mutex);
319
320
0
    if (changed) {
321
0
        seq_change(tnl_conf_seq);
322
0
    }
323
0
}
324
325
void
326
tnl_neigh_flush(const char br_name[IFNAMSIZ])
327
0
{
328
0
    struct tnl_neigh_entry *neigh;
329
0
    bool changed = false;
330
331
0
    ovs_mutex_lock(&mutex);
332
0
    CMAP_FOR_EACH (neigh, cmap_node, &table) {
333
0
        if (!strcmp(neigh->br_name, br_name)) {
334
0
            tnl_neigh_delete(neigh);
335
0
            changed = true;
336
0
        }
337
0
    }
338
0
    ovs_mutex_unlock(&mutex);
339
340
0
    if (changed) {
341
0
        seq_change(tnl_conf_seq);
342
0
    }
343
0
}
344
345
static void
346
tnl_neigh_cache_flush(struct unixctl_conn *conn, int argc OVS_UNUSED,
347
                    const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
348
0
{
349
0
    struct tnl_neigh_entry *neigh;
350
0
    bool changed = false;
351
352
0
    ovs_mutex_lock(&mutex);
353
0
    CMAP_FOR_EACH(neigh, cmap_node, &table) {
354
0
        tnl_neigh_delete(neigh);
355
0
        changed = true;
356
0
    }
357
0
    ovs_mutex_unlock(&mutex);
358
0
    if (changed) {
359
0
        seq_change(tnl_conf_seq);
360
0
    }
361
0
    unixctl_command_reply(conn, "OK");
362
0
}
363
364
static void
365
tnl_neigh_cache_aging(struct unixctl_conn *conn, int argc,
366
                        const char *argv[], void *aux OVS_UNUSED)
367
0
{
368
0
    long long int new_exp, curr_exp;
369
0
    struct tnl_neigh_entry *neigh;
370
0
    uint32_t aging;
371
372
0
    if (argc == 1) {
373
0
        struct ds ds = DS_EMPTY_INITIALIZER;
374
0
        ds_put_format(&ds, "%"PRIu32, tnl_neigh_get_aging() / 1000);
375
0
        unixctl_command_reply(conn, ds_cstr(&ds));
376
0
        ds_destroy(&ds);
377
378
0
        return;
379
0
    }
380
381
0
    if (!ovs_scan(argv[1], "%"SCNu32, &aging) ||
382
0
        !aging || aging > NEIGH_ENTRY_MAX_AGING_TIME_S) {
383
0
        unixctl_command_reply_error(conn, "bad aging value");
384
0
        return;
385
0
    }
386
387
0
    aging *= 1000;
388
389
0
    if (aging < tnl_neigh_get_retrans_time()) {
390
0
        unixctl_command_reply_error(conn, "aging value cannot be less than "
391
0
                                          "retrans_time");
392
0
        return;
393
0
    }
394
395
0
    atomic_store_explicit(&neigh_aging, aging, memory_order_release);
396
0
    new_exp = time_msec() + aging;
397
398
0
    CMAP_FOR_EACH (neigh, cmap_node, &table) {
399
0
        atomic_read_explicit(&neigh->expires, &curr_exp,
400
0
                             memory_order_acquire);
401
0
        if (new_exp < curr_exp) {
402
0
            atomic_store_explicit(&neigh->expires, new_exp,
403
0
                                  memory_order_release);
404
0
        }
405
0
    }
406
407
0
    unixctl_command_reply(conn, "OK");
408
0
}
409
410
static void
411
tnl_neigh_cache_retrans_time(struct unixctl_conn *conn, int argc,
412
                             const char *argv[], void *aux OVS_UNUSED)
413
0
{
414
0
    long long int new_exp, curr_exp;
415
0
    struct tnl_neigh_entry *neigh;
416
0
    uint32_t retrans_time;
417
418
0
    if (argc == 1) {
419
0
        struct ds ds = DS_EMPTY_INITIALIZER;
420
0
        ds_put_format(&ds, "%"PRIu32, tnl_neigh_get_retrans_time());
421
0
        unixctl_command_reply(conn, ds_cstr(&ds));
422
0
        ds_destroy(&ds);
423
424
0
        return;
425
0
    }
426
427
    /* Zero retransmit value is acceptable. */
428
0
    if (!ovs_scan(argv[1], "%"SCNu32, &retrans_time)) {
429
0
        unixctl_command_reply_error(conn, "bad retrans_time value");
430
0
        return;
431
0
    } else if (retrans_time > tnl_neigh_get_aging()) {
432
0
        unixctl_command_reply_error(conn, "retrans_time value cannot be "
433
0
                                          "greater than aging");
434
0
        return;
435
0
    }
436
437
0
    atomic_store_explicit(&neigh_retrans_time, retrans_time,
438
0
                          memory_order_release);
439
0
    new_exp = time_msec() + retrans_time;
440
441
0
    CMAP_FOR_EACH (neigh, cmap_node, &table) {
442
0
        if (tnl_neigh_is_complete(neigh)) {
443
0
            continue;
444
0
        }
445
0
        atomic_read_explicit(&neigh->expires, &curr_exp,
446
0
                             memory_order_acquire);
447
0
        if (new_exp < curr_exp) {
448
0
            atomic_store_explicit(&neigh->expires, new_exp,
449
0
                                  memory_order_release);
450
0
        }
451
0
    }
452
453
0
    unixctl_command_reply(conn, "OK");
454
0
}
455
456
static int
457
lookup_any(const char *host_name, struct in6_addr *address)
458
0
{
459
0
    if (addr_is_ipv6(host_name)) {
460
0
        return lookup_ipv6(host_name, address);
461
0
    } else {
462
0
        int r;
463
0
        struct in_addr ip;
464
0
        r = lookup_ip(host_name, &ip);
465
0
        if (r == 0) {
466
0
            in6_addr_set_mapped_ipv4(address, ip.s_addr);
467
0
        }
468
0
        return r;
469
0
    }
470
0
    return ENOENT;
471
0
}
472
473
static void
474
tnl_neigh_cache_add(struct unixctl_conn *conn, int argc OVS_UNUSED,
475
                    const char *argv[], void *aux OVS_UNUSED)
476
0
{
477
0
    const char *br_name = argv[1];
478
0
    struct eth_addr mac;
479
0
    struct in6_addr ip6;
480
481
0
    if (lookup_any(argv[2], &ip6) != 0) {
482
0
        unixctl_command_reply_error(conn, "bad IP address");
483
0
        return;
484
0
    }
485
486
0
    if (!eth_addr_from_string(argv[3], &mac)) {
487
0
        unixctl_command_reply_error(conn, "bad MAC address");
488
0
        return;
489
0
    }
490
491
0
    tnl_neigh_set(br_name, &ip6, mac);
492
0
    unixctl_command_reply(conn, "OK");
493
0
}
494
495
static void
496
tnl_neigh_cache_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
497
                     const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
498
0
{
499
0
    struct ds ds = DS_EMPTY_INITIALIZER;
500
0
    struct tnl_neigh_entry *neigh;
501
502
0
    ds_put_cstr(&ds, "IP                                            MAC                 Bridge\n");
503
0
    ds_put_cstr(&ds, "==========================================================================\n");
504
0
    ovs_mutex_lock(&mutex);
505
0
    CMAP_FOR_EACH(neigh, cmap_node, &table) {
506
0
        int start_len, need_ws;
507
508
0
        start_len = ds.length;
509
0
        ipv6_format_mapped(&neigh->ip, &ds);
510
511
0
        need_ws = INET6_ADDRSTRLEN - (ds.length - start_len);
512
0
        ds_put_char_multiple(&ds, ' ', need_ws);
513
514
0
        if (tnl_neigh_is_complete(neigh)) {
515
0
            ds_put_format(&ds, ETH_ADDR_FMT"   %s",
516
0
                          ETH_ADDR_ARGS(neigh->mac), neigh->br_name);
517
0
        } else {
518
0
            ds_put_format(&ds, "                    %s INCOMPLETE",
519
0
                          neigh->br_name);
520
0
        }
521
0
        if (tnl_neigh_expired(neigh)) {
522
0
            ds_put_format(&ds, " STALE");
523
0
        }
524
0
        ds_put_char(&ds, '\n');
525
526
0
    }
527
0
    ovs_mutex_unlock(&mutex);
528
0
    unixctl_command_reply(conn, ds_cstr(&ds));
529
0
    ds_destroy(&ds);
530
0
}
531
532
void
533
tnl_neigh_cache_init(void)
534
0
{
535
0
    atomic_init(&neigh_aging, NEIGH_ENTRY_DEFAULT_IDLE_TIME_MS);
536
0
    atomic_init(&neigh_retrans_time, NEIGH_ENTRY_LOOKUP_RETRANS_TIME);
537
0
    unixctl_command_register("tnl/arp/show", "", 0, 0,
538
0
                             tnl_neigh_cache_show, NULL);
539
0
    unixctl_command_register("tnl/arp/set", "BRIDGE IP MAC", 3, 3,
540
0
                             tnl_neigh_cache_add, NULL);
541
0
    unixctl_command_register("tnl/arp/flush", "", 0, 0,
542
0
                             tnl_neigh_cache_flush, NULL);
543
0
    unixctl_command_register("tnl/arp/aging", "[SECS]", 0, 1,
544
0
                             tnl_neigh_cache_aging, NULL);
545
0
    unixctl_command_register("tnl/neigh/show", "", 0, 0,
546
0
                             tnl_neigh_cache_show, NULL);
547
0
    unixctl_command_register("tnl/neigh/set", "BRIDGE IP MAC", 3, 3,
548
0
                             tnl_neigh_cache_add, NULL);
549
0
    unixctl_command_register("tnl/neigh/flush", "", 0, 0,
550
0
                             tnl_neigh_cache_flush, NULL);
551
0
    unixctl_command_register("tnl/neigh/aging", "[SECS]", 0, 1,
552
0
                             tnl_neigh_cache_aging, NULL);
553
0
    unixctl_command_register("tnl/neigh/retrans_time", "[MSECS]", 0, 1,
554
                             tnl_neigh_cache_retrans_time, NULL);
555
0
}