diff options
Diffstat (limited to 'net/bridge')
-rw-r--r-- | net/bridge/br_fdb.c | 148 | ||||
-rw-r--r-- | net/bridge/br_private.h | 6 |
2 files changed, 134 insertions, 20 deletions
diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c index 276a52254606..4b75ad43aa85 100644 --- a/net/bridge/br_fdb.c +++ b/net/bridge/br_fdb.c | |||
@@ -505,6 +505,10 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br, | |||
505 | ci.ndm_refcnt = 0; | 505 | ci.ndm_refcnt = 0; |
506 | if (nla_put(skb, NDA_CACHEINFO, sizeof(ci), &ci)) | 506 | if (nla_put(skb, NDA_CACHEINFO, sizeof(ci), &ci)) |
507 | goto nla_put_failure; | 507 | goto nla_put_failure; |
508 | |||
509 | if (nla_put(skb, NDA_VLAN, sizeof(u16), &fdb->vlan_id)) | ||
510 | goto nla_put_failure; | ||
511 | |||
508 | return nlmsg_end(skb, nlh); | 512 | return nlmsg_end(skb, nlh); |
509 | 513 | ||
510 | nla_put_failure: | 514 | nla_put_failure: |
@@ -516,6 +520,7 @@ static inline size_t fdb_nlmsg_size(void) | |||
516 | { | 520 | { |
517 | return NLMSG_ALIGN(sizeof(struct ndmsg)) | 521 | return NLMSG_ALIGN(sizeof(struct ndmsg)) |
518 | + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ | 522 | + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ |
523 | + nla_total_size(sizeof(u16)) /* NDA_VLAN */ | ||
519 | + nla_total_size(sizeof(struct nda_cacheinfo)); | 524 | + nla_total_size(sizeof(struct nda_cacheinfo)); |
520 | } | 525 | } |
521 | 526 | ||
@@ -617,6 +622,25 @@ static int fdb_add_entry(struct net_bridge_port *source, const __u8 *addr, | |||
617 | return 0; | 622 | return 0; |
618 | } | 623 | } |
619 | 624 | ||
625 | static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge_port *p, | ||
626 | const unsigned char *addr, u16 nlh_flags, u16 vid) | ||
627 | { | ||
628 | int err = 0; | ||
629 | |||
630 | if (ndm->ndm_flags & NTF_USE) { | ||
631 | rcu_read_lock(); | ||
632 | br_fdb_update(p->br, p, addr, vid); | ||
633 | rcu_read_unlock(); | ||
634 | } else { | ||
635 | spin_lock_bh(&p->br->hash_lock); | ||
636 | err = fdb_add_entry(p, addr, ndm->ndm_state, | ||
637 | nlh_flags, vid); | ||
638 | spin_unlock_bh(&p->br->hash_lock); | ||
639 | } | ||
640 | |||
641 | return err; | ||
642 | } | ||
643 | |||
620 | /* Add new permanent fdb entry with RTM_NEWNEIGH */ | 644 | /* Add new permanent fdb entry with RTM_NEWNEIGH */ |
621 | int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], | 645 | int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], |
622 | struct net_device *dev, | 646 | struct net_device *dev, |
@@ -624,12 +648,29 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], | |||
624 | { | 648 | { |
625 | struct net_bridge_port *p; | 649 | struct net_bridge_port *p; |
626 | int err = 0; | 650 | int err = 0; |
651 | struct net_port_vlans *pv; | ||
652 | unsigned short vid = VLAN_N_VID; | ||
627 | 653 | ||
628 | if (!(ndm->ndm_state & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE))) { | 654 | if (!(ndm->ndm_state & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE))) { |
629 | pr_info("bridge: RTM_NEWNEIGH with invalid state %#x\n", ndm->ndm_state); | 655 | pr_info("bridge: RTM_NEWNEIGH with invalid state %#x\n", ndm->ndm_state); |
630 | return -EINVAL; | 656 | return -EINVAL; |
631 | } | 657 | } |
632 | 658 | ||
659 | if (tb[NDA_VLAN]) { | ||
660 | if (nla_len(tb[NDA_VLAN]) != sizeof(unsigned short)) { | ||
661 | pr_info("bridge: RTM_NEWNEIGH with invalid vlan\n"); | ||
662 | return -EINVAL; | ||
663 | } | ||
664 | |||
665 | vid = nla_get_u16(tb[NDA_VLAN]); | ||
666 | |||
667 | if (vid >= VLAN_N_VID) { | ||
668 | pr_info("bridge: RTM_NEWNEIGH with invalid vlan id %d\n", | ||
669 | vid); | ||
670 | return -EINVAL; | ||
671 | } | ||
672 | } | ||
673 | |||
633 | p = br_port_get_rtnl(dev); | 674 | p = br_port_get_rtnl(dev); |
634 | if (p == NULL) { | 675 | if (p == NULL) { |
635 | pr_info("bridge: RTM_NEWNEIGH %s not a bridge port\n", | 676 | pr_info("bridge: RTM_NEWNEIGH %s not a bridge port\n", |
@@ -637,41 +678,90 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], | |||
637 | return -EINVAL; | 678 | return -EINVAL; |
638 | } | 679 | } |
639 | 680 | ||
640 | if (ndm->ndm_flags & NTF_USE) { | 681 | pv = nbp_get_vlan_info(p); |
641 | rcu_read_lock(); | 682 | if (vid != VLAN_N_VID) { |
642 | br_fdb_update(p->br, p, addr, 0); | 683 | if (!pv || !test_bit(vid, pv->vlan_bitmap)) { |
643 | rcu_read_unlock(); | 684 | pr_info("bridge: RTM_NEWNEIGH with unconfigured " |
685 | "vlan %d on port %s\n", vid, dev->name); | ||
686 | return -EINVAL; | ||
687 | } | ||
688 | |||
689 | /* VID was specified, so use it. */ | ||
690 | err = __br_fdb_add(ndm, p, addr, nlh_flags, vid); | ||
644 | } else { | 691 | } else { |
645 | spin_lock_bh(&p->br->hash_lock); | 692 | if (!pv || bitmap_empty(pv->vlan_bitmap, BR_VLAN_BITMAP_LEN)) { |
646 | err = fdb_add_entry(p, addr, ndm->ndm_state, nlh_flags, | 693 | err = __br_fdb_add(ndm, p, addr, nlh_flags, 0); |
647 | 0); | 694 | goto out; |
648 | spin_unlock_bh(&p->br->hash_lock); | 695 | } |
696 | |||
697 | /* We have vlans configured on this port and user didn't | ||
698 | * specify a VLAN. To be nice, add/update entry for every | ||
699 | * vlan on this port. | ||
700 | */ | ||
701 | vid = find_first_bit(pv->vlan_bitmap, BR_VLAN_BITMAP_LEN); | ||
702 | while (vid < BR_VLAN_BITMAP_LEN) { | ||
703 | err = __br_fdb_add(ndm, p, addr, nlh_flags, vid); | ||
704 | if (err) | ||
705 | goto out; | ||
706 | vid = find_next_bit(pv->vlan_bitmap, | ||
707 | BR_VLAN_BITMAP_LEN, vid+1); | ||
708 | } | ||
649 | } | 709 | } |
650 | 710 | ||
711 | out: | ||
651 | return err; | 712 | return err; |
652 | } | 713 | } |
653 | 714 | ||
654 | static int fdb_delete_by_addr(struct net_bridge_port *p, const u8 *addr) | 715 | static int fdb_delete_by_addr(struct net_bridge *br, const u8 *addr, |
716 | u16 vlan) | ||
655 | { | 717 | { |
656 | struct net_bridge *br = p->br; | 718 | struct hlist_head *head = &br->hash[br_mac_hash(addr, vlan)]; |
657 | struct hlist_head *head = &br->hash[br_mac_hash(addr, 0)]; | ||
658 | struct net_bridge_fdb_entry *fdb; | 719 | struct net_bridge_fdb_entry *fdb; |
659 | 720 | ||
660 | fdb = fdb_find(head, addr, 0); | 721 | fdb = fdb_find(head, addr, vlan); |
661 | if (!fdb) | 722 | if (!fdb) |
662 | return -ENOENT; | 723 | return -ENOENT; |
663 | 724 | ||
664 | fdb_delete(p->br, fdb); | 725 | fdb_delete(br, fdb); |
665 | return 0; | 726 | return 0; |
666 | } | 727 | } |
667 | 728 | ||
729 | static int __br_fdb_delete(struct net_bridge_port *p, | ||
730 | const unsigned char *addr, u16 vid) | ||
731 | { | ||
732 | int err; | ||
733 | |||
734 | spin_lock_bh(&p->br->hash_lock); | ||
735 | err = fdb_delete_by_addr(p->br, addr, vid); | ||
736 | spin_unlock_bh(&p->br->hash_lock); | ||
737 | |||
738 | return err; | ||
739 | } | ||
740 | |||
668 | /* Remove neighbor entry with RTM_DELNEIGH */ | 741 | /* Remove neighbor entry with RTM_DELNEIGH */ |
669 | int br_fdb_delete(struct ndmsg *ndm, struct net_device *dev, | 742 | int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], |
743 | struct net_device *dev, | ||
670 | const unsigned char *addr) | 744 | const unsigned char *addr) |
671 | { | 745 | { |
672 | struct net_bridge_port *p; | 746 | struct net_bridge_port *p; |
673 | int err; | 747 | int err; |
748 | struct net_port_vlans *pv; | ||
749 | unsigned short vid = VLAN_N_VID; | ||
674 | 750 | ||
751 | if (tb[NDA_VLAN]) { | ||
752 | if (nla_len(tb[NDA_VLAN]) != sizeof(unsigned short)) { | ||
753 | pr_info("bridge: RTM_NEWNEIGH with invalid vlan\n"); | ||
754 | return -EINVAL; | ||
755 | } | ||
756 | |||
757 | vid = nla_get_u16(tb[NDA_VLAN]); | ||
758 | |||
759 | if (vid >= VLAN_N_VID) { | ||
760 | pr_info("bridge: RTM_NEWNEIGH with invalid vlan id %d\n", | ||
761 | vid); | ||
762 | return -EINVAL; | ||
763 | } | ||
764 | } | ||
675 | p = br_port_get_rtnl(dev); | 765 | p = br_port_get_rtnl(dev); |
676 | if (p == NULL) { | 766 | if (p == NULL) { |
677 | pr_info("bridge: RTM_DELNEIGH %s not a bridge port\n", | 767 | pr_info("bridge: RTM_DELNEIGH %s not a bridge port\n", |
@@ -679,9 +769,33 @@ int br_fdb_delete(struct ndmsg *ndm, struct net_device *dev, | |||
679 | return -EINVAL; | 769 | return -EINVAL; |
680 | } | 770 | } |
681 | 771 | ||
682 | spin_lock_bh(&p->br->hash_lock); | 772 | pv = nbp_get_vlan_info(p); |
683 | err = fdb_delete_by_addr(p, addr); | 773 | if (vid != VLAN_N_VID) { |
684 | spin_unlock_bh(&p->br->hash_lock); | 774 | if (!pv || !test_bit(vid, pv->vlan_bitmap)) { |
775 | pr_info("bridge: RTM_DELNEIGH with unconfigured " | ||
776 | "vlan %d on port %s\n", vid, dev->name); | ||
777 | return -EINVAL; | ||
778 | } | ||
685 | 779 | ||
780 | err = __br_fdb_delete(p, addr, vid); | ||
781 | } else { | ||
782 | if (!pv || bitmap_empty(pv->vlan_bitmap, BR_VLAN_BITMAP_LEN)) { | ||
783 | err = __br_fdb_delete(p, addr, 0); | ||
784 | goto out; | ||
785 | } | ||
786 | |||
787 | /* We have vlans configured on this port and user didn't | ||
788 | * specify a VLAN. To be nice, add/update entry for every | ||
789 | * vlan on this port. | ||
790 | */ | ||
791 | err = -ENOENT; | ||
792 | vid = find_first_bit(pv->vlan_bitmap, BR_VLAN_BITMAP_LEN); | ||
793 | while (vid < BR_VLAN_BITMAP_LEN) { | ||
794 | err &= __br_fdb_delete(p, addr, vid); | ||
795 | vid = find_next_bit(pv->vlan_bitmap, | ||
796 | BR_VLAN_BITMAP_LEN, vid+1); | ||
797 | } | ||
798 | } | ||
799 | out: | ||
686 | return err; | 800 | return err; |
687 | } | 801 | } |
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index 22915c8e9961..799dbb37e5a2 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h | |||
@@ -388,7 +388,7 @@ extern void br_fdb_update(struct net_bridge *br, | |||
388 | const unsigned char *addr, | 388 | const unsigned char *addr, |
389 | u16 vid); | 389 | u16 vid); |
390 | 390 | ||
391 | extern int br_fdb_delete(struct ndmsg *ndm, | 391 | extern int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[], |
392 | struct net_device *dev, | 392 | struct net_device *dev, |
393 | const unsigned char *addr); | 393 | const unsigned char *addr); |
394 | extern int br_fdb_add(struct ndmsg *nlh, struct nlattr *tb[], | 394 | extern int br_fdb_add(struct ndmsg *nlh, struct nlattr *tb[], |
@@ -577,13 +577,13 @@ extern void nbp_vlan_flush(struct net_bridge_port *port); | |||
577 | static inline struct net_port_vlans *br_get_vlan_info( | 577 | static inline struct net_port_vlans *br_get_vlan_info( |
578 | const struct net_bridge *br) | 578 | const struct net_bridge *br) |
579 | { | 579 | { |
580 | return rcu_dereference(br->vlan_info); | 580 | return rcu_dereference_rtnl(br->vlan_info); |
581 | } | 581 | } |
582 | 582 | ||
583 | static inline struct net_port_vlans *nbp_get_vlan_info( | 583 | static inline struct net_port_vlans *nbp_get_vlan_info( |
584 | const struct net_bridge_port *p) | 584 | const struct net_bridge_port *p) |
585 | { | 585 | { |
586 | return rcu_dereference(p->vlan_info); | 586 | return rcu_dereference_rtnl(p->vlan_info); |
587 | } | 587 | } |
588 | 588 | ||
589 | /* Since bridge now depends on 8021Q module, but the time bridge sees the | 589 | /* Since bridge now depends on 8021Q module, but the time bridge sees the |