diff options
-rw-r--r-- | include/linux/netdevice.h | 3 | ||||
-rw-r--r-- | net/core/dev_addr_lists.c | 210 |
2 files changed, 171 insertions, 42 deletions
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 53d3939358a7..623b57b52195 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h | |||
@@ -209,6 +209,7 @@ struct netdev_hw_addr { | |||
209 | #define NETDEV_HW_ADDR_T_UNICAST 4 | 209 | #define NETDEV_HW_ADDR_T_UNICAST 4 |
210 | #define NETDEV_HW_ADDR_T_MULTICAST 5 | 210 | #define NETDEV_HW_ADDR_T_MULTICAST 5 |
211 | bool global_use; | 211 | bool global_use; |
212 | int sync_cnt; | ||
212 | int refcount; | 213 | int refcount; |
213 | int synced; | 214 | int synced; |
214 | struct rcu_head rcu_head; | 215 | struct rcu_head rcu_head; |
@@ -2627,6 +2628,7 @@ extern int dev_uc_add(struct net_device *dev, const unsigned char *addr); | |||
2627 | extern int dev_uc_add_excl(struct net_device *dev, const unsigned char *addr); | 2628 | extern int dev_uc_add_excl(struct net_device *dev, const unsigned char *addr); |
2628 | extern int dev_uc_del(struct net_device *dev, const unsigned char *addr); | 2629 | extern int dev_uc_del(struct net_device *dev, const unsigned char *addr); |
2629 | extern int dev_uc_sync(struct net_device *to, struct net_device *from); | 2630 | extern int dev_uc_sync(struct net_device *to, struct net_device *from); |
2631 | extern int dev_uc_sync_multiple(struct net_device *to, struct net_device *from); | ||
2630 | extern void dev_uc_unsync(struct net_device *to, struct net_device *from); | 2632 | extern void dev_uc_unsync(struct net_device *to, struct net_device *from); |
2631 | extern void dev_uc_flush(struct net_device *dev); | 2633 | extern void dev_uc_flush(struct net_device *dev); |
2632 | extern void dev_uc_init(struct net_device *dev); | 2634 | extern void dev_uc_init(struct net_device *dev); |
@@ -2638,6 +2640,7 @@ extern int dev_mc_add_excl(struct net_device *dev, const unsigned char *addr); | |||
2638 | extern int dev_mc_del(struct net_device *dev, const unsigned char *addr); | 2640 | extern int dev_mc_del(struct net_device *dev, const unsigned char *addr); |
2639 | extern int dev_mc_del_global(struct net_device *dev, const unsigned char *addr); | 2641 | extern int dev_mc_del_global(struct net_device *dev, const unsigned char *addr); |
2640 | extern int dev_mc_sync(struct net_device *to, struct net_device *from); | 2642 | extern int dev_mc_sync(struct net_device *to, struct net_device *from); |
2643 | extern int dev_mc_sync_multiple(struct net_device *to, struct net_device *from); | ||
2641 | extern void dev_mc_unsync(struct net_device *to, struct net_device *from); | 2644 | extern void dev_mc_unsync(struct net_device *to, struct net_device *from); |
2642 | extern void dev_mc_flush(struct net_device *dev); | 2645 | extern void dev_mc_flush(struct net_device *dev); |
2643 | extern void dev_mc_init(struct net_device *dev); | 2646 | extern void dev_mc_init(struct net_device *dev); |
diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c index abdc9e6ef33e..c013f38482a1 100644 --- a/net/core/dev_addr_lists.c +++ b/net/core/dev_addr_lists.c | |||
@@ -22,7 +22,8 @@ | |||
22 | 22 | ||
23 | static int __hw_addr_create_ex(struct netdev_hw_addr_list *list, | 23 | static int __hw_addr_create_ex(struct netdev_hw_addr_list *list, |
24 | const unsigned char *addr, int addr_len, | 24 | const unsigned char *addr, int addr_len, |
25 | unsigned char addr_type, bool global) | 25 | unsigned char addr_type, bool global, |
26 | bool sync) | ||
26 | { | 27 | { |
27 | struct netdev_hw_addr *ha; | 28 | struct netdev_hw_addr *ha; |
28 | int alloc_size; | 29 | int alloc_size; |
@@ -37,7 +38,7 @@ static int __hw_addr_create_ex(struct netdev_hw_addr_list *list, | |||
37 | ha->type = addr_type; | 38 | ha->type = addr_type; |
38 | ha->refcount = 1; | 39 | ha->refcount = 1; |
39 | ha->global_use = global; | 40 | ha->global_use = global; |
40 | ha->synced = 0; | 41 | ha->synced = sync; |
41 | list_add_tail_rcu(&ha->list, &list->list); | 42 | list_add_tail_rcu(&ha->list, &list->list); |
42 | list->count++; | 43 | list->count++; |
43 | 44 | ||
@@ -46,7 +47,7 @@ static int __hw_addr_create_ex(struct netdev_hw_addr_list *list, | |||
46 | 47 | ||
47 | static int __hw_addr_add_ex(struct netdev_hw_addr_list *list, | 48 | static int __hw_addr_add_ex(struct netdev_hw_addr_list *list, |
48 | const unsigned char *addr, int addr_len, | 49 | const unsigned char *addr, int addr_len, |
49 | unsigned char addr_type, bool global) | 50 | unsigned char addr_type, bool global, bool sync) |
50 | { | 51 | { |
51 | struct netdev_hw_addr *ha; | 52 | struct netdev_hw_addr *ha; |
52 | 53 | ||
@@ -63,43 +64,62 @@ static int __hw_addr_add_ex(struct netdev_hw_addr_list *list, | |||
63 | else | 64 | else |
64 | ha->global_use = true; | 65 | ha->global_use = true; |
65 | } | 66 | } |
67 | if (sync) { | ||
68 | if (ha->synced) | ||
69 | return 0; | ||
70 | else | ||
71 | ha->synced = true; | ||
72 | } | ||
66 | ha->refcount++; | 73 | ha->refcount++; |
67 | return 0; | 74 | return 0; |
68 | } | 75 | } |
69 | } | 76 | } |
70 | 77 | ||
71 | return __hw_addr_create_ex(list, addr, addr_len, addr_type, global); | 78 | return __hw_addr_create_ex(list, addr, addr_len, addr_type, global, |
79 | sync); | ||
72 | } | 80 | } |
73 | 81 | ||
74 | static int __hw_addr_add(struct netdev_hw_addr_list *list, | 82 | static int __hw_addr_add(struct netdev_hw_addr_list *list, |
75 | const unsigned char *addr, int addr_len, | 83 | const unsigned char *addr, int addr_len, |
76 | unsigned char addr_type) | 84 | unsigned char addr_type) |
77 | { | 85 | { |
78 | return __hw_addr_add_ex(list, addr, addr_len, addr_type, false); | 86 | return __hw_addr_add_ex(list, addr, addr_len, addr_type, false, false); |
87 | } | ||
88 | |||
89 | static int __hw_addr_del_entry(struct netdev_hw_addr_list *list, | ||
90 | struct netdev_hw_addr *ha, bool global, | ||
91 | bool sync) | ||
92 | { | ||
93 | if (global && !ha->global_use) | ||
94 | return -ENOENT; | ||
95 | |||
96 | if (sync && !ha->synced) | ||
97 | return -ENOENT; | ||
98 | |||
99 | if (global) | ||
100 | ha->global_use = false; | ||
101 | |||
102 | if (sync) | ||
103 | ha->synced = false; | ||
104 | |||
105 | if (--ha->refcount) | ||
106 | return 0; | ||
107 | list_del_rcu(&ha->list); | ||
108 | kfree_rcu(ha, rcu_head); | ||
109 | list->count--; | ||
110 | return 0; | ||
79 | } | 111 | } |
80 | 112 | ||
81 | static int __hw_addr_del_ex(struct netdev_hw_addr_list *list, | 113 | static int __hw_addr_del_ex(struct netdev_hw_addr_list *list, |
82 | const unsigned char *addr, int addr_len, | 114 | const unsigned char *addr, int addr_len, |
83 | unsigned char addr_type, bool global) | 115 | unsigned char addr_type, bool global, bool sync) |
84 | { | 116 | { |
85 | struct netdev_hw_addr *ha; | 117 | struct netdev_hw_addr *ha; |
86 | 118 | ||
87 | list_for_each_entry(ha, &list->list, list) { | 119 | list_for_each_entry(ha, &list->list, list) { |
88 | if (!memcmp(ha->addr, addr, addr_len) && | 120 | if (!memcmp(ha->addr, addr, addr_len) && |
89 | (ha->type == addr_type || !addr_type)) { | 121 | (ha->type == addr_type || !addr_type)) |
90 | if (global) { | 122 | return __hw_addr_del_entry(list, ha, global, sync); |
91 | if (!ha->global_use) | ||
92 | break; | ||
93 | else | ||
94 | ha->global_use = false; | ||
95 | } | ||
96 | if (--ha->refcount) | ||
97 | return 0; | ||
98 | list_del_rcu(&ha->list); | ||
99 | kfree_rcu(ha, rcu_head); | ||
100 | list->count--; | ||
101 | return 0; | ||
102 | } | ||
103 | } | 123 | } |
104 | return -ENOENT; | 124 | return -ENOENT; |
105 | } | 125 | } |
@@ -108,7 +128,57 @@ static int __hw_addr_del(struct netdev_hw_addr_list *list, | |||
108 | const unsigned char *addr, int addr_len, | 128 | const unsigned char *addr, int addr_len, |
109 | unsigned char addr_type) | 129 | unsigned char addr_type) |
110 | { | 130 | { |
111 | return __hw_addr_del_ex(list, addr, addr_len, addr_type, false); | 131 | return __hw_addr_del_ex(list, addr, addr_len, addr_type, false, false); |
132 | } | ||
133 | |||
134 | static int __hw_addr_sync_one(struct netdev_hw_addr_list *to_list, | ||
135 | struct netdev_hw_addr *ha, | ||
136 | int addr_len) | ||
137 | { | ||
138 | int err; | ||
139 | |||
140 | err = __hw_addr_add_ex(to_list, ha->addr, addr_len, ha->type, | ||
141 | false, true); | ||
142 | if (err) | ||
143 | return err; | ||
144 | ha->sync_cnt++; | ||
145 | ha->refcount++; | ||
146 | |||
147 | return 0; | ||
148 | } | ||
149 | |||
150 | static void __hw_addr_unsync_one(struct netdev_hw_addr_list *to_list, | ||
151 | struct netdev_hw_addr_list *from_list, | ||
152 | struct netdev_hw_addr *ha, | ||
153 | int addr_len) | ||
154 | { | ||
155 | int err; | ||
156 | |||
157 | err = __hw_addr_del_ex(to_list, ha->addr, addr_len, ha->type, | ||
158 | false, true); | ||
159 | if (err) | ||
160 | return; | ||
161 | ha->sync_cnt--; | ||
162 | __hw_addr_del_entry(from_list, ha, false, true); | ||
163 | } | ||
164 | |||
165 | static int __hw_addr_sync_multiple(struct netdev_hw_addr_list *to_list, | ||
166 | struct netdev_hw_addr_list *from_list, | ||
167 | int addr_len) | ||
168 | { | ||
169 | int err = 0; | ||
170 | struct netdev_hw_addr *ha, *tmp; | ||
171 | |||
172 | list_for_each_entry_safe(ha, tmp, &from_list->list, list) { | ||
173 | if (ha->sync_cnt == ha->refcount) { | ||
174 | __hw_addr_unsync_one(to_list, from_list, ha, addr_len); | ||
175 | } else { | ||
176 | err = __hw_addr_sync_one(to_list, ha, addr_len); | ||
177 | if (err) | ||
178 | break; | ||
179 | } | ||
180 | } | ||
181 | return err; | ||
112 | } | 182 | } |
113 | 183 | ||
114 | int __hw_addr_add_multiple(struct netdev_hw_addr_list *to_list, | 184 | int __hw_addr_add_multiple(struct netdev_hw_addr_list *to_list, |
@@ -152,6 +222,11 @@ void __hw_addr_del_multiple(struct netdev_hw_addr_list *to_list, | |||
152 | } | 222 | } |
153 | EXPORT_SYMBOL(__hw_addr_del_multiple); | 223 | EXPORT_SYMBOL(__hw_addr_del_multiple); |
154 | 224 | ||
225 | /* This function only works where there is a strict 1-1 relationship | ||
226 | * between source and destionation of they synch. If you ever need to | ||
227 | * sync addresses to more then 1 destination, you need to use | ||
228 | * __hw_addr_sync_multiple(). | ||
229 | */ | ||
155 | int __hw_addr_sync(struct netdev_hw_addr_list *to_list, | 230 | int __hw_addr_sync(struct netdev_hw_addr_list *to_list, |
156 | struct netdev_hw_addr_list *from_list, | 231 | struct netdev_hw_addr_list *from_list, |
157 | int addr_len) | 232 | int addr_len) |
@@ -160,17 +235,12 @@ int __hw_addr_sync(struct netdev_hw_addr_list *to_list, | |||
160 | struct netdev_hw_addr *ha, *tmp; | 235 | struct netdev_hw_addr *ha, *tmp; |
161 | 236 | ||
162 | list_for_each_entry_safe(ha, tmp, &from_list->list, list) { | 237 | list_for_each_entry_safe(ha, tmp, &from_list->list, list) { |
163 | if (!ha->synced) { | 238 | if (!ha->sync_cnt) { |
164 | err = __hw_addr_add(to_list, ha->addr, | 239 | err = __hw_addr_sync_one(to_list, ha, addr_len); |
165 | addr_len, ha->type); | ||
166 | if (err) | 240 | if (err) |
167 | break; | 241 | break; |
168 | ha->synced++; | 242 | } else if (ha->refcount == 1) |
169 | ha->refcount++; | 243 | __hw_addr_unsync_one(to_list, from_list, ha, addr_len); |
170 | } else if (ha->refcount == 1) { | ||
171 | __hw_addr_del(to_list, ha->addr, addr_len, ha->type); | ||
172 | __hw_addr_del(from_list, ha->addr, addr_len, ha->type); | ||
173 | } | ||
174 | } | 244 | } |
175 | return err; | 245 | return err; |
176 | } | 246 | } |
@@ -183,13 +253,8 @@ void __hw_addr_unsync(struct netdev_hw_addr_list *to_list, | |||
183 | struct netdev_hw_addr *ha, *tmp; | 253 | struct netdev_hw_addr *ha, *tmp; |
184 | 254 | ||
185 | list_for_each_entry_safe(ha, tmp, &from_list->list, list) { | 255 | list_for_each_entry_safe(ha, tmp, &from_list->list, list) { |
186 | if (ha->synced) { | 256 | if (ha->sync_cnt) |
187 | __hw_addr_del(to_list, ha->addr, | 257 | __hw_addr_unsync_one(to_list, from_list, ha, addr_len); |
188 | addr_len, ha->type); | ||
189 | ha->synced--; | ||
190 | __hw_addr_del(from_list, ha->addr, | ||
191 | addr_len, ha->type); | ||
192 | } | ||
193 | } | 258 | } |
194 | } | 259 | } |
195 | EXPORT_SYMBOL(__hw_addr_unsync); | 260 | EXPORT_SYMBOL(__hw_addr_unsync); |
@@ -406,7 +471,7 @@ int dev_uc_add_excl(struct net_device *dev, const unsigned char *addr) | |||
406 | } | 471 | } |
407 | } | 472 | } |
408 | err = __hw_addr_create_ex(&dev->uc, addr, dev->addr_len, | 473 | err = __hw_addr_create_ex(&dev->uc, addr, dev->addr_len, |
409 | NETDEV_HW_ADDR_T_UNICAST, true); | 474 | NETDEV_HW_ADDR_T_UNICAST, true, false); |
410 | if (!err) | 475 | if (!err) |
411 | __dev_set_rx_mode(dev); | 476 | __dev_set_rx_mode(dev); |
412 | out: | 477 | out: |
@@ -469,7 +534,8 @@ EXPORT_SYMBOL(dev_uc_del); | |||
469 | * locked by netif_addr_lock_bh. | 534 | * locked by netif_addr_lock_bh. |
470 | * | 535 | * |
471 | * This function is intended to be called from the dev->set_rx_mode | 536 | * This function is intended to be called from the dev->set_rx_mode |
472 | * function of layered software devices. | 537 | * function of layered software devices. This function assumes that |
538 | * addresses will only ever be synced to the @to devices and no other. | ||
473 | */ | 539 | */ |
474 | int dev_uc_sync(struct net_device *to, struct net_device *from) | 540 | int dev_uc_sync(struct net_device *to, struct net_device *from) |
475 | { | 541 | { |
@@ -488,6 +554,36 @@ int dev_uc_sync(struct net_device *to, struct net_device *from) | |||
488 | EXPORT_SYMBOL(dev_uc_sync); | 554 | EXPORT_SYMBOL(dev_uc_sync); |
489 | 555 | ||
490 | /** | 556 | /** |
557 | * dev_uc_sync_multiple - Synchronize device's unicast list to another | ||
558 | * device, but allow for multiple calls to sync to multiple devices. | ||
559 | * @to: destination device | ||
560 | * @from: source device | ||
561 | * | ||
562 | * Add newly added addresses to the destination device and release | ||
563 | * addresses that have been deleted from the source. The source device | ||
564 | * must be locked by netif_addr_lock_bh. | ||
565 | * | ||
566 | * This function is intended to be called from the dev->set_rx_mode | ||
567 | * function of layered software devices. It allows for a single source | ||
568 | * device to be synced to multiple destination devices. | ||
569 | */ | ||
570 | int dev_uc_sync_multiple(struct net_device *to, struct net_device *from) | ||
571 | { | ||
572 | int err = 0; | ||
573 | |||
574 | if (to->addr_len != from->addr_len) | ||
575 | return -EINVAL; | ||
576 | |||
577 | netif_addr_lock_nested(to); | ||
578 | err = __hw_addr_sync_multiple(&to->uc, &from->uc, to->addr_len); | ||
579 | if (!err) | ||
580 | __dev_set_rx_mode(to); | ||
581 | netif_addr_unlock(to); | ||
582 | return err; | ||
583 | } | ||
584 | EXPORT_SYMBOL(dev_uc_sync_multiple); | ||
585 | |||
586 | /** | ||
491 | * dev_uc_unsync - Remove synchronized addresses from the destination device | 587 | * dev_uc_unsync - Remove synchronized addresses from the destination device |
492 | * @to: destination device | 588 | * @to: destination device |
493 | * @from: source device | 589 | * @from: source device |
@@ -559,7 +655,7 @@ int dev_mc_add_excl(struct net_device *dev, const unsigned char *addr) | |||
559 | } | 655 | } |
560 | } | 656 | } |
561 | err = __hw_addr_create_ex(&dev->mc, addr, dev->addr_len, | 657 | err = __hw_addr_create_ex(&dev->mc, addr, dev->addr_len, |
562 | NETDEV_HW_ADDR_T_MULTICAST, true); | 658 | NETDEV_HW_ADDR_T_MULTICAST, true, false); |
563 | if (!err) | 659 | if (!err) |
564 | __dev_set_rx_mode(dev); | 660 | __dev_set_rx_mode(dev); |
565 | out: | 661 | out: |
@@ -575,7 +671,7 @@ static int __dev_mc_add(struct net_device *dev, const unsigned char *addr, | |||
575 | 671 | ||
576 | netif_addr_lock_bh(dev); | 672 | netif_addr_lock_bh(dev); |
577 | err = __hw_addr_add_ex(&dev->mc, addr, dev->addr_len, | 673 | err = __hw_addr_add_ex(&dev->mc, addr, dev->addr_len, |
578 | NETDEV_HW_ADDR_T_MULTICAST, global); | 674 | NETDEV_HW_ADDR_T_MULTICAST, global, false); |
579 | if (!err) | 675 | if (!err) |
580 | __dev_set_rx_mode(dev); | 676 | __dev_set_rx_mode(dev); |
581 | netif_addr_unlock_bh(dev); | 677 | netif_addr_unlock_bh(dev); |
@@ -615,7 +711,7 @@ static int __dev_mc_del(struct net_device *dev, const unsigned char *addr, | |||
615 | 711 | ||
616 | netif_addr_lock_bh(dev); | 712 | netif_addr_lock_bh(dev); |
617 | err = __hw_addr_del_ex(&dev->mc, addr, dev->addr_len, | 713 | err = __hw_addr_del_ex(&dev->mc, addr, dev->addr_len, |
618 | NETDEV_HW_ADDR_T_MULTICAST, global); | 714 | NETDEV_HW_ADDR_T_MULTICAST, global, false); |
619 | if (!err) | 715 | if (!err) |
620 | __dev_set_rx_mode(dev); | 716 | __dev_set_rx_mode(dev); |
621 | netif_addr_unlock_bh(dev); | 717 | netif_addr_unlock_bh(dev); |
@@ -679,6 +775,36 @@ int dev_mc_sync(struct net_device *to, struct net_device *from) | |||
679 | EXPORT_SYMBOL(dev_mc_sync); | 775 | EXPORT_SYMBOL(dev_mc_sync); |
680 | 776 | ||
681 | /** | 777 | /** |
778 | * dev_mc_sync_multiple - Synchronize device's unicast list to another | ||
779 | * device, but allow for multiple calls to sync to multiple devices. | ||
780 | * @to: destination device | ||
781 | * @from: source device | ||
782 | * | ||
783 | * Add newly added addresses to the destination device and release | ||
784 | * addresses that have no users left. The source device must be | ||
785 | * locked by netif_addr_lock_bh. | ||
786 | * | ||
787 | * This function is intended to be called from the ndo_set_rx_mode | ||
788 | * function of layered software devices. It allows for a single | ||
789 | * source device to be synced to multiple destination devices. | ||
790 | */ | ||
791 | int dev_mc_sync_multiple(struct net_device *to, struct net_device *from) | ||
792 | { | ||
793 | int err = 0; | ||
794 | |||
795 | if (to->addr_len != from->addr_len) | ||
796 | return -EINVAL; | ||
797 | |||
798 | netif_addr_lock_nested(to); | ||
799 | err = __hw_addr_sync(&to->mc, &from->mc, to->addr_len); | ||
800 | if (!err) | ||
801 | __dev_set_rx_mode(to); | ||
802 | netif_addr_unlock(to); | ||
803 | return err; | ||
804 | } | ||
805 | EXPORT_SYMBOL(dev_mc_sync_multiple); | ||
806 | |||
807 | /** | ||
682 | * dev_mc_unsync - Remove synchronized addresses from the destination device | 808 | * dev_mc_unsync - Remove synchronized addresses from the destination device |
683 | * @to: destination device | 809 | * @to: destination device |
684 | * @from: source device | 810 | * @from: source device |