| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Forwarding database |
|---|
| 3 | 4 | * Linux ethernet bridge |
|---|
| 4 | 5 | * |
|---|
| 5 | 6 | * Authors: |
|---|
| 6 | 7 | * Lennert Buytenhek <buytenh@gnu.org> |
|---|
| 7 | | - * |
|---|
| 8 | | - * This program is free software; you can redistribute it and/or |
|---|
| 9 | | - * modify it under the terms of the GNU General Public License |
|---|
| 10 | | - * as published by the Free Software Foundation; either version |
|---|
| 11 | | - * 2 of the License, or (at your option) any later version. |
|---|
| 12 | 8 | */ |
|---|
| 13 | 9 | |
|---|
| 14 | 10 | #include <linux/kernel.h> |
|---|
| .. | .. |
|---|
| 33 | 29 | .key_offset = offsetof(struct net_bridge_fdb_entry, key), |
|---|
| 34 | 30 | .key_len = sizeof(struct net_bridge_fdb_key), |
|---|
| 35 | 31 | .automatic_shrinking = true, |
|---|
| 36 | | - .locks_mul = 1, |
|---|
| 37 | 32 | }; |
|---|
| 38 | 33 | |
|---|
| 39 | 34 | static struct kmem_cache *br_fdb_cache __read_mostly; |
|---|
| .. | .. |
|---|
| 80 | 75 | static inline int has_expired(const struct net_bridge *br, |
|---|
| 81 | 76 | const struct net_bridge_fdb_entry *fdb) |
|---|
| 82 | 77 | { |
|---|
| 83 | | - return !fdb->is_static && !fdb->added_by_external_learn && |
|---|
| 84 | | - time_before_eq(fdb->updated + hold_time(br), jiffies); |
|---|
| 78 | + return !test_bit(BR_FDB_STATIC, &fdb->flags) && |
|---|
| 79 | + !test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags) && |
|---|
| 80 | + time_before_eq(fdb->updated + hold_time(br), jiffies); |
|---|
| 85 | 81 | } |
|---|
| 86 | 82 | |
|---|
| 87 | 83 | static void fdb_rcu_free(struct rcu_head *head) |
|---|
| .. | .. |
|---|
| 202 | 198 | { |
|---|
| 203 | 199 | trace_fdb_delete(br, f); |
|---|
| 204 | 200 | |
|---|
| 205 | | - if (f->is_static) |
|---|
| 201 | + if (test_bit(BR_FDB_STATIC, &f->flags)) |
|---|
| 206 | 202 | fdb_del_hw_addr(br, f->key.addr.addr); |
|---|
| 207 | 203 | |
|---|
| 208 | 204 | hlist_del_init_rcu(&f->fdb_node); |
|---|
| .. | .. |
|---|
| 229 | 225 | if (op != p && ether_addr_equal(op->dev->dev_addr, addr) && |
|---|
| 230 | 226 | (!vid || br_vlan_find(vg, vid))) { |
|---|
| 231 | 227 | f->dst = op; |
|---|
| 232 | | - f->added_by_user = 0; |
|---|
| 228 | + clear_bit(BR_FDB_ADDED_BY_USER, &f->flags); |
|---|
| 233 | 229 | return; |
|---|
| 234 | 230 | } |
|---|
| 235 | 231 | } |
|---|
| .. | .. |
|---|
| 240 | 236 | if (p && ether_addr_equal(br->dev->dev_addr, addr) && |
|---|
| 241 | 237 | (!vid || (v && br_vlan_should_use(v)))) { |
|---|
| 242 | 238 | f->dst = NULL; |
|---|
| 243 | | - f->added_by_user = 0; |
|---|
| 239 | + clear_bit(BR_FDB_ADDED_BY_USER, &f->flags); |
|---|
| 244 | 240 | return; |
|---|
| 245 | 241 | } |
|---|
| 246 | 242 | |
|---|
| .. | .. |
|---|
| 255 | 251 | |
|---|
| 256 | 252 | spin_lock_bh(&br->hash_lock); |
|---|
| 257 | 253 | f = br_fdb_find(br, addr, vid); |
|---|
| 258 | | - if (f && f->is_local && !f->added_by_user && f->dst == p) |
|---|
| 254 | + if (f && test_bit(BR_FDB_LOCAL, &f->flags) && |
|---|
| 255 | + !test_bit(BR_FDB_ADDED_BY_USER, &f->flags) && f->dst == p) |
|---|
| 259 | 256 | fdb_delete_local(br, p, f); |
|---|
| 260 | 257 | spin_unlock_bh(&br->hash_lock); |
|---|
| 261 | 258 | } |
|---|
| .. | .. |
|---|
| 270 | 267 | spin_lock_bh(&br->hash_lock); |
|---|
| 271 | 268 | vg = nbp_vlan_group(p); |
|---|
| 272 | 269 | hlist_for_each_entry(f, &br->fdb_list, fdb_node) { |
|---|
| 273 | | - if (f->dst == p && f->is_local && !f->added_by_user) { |
|---|
| 270 | + if (f->dst == p && test_bit(BR_FDB_LOCAL, &f->flags) && |
|---|
| 271 | + !test_bit(BR_FDB_ADDED_BY_USER, &f->flags)) { |
|---|
| 274 | 272 | /* delete old one */ |
|---|
| 275 | 273 | fdb_delete_local(br, p, f); |
|---|
| 276 | 274 | |
|---|
| .. | .. |
|---|
| 311 | 309 | |
|---|
| 312 | 310 | /* If old entry was unassociated with any port, then delete it. */ |
|---|
| 313 | 311 | f = br_fdb_find(br, br->dev->dev_addr, 0); |
|---|
| 314 | | - if (f && f->is_local && !f->dst && !f->added_by_user) |
|---|
| 312 | + if (f && test_bit(BR_FDB_LOCAL, &f->flags) && |
|---|
| 313 | + !f->dst && !test_bit(BR_FDB_ADDED_BY_USER, &f->flags)) |
|---|
| 315 | 314 | fdb_delete_local(br, NULL, f); |
|---|
| 316 | 315 | |
|---|
| 317 | 316 | fdb_insert(br, NULL, newaddr, 0); |
|---|
| .. | .. |
|---|
| 326 | 325 | if (!br_vlan_should_use(v)) |
|---|
| 327 | 326 | continue; |
|---|
| 328 | 327 | f = br_fdb_find(br, br->dev->dev_addr, v->vid); |
|---|
| 329 | | - if (f && f->is_local && !f->dst && !f->added_by_user) |
|---|
| 328 | + if (f && test_bit(BR_FDB_LOCAL, &f->flags) && |
|---|
| 329 | + !f->dst && !test_bit(BR_FDB_ADDED_BY_USER, &f->flags)) |
|---|
| 330 | 330 | fdb_delete_local(br, NULL, f); |
|---|
| 331 | 331 | fdb_insert(br, NULL, newaddr, v->vid); |
|---|
| 332 | 332 | } |
|---|
| .. | .. |
|---|
| 349 | 349 | */ |
|---|
| 350 | 350 | rcu_read_lock(); |
|---|
| 351 | 351 | hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) { |
|---|
| 352 | | - unsigned long this_timer; |
|---|
| 352 | + unsigned long this_timer = f->updated + delay; |
|---|
| 353 | 353 | |
|---|
| 354 | | - if (f->is_static || f->added_by_external_learn) |
|---|
| 354 | + if (test_bit(BR_FDB_STATIC, &f->flags) || |
|---|
| 355 | + test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &f->flags)) { |
|---|
| 356 | + if (test_bit(BR_FDB_NOTIFY, &f->flags)) { |
|---|
| 357 | + if (time_after(this_timer, now)) |
|---|
| 358 | + work_delay = min(work_delay, |
|---|
| 359 | + this_timer - now); |
|---|
| 360 | + else if (!test_and_set_bit(BR_FDB_NOTIFY_INACTIVE, |
|---|
| 361 | + &f->flags)) |
|---|
| 362 | + fdb_notify(br, f, RTM_NEWNEIGH, false); |
|---|
| 363 | + } |
|---|
| 355 | 364 | continue; |
|---|
| 356 | | - this_timer = f->updated + delay; |
|---|
| 365 | + } |
|---|
| 366 | + |
|---|
| 357 | 367 | if (time_after(this_timer, now)) { |
|---|
| 358 | 368 | work_delay = min(work_delay, this_timer - now); |
|---|
| 359 | 369 | } else { |
|---|
| .. | .. |
|---|
| 378 | 388 | |
|---|
| 379 | 389 | spin_lock_bh(&br->hash_lock); |
|---|
| 380 | 390 | hlist_for_each_entry_safe(f, tmp, &br->fdb_list, fdb_node) { |
|---|
| 381 | | - if (!f->is_static) |
|---|
| 391 | + if (!test_bit(BR_FDB_STATIC, &f->flags)) |
|---|
| 382 | 392 | fdb_delete(br, f, true); |
|---|
| 383 | 393 | } |
|---|
| 384 | 394 | spin_unlock_bh(&br->hash_lock); |
|---|
| .. | .. |
|---|
| 402 | 412 | continue; |
|---|
| 403 | 413 | |
|---|
| 404 | 414 | if (!do_all) |
|---|
| 405 | | - if (f->is_static || (vid && f->key.vlan_id != vid)) |
|---|
| 415 | + if (test_bit(BR_FDB_STATIC, &f->flags) || |
|---|
| 416 | + (test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &f->flags) && |
|---|
| 417 | + !test_bit(BR_FDB_OFFLOADED, &f->flags)) || |
|---|
| 418 | + (vid && f->key.vlan_id != vid)) |
|---|
| 406 | 419 | continue; |
|---|
| 407 | 420 | |
|---|
| 408 | | - if (f->is_local) |
|---|
| 421 | + if (test_bit(BR_FDB_LOCAL, &f->flags)) |
|---|
| 409 | 422 | fdb_delete_local(br, p, f); |
|---|
| 410 | 423 | else |
|---|
| 411 | 424 | fdb_delete(br, f, true); |
|---|
| .. | .. |
|---|
| 474 | 487 | fe->port_no = f->dst->port_no; |
|---|
| 475 | 488 | fe->port_hi = f->dst->port_no >> 8; |
|---|
| 476 | 489 | |
|---|
| 477 | | - fe->is_local = f->is_local; |
|---|
| 478 | | - if (!f->is_static) |
|---|
| 490 | + fe->is_local = test_bit(BR_FDB_LOCAL, &f->flags); |
|---|
| 491 | + if (!test_bit(BR_FDB_STATIC, &f->flags)) |
|---|
| 479 | 492 | fe->ageing_timer_value = jiffies_delta_to_clock_t(jiffies - f->updated); |
|---|
| 480 | 493 | ++fe; |
|---|
| 481 | 494 | ++num; |
|---|
| .. | .. |
|---|
| 489 | 502 | struct net_bridge_port *source, |
|---|
| 490 | 503 | const unsigned char *addr, |
|---|
| 491 | 504 | __u16 vid, |
|---|
| 492 | | - unsigned char is_local, |
|---|
| 493 | | - unsigned char is_static) |
|---|
| 505 | + unsigned long flags) |
|---|
| 494 | 506 | { |
|---|
| 495 | 507 | struct net_bridge_fdb_entry *fdb; |
|---|
| 496 | 508 | |
|---|
| .. | .. |
|---|
| 499 | 511 | memcpy(fdb->key.addr.addr, addr, ETH_ALEN); |
|---|
| 500 | 512 | fdb->dst = source; |
|---|
| 501 | 513 | fdb->key.vlan_id = vid; |
|---|
| 502 | | - fdb->is_local = is_local; |
|---|
| 503 | | - fdb->is_static = is_static; |
|---|
| 504 | | - fdb->added_by_user = 0; |
|---|
| 505 | | - fdb->added_by_external_learn = 0; |
|---|
| 506 | | - fdb->offloaded = 0; |
|---|
| 514 | + fdb->flags = flags; |
|---|
| 507 | 515 | fdb->updated = fdb->used = jiffies; |
|---|
| 508 | 516 | if (rhashtable_lookup_insert_fast(&br->fdb_hash_tbl, |
|---|
| 509 | 517 | &fdb->rhnode, |
|---|
| .. | .. |
|---|
| 530 | 538 | /* it is okay to have multiple ports with same |
|---|
| 531 | 539 | * address, just use the first one. |
|---|
| 532 | 540 | */ |
|---|
| 533 | | - if (fdb->is_local) |
|---|
| 541 | + if (test_bit(BR_FDB_LOCAL, &fdb->flags)) |
|---|
| 534 | 542 | return 0; |
|---|
| 535 | 543 | br_warn(br, "adding interface %s with same address as a received packet (addr:%pM, vlan:%u)\n", |
|---|
| 536 | 544 | source ? source->dev->name : br->dev->name, addr, vid); |
|---|
| 537 | 545 | fdb_delete(br, fdb, true); |
|---|
| 538 | 546 | } |
|---|
| 539 | 547 | |
|---|
| 540 | | - fdb = fdb_create(br, source, addr, vid, 1, 1); |
|---|
| 548 | + fdb = fdb_create(br, source, addr, vid, |
|---|
| 549 | + BIT(BR_FDB_LOCAL) | BIT(BR_FDB_STATIC)); |
|---|
| 541 | 550 | if (!fdb) |
|---|
| 542 | 551 | return -ENOMEM; |
|---|
| 543 | 552 | |
|---|
| .. | .. |
|---|
| 558 | 567 | return ret; |
|---|
| 559 | 568 | } |
|---|
| 560 | 569 | |
|---|
| 570 | +/* returns true if the fdb was modified */ |
|---|
| 571 | +static bool __fdb_mark_active(struct net_bridge_fdb_entry *fdb) |
|---|
| 572 | +{ |
|---|
| 573 | + return !!(test_bit(BR_FDB_NOTIFY_INACTIVE, &fdb->flags) && |
|---|
| 574 | + test_and_clear_bit(BR_FDB_NOTIFY_INACTIVE, &fdb->flags)); |
|---|
| 575 | +} |
|---|
| 576 | + |
|---|
| 561 | 577 | void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, |
|---|
| 562 | | - const unsigned char *addr, u16 vid, bool added_by_user) |
|---|
| 578 | + const unsigned char *addr, u16 vid, unsigned long flags) |
|---|
| 563 | 579 | { |
|---|
| 564 | 580 | struct net_bridge_fdb_entry *fdb; |
|---|
| 565 | | - bool fdb_modified = false; |
|---|
| 566 | 581 | |
|---|
| 567 | 582 | /* some users want to always flood. */ |
|---|
| 568 | 583 | if (hold_time(br) == 0) |
|---|
| 569 | 584 | return; |
|---|
| 570 | 585 | |
|---|
| 571 | | - /* ignore packets unless we are using this port */ |
|---|
| 572 | | - if (!(source->state == BR_STATE_LEARNING || |
|---|
| 573 | | - source->state == BR_STATE_FORWARDING)) |
|---|
| 574 | | - return; |
|---|
| 575 | | - |
|---|
| 576 | 586 | fdb = fdb_find_rcu(&br->fdb_hash_tbl, addr, vid); |
|---|
| 577 | 587 | if (likely(fdb)) { |
|---|
| 578 | 588 | /* attempt to update an entry for a local interface */ |
|---|
| 579 | | - if (unlikely(fdb->is_local)) { |
|---|
| 589 | + if (unlikely(test_bit(BR_FDB_LOCAL, &fdb->flags))) { |
|---|
| 580 | 590 | if (net_ratelimit()) |
|---|
| 581 | 591 | br_warn(br, "received packet on %s with own address as source address (addr:%pM, vlan:%u)\n", |
|---|
| 582 | 592 | source->dev->name, addr, vid); |
|---|
| 583 | 593 | } else { |
|---|
| 584 | 594 | unsigned long now = jiffies; |
|---|
| 595 | + bool fdb_modified = false; |
|---|
| 596 | + |
|---|
| 597 | + if (now != fdb->updated) { |
|---|
| 598 | + fdb->updated = now; |
|---|
| 599 | + fdb_modified = __fdb_mark_active(fdb); |
|---|
| 600 | + } |
|---|
| 585 | 601 | |
|---|
| 586 | 602 | /* fastpath: update of existing entry */ |
|---|
| 587 | | - if (unlikely(source != fdb->dst)) { |
|---|
| 603 | + if (unlikely(source != fdb->dst && |
|---|
| 604 | + !test_bit(BR_FDB_STICKY, &fdb->flags))) { |
|---|
| 588 | 605 | fdb->dst = source; |
|---|
| 589 | 606 | fdb_modified = true; |
|---|
| 590 | 607 | /* Take over HW learned entry */ |
|---|
| 591 | | - if (unlikely(fdb->added_by_external_learn)) |
|---|
| 592 | | - fdb->added_by_external_learn = 0; |
|---|
| 608 | + if (unlikely(test_bit(BR_FDB_ADDED_BY_EXT_LEARN, |
|---|
| 609 | + &fdb->flags))) |
|---|
| 610 | + clear_bit(BR_FDB_ADDED_BY_EXT_LEARN, |
|---|
| 611 | + &fdb->flags); |
|---|
| 593 | 612 | } |
|---|
| 594 | | - if (now != fdb->updated) |
|---|
| 595 | | - fdb->updated = now; |
|---|
| 596 | | - if (unlikely(added_by_user)) |
|---|
| 597 | | - fdb->added_by_user = 1; |
|---|
| 613 | + |
|---|
| 614 | + if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags))) |
|---|
| 615 | + set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags); |
|---|
| 598 | 616 | if (unlikely(fdb_modified)) { |
|---|
| 599 | | - trace_br_fdb_update(br, source, addr, vid, added_by_user); |
|---|
| 617 | + trace_br_fdb_update(br, source, addr, vid, flags); |
|---|
| 600 | 618 | fdb_notify(br, fdb, RTM_NEWNEIGH, true); |
|---|
| 601 | 619 | } |
|---|
| 602 | 620 | } |
|---|
| 603 | 621 | } else { |
|---|
| 604 | 622 | spin_lock(&br->hash_lock); |
|---|
| 605 | | - fdb = fdb_create(br, source, addr, vid, 0, 0); |
|---|
| 623 | + fdb = fdb_create(br, source, addr, vid, flags); |
|---|
| 606 | 624 | if (fdb) { |
|---|
| 607 | | - if (unlikely(added_by_user)) |
|---|
| 608 | | - fdb->added_by_user = 1; |
|---|
| 609 | | - trace_br_fdb_update(br, source, addr, vid, |
|---|
| 610 | | - added_by_user); |
|---|
| 625 | + trace_br_fdb_update(br, source, addr, vid, flags); |
|---|
| 611 | 626 | fdb_notify(br, fdb, RTM_NEWNEIGH, true); |
|---|
| 612 | 627 | } |
|---|
| 613 | 628 | /* else we lose race and someone else inserts |
|---|
| .. | .. |
|---|
| 620 | 635 | static int fdb_to_nud(const struct net_bridge *br, |
|---|
| 621 | 636 | const struct net_bridge_fdb_entry *fdb) |
|---|
| 622 | 637 | { |
|---|
| 623 | | - if (fdb->is_local) |
|---|
| 638 | + if (test_bit(BR_FDB_LOCAL, &fdb->flags)) |
|---|
| 624 | 639 | return NUD_PERMANENT; |
|---|
| 625 | | - else if (fdb->is_static) |
|---|
| 640 | + else if (test_bit(BR_FDB_STATIC, &fdb->flags)) |
|---|
| 626 | 641 | return NUD_NOARP; |
|---|
| 627 | 642 | else if (has_expired(br, fdb)) |
|---|
| 628 | 643 | return NUD_STALE; |
|---|
| .. | .. |
|---|
| 652 | 667 | ndm->ndm_ifindex = fdb->dst ? fdb->dst->dev->ifindex : br->dev->ifindex; |
|---|
| 653 | 668 | ndm->ndm_state = fdb_to_nud(br, fdb); |
|---|
| 654 | 669 | |
|---|
| 655 | | - if (fdb->offloaded) |
|---|
| 670 | + if (test_bit(BR_FDB_OFFLOADED, &fdb->flags)) |
|---|
| 656 | 671 | ndm->ndm_flags |= NTF_OFFLOADED; |
|---|
| 657 | | - if (fdb->added_by_external_learn) |
|---|
| 672 | + if (test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags)) |
|---|
| 658 | 673 | ndm->ndm_flags |= NTF_EXT_LEARNED; |
|---|
| 674 | + if (test_bit(BR_FDB_STICKY, &fdb->flags)) |
|---|
| 675 | + ndm->ndm_flags |= NTF_STICKY; |
|---|
| 659 | 676 | |
|---|
| 660 | 677 | if (nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->key.addr)) |
|---|
| 661 | 678 | goto nla_put_failure; |
|---|
| .. | .. |
|---|
| 672 | 689 | &fdb->key.vlan_id)) |
|---|
| 673 | 690 | goto nla_put_failure; |
|---|
| 674 | 691 | |
|---|
| 692 | + if (test_bit(BR_FDB_NOTIFY, &fdb->flags)) { |
|---|
| 693 | + struct nlattr *nest = nla_nest_start(skb, NDA_FDB_EXT_ATTRS); |
|---|
| 694 | + u8 notify_bits = FDB_NOTIFY_BIT; |
|---|
| 695 | + |
|---|
| 696 | + if (!nest) |
|---|
| 697 | + goto nla_put_failure; |
|---|
| 698 | + if (test_bit(BR_FDB_NOTIFY_INACTIVE, &fdb->flags)) |
|---|
| 699 | + notify_bits |= FDB_NOTIFY_INACTIVE_BIT; |
|---|
| 700 | + |
|---|
| 701 | + if (nla_put_u8(skb, NFEA_ACTIVITY_NOTIFY, notify_bits)) { |
|---|
| 702 | + nla_nest_cancel(skb, nest); |
|---|
| 703 | + goto nla_put_failure; |
|---|
| 704 | + } |
|---|
| 705 | + |
|---|
| 706 | + nla_nest_end(skb, nest); |
|---|
| 707 | + } |
|---|
| 708 | + |
|---|
| 675 | 709 | nlmsg_end(skb, nlh); |
|---|
| 676 | 710 | return 0; |
|---|
| 677 | 711 | |
|---|
| .. | .. |
|---|
| 686 | 720 | + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ |
|---|
| 687 | 721 | + nla_total_size(sizeof(u32)) /* NDA_MASTER */ |
|---|
| 688 | 722 | + nla_total_size(sizeof(u16)) /* NDA_VLAN */ |
|---|
| 689 | | - + nla_total_size(sizeof(struct nda_cacheinfo)); |
|---|
| 723 | + + nla_total_size(sizeof(struct nda_cacheinfo)) |
|---|
| 724 | + + nla_total_size(0) /* NDA_FDB_EXT_ATTRS */ |
|---|
| 725 | + + nla_total_size(sizeof(u8)); /* NFEA_ACTIVITY_NOTIFY */ |
|---|
| 690 | 726 | } |
|---|
| 691 | 727 | |
|---|
| 692 | 728 | static void fdb_notify(struct net_bridge *br, |
|---|
| .. | .. |
|---|
| 770 | 806 | return err; |
|---|
| 771 | 807 | } |
|---|
| 772 | 808 | |
|---|
| 809 | +int br_fdb_get(struct sk_buff *skb, |
|---|
| 810 | + struct nlattr *tb[], |
|---|
| 811 | + struct net_device *dev, |
|---|
| 812 | + const unsigned char *addr, |
|---|
| 813 | + u16 vid, u32 portid, u32 seq, |
|---|
| 814 | + struct netlink_ext_ack *extack) |
|---|
| 815 | +{ |
|---|
| 816 | + struct net_bridge *br = netdev_priv(dev); |
|---|
| 817 | + struct net_bridge_fdb_entry *f; |
|---|
| 818 | + int err = 0; |
|---|
| 819 | + |
|---|
| 820 | + rcu_read_lock(); |
|---|
| 821 | + f = br_fdb_find_rcu(br, addr, vid); |
|---|
| 822 | + if (!f) { |
|---|
| 823 | + NL_SET_ERR_MSG(extack, "Fdb entry not found"); |
|---|
| 824 | + err = -ENOENT; |
|---|
| 825 | + goto errout; |
|---|
| 826 | + } |
|---|
| 827 | + |
|---|
| 828 | + err = fdb_fill_info(skb, br, f, portid, seq, |
|---|
| 829 | + RTM_NEWNEIGH, 0); |
|---|
| 830 | +errout: |
|---|
| 831 | + rcu_read_unlock(); |
|---|
| 832 | + return err; |
|---|
| 833 | +} |
|---|
| 834 | + |
|---|
| 835 | +/* returns true if the fdb is modified */ |
|---|
| 836 | +static bool fdb_handle_notify(struct net_bridge_fdb_entry *fdb, u8 notify) |
|---|
| 837 | +{ |
|---|
| 838 | + bool modified = false; |
|---|
| 839 | + |
|---|
| 840 | + /* allow to mark an entry as inactive, usually done on creation */ |
|---|
| 841 | + if ((notify & FDB_NOTIFY_INACTIVE_BIT) && |
|---|
| 842 | + !test_and_set_bit(BR_FDB_NOTIFY_INACTIVE, &fdb->flags)) |
|---|
| 843 | + modified = true; |
|---|
| 844 | + |
|---|
| 845 | + if ((notify & FDB_NOTIFY_BIT) && |
|---|
| 846 | + !test_and_set_bit(BR_FDB_NOTIFY, &fdb->flags)) { |
|---|
| 847 | + /* enabled activity tracking */ |
|---|
| 848 | + modified = true; |
|---|
| 849 | + } else if (!(notify & FDB_NOTIFY_BIT) && |
|---|
| 850 | + test_and_clear_bit(BR_FDB_NOTIFY, &fdb->flags)) { |
|---|
| 851 | + /* disabled activity tracking, clear notify state */ |
|---|
| 852 | + clear_bit(BR_FDB_NOTIFY_INACTIVE, &fdb->flags); |
|---|
| 853 | + modified = true; |
|---|
| 854 | + } |
|---|
| 855 | + |
|---|
| 856 | + return modified; |
|---|
| 857 | +} |
|---|
| 858 | + |
|---|
| 773 | 859 | /* Update (create or replace) forwarding database entry */ |
|---|
| 774 | 860 | static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, |
|---|
| 775 | | - const __u8 *addr, __u16 state, __u16 flags, __u16 vid) |
|---|
| 861 | + const u8 *addr, struct ndmsg *ndm, u16 flags, u16 vid, |
|---|
| 862 | + struct nlattr *nfea_tb[]) |
|---|
| 776 | 863 | { |
|---|
| 864 | + bool is_sticky = !!(ndm->ndm_flags & NTF_STICKY); |
|---|
| 865 | + bool refresh = !nfea_tb[NFEA_DONT_REFRESH]; |
|---|
| 777 | 866 | struct net_bridge_fdb_entry *fdb; |
|---|
| 867 | + u16 state = ndm->ndm_state; |
|---|
| 778 | 868 | bool modified = false; |
|---|
| 869 | + u8 notify = 0; |
|---|
| 779 | 870 | |
|---|
| 780 | 871 | /* If the port cannot learn allow only local and static entries */ |
|---|
| 781 | 872 | if (source && !(state & NUD_PERMANENT) && !(state & NUD_NOARP) && |
|---|
| .. | .. |
|---|
| 789 | 880 | return -EINVAL; |
|---|
| 790 | 881 | } |
|---|
| 791 | 882 | |
|---|
| 883 | + if (is_sticky && (state & NUD_PERMANENT)) |
|---|
| 884 | + return -EINVAL; |
|---|
| 885 | + |
|---|
| 886 | + if (nfea_tb[NFEA_ACTIVITY_NOTIFY]) { |
|---|
| 887 | + notify = nla_get_u8(nfea_tb[NFEA_ACTIVITY_NOTIFY]); |
|---|
| 888 | + if ((notify & ~BR_FDB_NOTIFY_SETTABLE_BITS) || |
|---|
| 889 | + (notify & BR_FDB_NOTIFY_SETTABLE_BITS) == FDB_NOTIFY_INACTIVE_BIT) |
|---|
| 890 | + return -EINVAL; |
|---|
| 891 | + } |
|---|
| 892 | + |
|---|
| 792 | 893 | fdb = br_fdb_find(br, addr, vid); |
|---|
| 793 | 894 | if (fdb == NULL) { |
|---|
| 794 | 895 | if (!(flags & NLM_F_CREATE)) |
|---|
| 795 | 896 | return -ENOENT; |
|---|
| 796 | 897 | |
|---|
| 797 | | - fdb = fdb_create(br, source, addr, vid, 0, 0); |
|---|
| 898 | + fdb = fdb_create(br, source, addr, vid, 0); |
|---|
| 798 | 899 | if (!fdb) |
|---|
| 799 | 900 | return -ENOMEM; |
|---|
| 800 | 901 | |
|---|
| .. | .. |
|---|
| 811 | 912 | |
|---|
| 812 | 913 | if (fdb_to_nud(br, fdb) != state) { |
|---|
| 813 | 914 | if (state & NUD_PERMANENT) { |
|---|
| 814 | | - fdb->is_local = 1; |
|---|
| 815 | | - if (!fdb->is_static) { |
|---|
| 816 | | - fdb->is_static = 1; |
|---|
| 915 | + set_bit(BR_FDB_LOCAL, &fdb->flags); |
|---|
| 916 | + if (!test_and_set_bit(BR_FDB_STATIC, &fdb->flags)) |
|---|
| 817 | 917 | fdb_add_hw_addr(br, addr); |
|---|
| 818 | | - } |
|---|
| 819 | 918 | } else if (state & NUD_NOARP) { |
|---|
| 820 | | - fdb->is_local = 0; |
|---|
| 821 | | - if (!fdb->is_static) { |
|---|
| 822 | | - fdb->is_static = 1; |
|---|
| 919 | + clear_bit(BR_FDB_LOCAL, &fdb->flags); |
|---|
| 920 | + if (!test_and_set_bit(BR_FDB_STATIC, &fdb->flags)) |
|---|
| 823 | 921 | fdb_add_hw_addr(br, addr); |
|---|
| 824 | | - } |
|---|
| 825 | 922 | } else { |
|---|
| 826 | | - fdb->is_local = 0; |
|---|
| 827 | | - if (fdb->is_static) { |
|---|
| 828 | | - fdb->is_static = 0; |
|---|
| 923 | + clear_bit(BR_FDB_LOCAL, &fdb->flags); |
|---|
| 924 | + if (test_and_clear_bit(BR_FDB_STATIC, &fdb->flags)) |
|---|
| 829 | 925 | fdb_del_hw_addr(br, addr); |
|---|
| 830 | | - } |
|---|
| 831 | 926 | } |
|---|
| 832 | 927 | |
|---|
| 833 | 928 | modified = true; |
|---|
| 834 | 929 | } |
|---|
| 835 | | - fdb->added_by_user = 1; |
|---|
| 930 | + |
|---|
| 931 | + if (is_sticky != test_bit(BR_FDB_STICKY, &fdb->flags)) { |
|---|
| 932 | + change_bit(BR_FDB_STICKY, &fdb->flags); |
|---|
| 933 | + modified = true; |
|---|
| 934 | + } |
|---|
| 935 | + |
|---|
| 936 | + if (fdb_handle_notify(fdb, notify)) |
|---|
| 937 | + modified = true; |
|---|
| 938 | + |
|---|
| 939 | + set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags); |
|---|
| 836 | 940 | |
|---|
| 837 | 941 | fdb->used = jiffies; |
|---|
| 838 | 942 | if (modified) { |
|---|
| 839 | | - fdb->updated = jiffies; |
|---|
| 943 | + if (refresh) |
|---|
| 944 | + fdb->updated = jiffies; |
|---|
| 840 | 945 | fdb_notify(br, fdb, RTM_NEWNEIGH, true); |
|---|
| 841 | 946 | } |
|---|
| 842 | 947 | |
|---|
| .. | .. |
|---|
| 845 | 950 | |
|---|
| 846 | 951 | static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge *br, |
|---|
| 847 | 952 | struct net_bridge_port *p, const unsigned char *addr, |
|---|
| 848 | | - u16 nlh_flags, u16 vid) |
|---|
| 953 | + u16 nlh_flags, u16 vid, struct nlattr *nfea_tb[], |
|---|
| 954 | + struct netlink_ext_ack *extack) |
|---|
| 849 | 955 | { |
|---|
| 850 | 956 | int err = 0; |
|---|
| 851 | 957 | |
|---|
| .. | .. |
|---|
| 855 | 961 | br->dev->name); |
|---|
| 856 | 962 | return -EINVAL; |
|---|
| 857 | 963 | } |
|---|
| 964 | + if (!nbp_state_should_learn(p)) |
|---|
| 965 | + return 0; |
|---|
| 966 | + |
|---|
| 858 | 967 | local_bh_disable(); |
|---|
| 859 | 968 | rcu_read_lock(); |
|---|
| 860 | | - br_fdb_update(br, p, addr, vid, true); |
|---|
| 969 | + br_fdb_update(br, p, addr, vid, BIT(BR_FDB_ADDED_BY_USER)); |
|---|
| 861 | 970 | rcu_read_unlock(); |
|---|
| 862 | 971 | local_bh_enable(); |
|---|
| 863 | 972 | } else if (ndm->ndm_flags & NTF_EXT_LEARNED) { |
|---|
| 973 | + if (!p && !(ndm->ndm_state & NUD_PERMANENT)) { |
|---|
| 974 | + NL_SET_ERR_MSG_MOD(extack, |
|---|
| 975 | + "FDB entry towards bridge must be permanent"); |
|---|
| 976 | + return -EINVAL; |
|---|
| 977 | + } |
|---|
| 864 | 978 | err = br_fdb_external_learn_add(br, p, addr, vid, true); |
|---|
| 865 | 979 | } else { |
|---|
| 866 | 980 | spin_lock_bh(&br->hash_lock); |
|---|
| 867 | | - err = fdb_add_entry(br, p, addr, ndm->ndm_state, |
|---|
| 868 | | - nlh_flags, vid); |
|---|
| 981 | + err = fdb_add_entry(br, p, addr, ndm, nlh_flags, vid, nfea_tb); |
|---|
| 869 | 982 | spin_unlock_bh(&br->hash_lock); |
|---|
| 870 | 983 | } |
|---|
| 871 | 984 | |
|---|
| 872 | 985 | return err; |
|---|
| 873 | 986 | } |
|---|
| 874 | 987 | |
|---|
| 988 | +static const struct nla_policy br_nda_fdb_pol[NFEA_MAX + 1] = { |
|---|
| 989 | + [NFEA_ACTIVITY_NOTIFY] = { .type = NLA_U8 }, |
|---|
| 990 | + [NFEA_DONT_REFRESH] = { .type = NLA_FLAG }, |
|---|
| 991 | +}; |
|---|
| 992 | + |
|---|
| 875 | 993 | /* Add new permanent fdb entry with RTM_NEWNEIGH */ |
|---|
| 876 | 994 | int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], |
|---|
| 877 | 995 | struct net_device *dev, |
|---|
| 878 | | - const unsigned char *addr, u16 vid, u16 nlh_flags) |
|---|
| 996 | + const unsigned char *addr, u16 vid, u16 nlh_flags, |
|---|
| 997 | + struct netlink_ext_ack *extack) |
|---|
| 879 | 998 | { |
|---|
| 999 | + struct nlattr *nfea_tb[NFEA_MAX + 1], *attr; |
|---|
| 880 | 1000 | struct net_bridge_vlan_group *vg; |
|---|
| 881 | 1001 | struct net_bridge_port *p = NULL; |
|---|
| 882 | 1002 | struct net_bridge_vlan *v; |
|---|
| .. | .. |
|---|
| 909 | 1029 | vg = nbp_vlan_group(p); |
|---|
| 910 | 1030 | } |
|---|
| 911 | 1031 | |
|---|
| 1032 | + if (tb[NDA_FDB_EXT_ATTRS]) { |
|---|
| 1033 | + attr = tb[NDA_FDB_EXT_ATTRS]; |
|---|
| 1034 | + err = nla_parse_nested(nfea_tb, NFEA_MAX, attr, |
|---|
| 1035 | + br_nda_fdb_pol, extack); |
|---|
| 1036 | + if (err) |
|---|
| 1037 | + return err; |
|---|
| 1038 | + } else { |
|---|
| 1039 | + memset(nfea_tb, 0, sizeof(struct nlattr *) * (NFEA_MAX + 1)); |
|---|
| 1040 | + } |
|---|
| 1041 | + |
|---|
| 912 | 1042 | if (vid) { |
|---|
| 913 | 1043 | v = br_vlan_find(vg, vid); |
|---|
| 914 | 1044 | if (!v || !br_vlan_should_use(v)) { |
|---|
| .. | .. |
|---|
| 917 | 1047 | } |
|---|
| 918 | 1048 | |
|---|
| 919 | 1049 | /* VID was specified, so use it. */ |
|---|
| 920 | | - err = __br_fdb_add(ndm, br, p, addr, nlh_flags, vid); |
|---|
| 1050 | + err = __br_fdb_add(ndm, br, p, addr, nlh_flags, vid, nfea_tb, |
|---|
| 1051 | + extack); |
|---|
| 921 | 1052 | } else { |
|---|
| 922 | | - err = __br_fdb_add(ndm, br, p, addr, nlh_flags, 0); |
|---|
| 1053 | + err = __br_fdb_add(ndm, br, p, addr, nlh_flags, 0, nfea_tb, |
|---|
| 1054 | + extack); |
|---|
| 923 | 1055 | if (err || !vg || !vg->num_vlans) |
|---|
| 924 | 1056 | goto out; |
|---|
| 925 | 1057 | |
|---|
| .. | .. |
|---|
| 930 | 1062 | list_for_each_entry(v, &vg->vlan_list, vlist) { |
|---|
| 931 | 1063 | if (!br_vlan_should_use(v)) |
|---|
| 932 | 1064 | continue; |
|---|
| 933 | | - err = __br_fdb_add(ndm, br, p, addr, nlh_flags, v->vid); |
|---|
| 1065 | + err = __br_fdb_add(ndm, br, p, addr, nlh_flags, v->vid, |
|---|
| 1066 | + nfea_tb, extack); |
|---|
| 934 | 1067 | if (err) |
|---|
| 935 | 1068 | goto out; |
|---|
| 936 | 1069 | } |
|---|
| .. | .. |
|---|
| 1028 | 1161 | rcu_read_lock(); |
|---|
| 1029 | 1162 | hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) { |
|---|
| 1030 | 1163 | /* We only care for static entries */ |
|---|
| 1031 | | - if (!f->is_static) |
|---|
| 1164 | + if (!test_bit(BR_FDB_STATIC, &f->flags)) |
|---|
| 1032 | 1165 | continue; |
|---|
| 1033 | 1166 | err = dev_uc_add(p->dev, f->key.addr.addr); |
|---|
| 1034 | 1167 | if (err) |
|---|
| .. | .. |
|---|
| 1042 | 1175 | rollback: |
|---|
| 1043 | 1176 | hlist_for_each_entry_rcu(tmp, &br->fdb_list, fdb_node) { |
|---|
| 1044 | 1177 | /* We only care for static entries */ |
|---|
| 1045 | | - if (!tmp->is_static) |
|---|
| 1178 | + if (!test_bit(BR_FDB_STATIC, &tmp->flags)) |
|---|
| 1046 | 1179 | continue; |
|---|
| 1047 | 1180 | if (tmp == f) |
|---|
| 1048 | 1181 | break; |
|---|
| .. | .. |
|---|
| 1061 | 1194 | rcu_read_lock(); |
|---|
| 1062 | 1195 | hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) { |
|---|
| 1063 | 1196 | /* We only care for static entries */ |
|---|
| 1064 | | - if (!f->is_static) |
|---|
| 1197 | + if (!test_bit(BR_FDB_STATIC, &f->flags)) |
|---|
| 1065 | 1198 | continue; |
|---|
| 1066 | 1199 | |
|---|
| 1067 | 1200 | dev_uc_del(p->dev, f->key.addr.addr); |
|---|
| .. | .. |
|---|
| 1083 | 1216 | |
|---|
| 1084 | 1217 | fdb = br_fdb_find(br, addr, vid); |
|---|
| 1085 | 1218 | if (!fdb) { |
|---|
| 1086 | | - fdb = fdb_create(br, p, addr, vid, 0, 0); |
|---|
| 1219 | + unsigned long flags = BIT(BR_FDB_ADDED_BY_EXT_LEARN); |
|---|
| 1220 | + |
|---|
| 1221 | + if (swdev_notify) |
|---|
| 1222 | + flags |= BIT(BR_FDB_ADDED_BY_USER); |
|---|
| 1223 | + |
|---|
| 1224 | + if (!p) |
|---|
| 1225 | + flags |= BIT(BR_FDB_LOCAL); |
|---|
| 1226 | + |
|---|
| 1227 | + fdb = fdb_create(br, p, addr, vid, flags); |
|---|
| 1087 | 1228 | if (!fdb) { |
|---|
| 1088 | 1229 | err = -ENOMEM; |
|---|
| 1089 | 1230 | goto err_unlock; |
|---|
| 1090 | 1231 | } |
|---|
| 1091 | | - if (swdev_notify) |
|---|
| 1092 | | - fdb->added_by_user = 1; |
|---|
| 1093 | | - fdb->added_by_external_learn = 1; |
|---|
| 1094 | 1232 | fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify); |
|---|
| 1095 | 1233 | } else { |
|---|
| 1096 | 1234 | fdb->updated = jiffies; |
|---|
| .. | .. |
|---|
| 1100 | 1238 | modified = true; |
|---|
| 1101 | 1239 | } |
|---|
| 1102 | 1240 | |
|---|
| 1103 | | - if (fdb->added_by_external_learn) { |
|---|
| 1241 | + if (test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags)) { |
|---|
| 1104 | 1242 | /* Refresh entry */ |
|---|
| 1105 | 1243 | fdb->used = jiffies; |
|---|
| 1106 | | - } else if (!fdb->added_by_user) { |
|---|
| 1244 | + } else if (!test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags)) { |
|---|
| 1107 | 1245 | /* Take over SW learned entry */ |
|---|
| 1108 | | - fdb->added_by_external_learn = 1; |
|---|
| 1246 | + set_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags); |
|---|
| 1109 | 1247 | modified = true; |
|---|
| 1110 | 1248 | } |
|---|
| 1111 | 1249 | |
|---|
| 1112 | 1250 | if (swdev_notify) |
|---|
| 1113 | | - fdb->added_by_user = 1; |
|---|
| 1251 | + set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags); |
|---|
| 1252 | + |
|---|
| 1253 | + if (!p) |
|---|
| 1254 | + set_bit(BR_FDB_LOCAL, &fdb->flags); |
|---|
| 1114 | 1255 | |
|---|
| 1115 | 1256 | if (modified) |
|---|
| 1116 | 1257 | fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify); |
|---|
| .. | .. |
|---|
| 1132 | 1273 | spin_lock_bh(&br->hash_lock); |
|---|
| 1133 | 1274 | |
|---|
| 1134 | 1275 | fdb = br_fdb_find(br, addr, vid); |
|---|
| 1135 | | - if (fdb && fdb->added_by_external_learn) |
|---|
| 1276 | + if (fdb && test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags)) |
|---|
| 1136 | 1277 | fdb_delete(br, fdb, swdev_notify); |
|---|
| 1137 | 1278 | else |
|---|
| 1138 | 1279 | err = -ENOENT; |
|---|
| .. | .. |
|---|
| 1143 | 1284 | } |
|---|
| 1144 | 1285 | |
|---|
| 1145 | 1286 | void br_fdb_offloaded_set(struct net_bridge *br, struct net_bridge_port *p, |
|---|
| 1146 | | - const unsigned char *addr, u16 vid) |
|---|
| 1287 | + const unsigned char *addr, u16 vid, bool offloaded) |
|---|
| 1147 | 1288 | { |
|---|
| 1148 | 1289 | struct net_bridge_fdb_entry *fdb; |
|---|
| 1149 | 1290 | |
|---|
| 1150 | 1291 | spin_lock_bh(&br->hash_lock); |
|---|
| 1151 | 1292 | |
|---|
| 1152 | 1293 | fdb = br_fdb_find(br, addr, vid); |
|---|
| 1153 | | - if (fdb) |
|---|
| 1154 | | - fdb->offloaded = 1; |
|---|
| 1294 | + if (fdb && offloaded != test_bit(BR_FDB_OFFLOADED, &fdb->flags)) |
|---|
| 1295 | + change_bit(BR_FDB_OFFLOADED, &fdb->flags); |
|---|
| 1155 | 1296 | |
|---|
| 1156 | 1297 | spin_unlock_bh(&br->hash_lock); |
|---|
| 1157 | 1298 | } |
|---|
| 1299 | + |
|---|
| 1300 | +void br_fdb_clear_offload(const struct net_device *dev, u16 vid) |
|---|
| 1301 | +{ |
|---|
| 1302 | + struct net_bridge_fdb_entry *f; |
|---|
| 1303 | + struct net_bridge_port *p; |
|---|
| 1304 | + |
|---|
| 1305 | + ASSERT_RTNL(); |
|---|
| 1306 | + |
|---|
| 1307 | + p = br_port_get_rtnl(dev); |
|---|
| 1308 | + if (!p) |
|---|
| 1309 | + return; |
|---|
| 1310 | + |
|---|
| 1311 | + spin_lock_bh(&p->br->hash_lock); |
|---|
| 1312 | + hlist_for_each_entry(f, &p->br->fdb_list, fdb_node) { |
|---|
| 1313 | + if (f->dst == p && f->key.vlan_id == vid) |
|---|
| 1314 | + clear_bit(BR_FDB_OFFLOADED, &f->flags); |
|---|
| 1315 | + } |
|---|
| 1316 | + spin_unlock_bh(&p->br->hash_lock); |
|---|
| 1317 | +} |
|---|
| 1318 | +EXPORT_SYMBOL_GPL(br_fdb_clear_offload); |
|---|