Skip to content

Commit 6db6f0e

Browse files
nbd168davem330
authored andcommitted
bridge: multicast to unicast
Implements an optional, per bridge port flag and feature to deliver multicast packets to any host on the according port via unicast individually. This is done by copying the packet per host and changing the multicast destination MAC to a unicast one accordingly. multicast-to-unicast works on top of the multicast snooping feature of the bridge. Which means unicast copies are only delivered to hosts which are interested in it and signalized this via IGMP/MLD reports previously. This feature is intended for interface types which have a more reliable and/or efficient way to deliver unicast packets than broadcast ones (e.g. wifi). However, it should only be enabled on interfaces where no IGMPv2/MLDv1 report suppression takes place. This feature is disabled by default. The initial patch and idea is from Felix Fietkau. Signed-off-by: Felix Fietkau <nbd@nbd.name> [linus.luessing@c0d3.blue: various bug + style fixes, commit message] Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue> Reviewed-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent 4548b68 commit 6db6f0e

File tree

8 files changed

+114
-29
lines changed

8 files changed

+114
-29
lines changed

include/linux/if_bridge.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct br_ip_list {
4646
#define BR_LEARNING_SYNC BIT(9)
4747
#define BR_PROXYARP_WIFI BIT(10)
4848
#define BR_MCAST_FLOOD BIT(11)
49+
#define BR_MULTICAST_TO_UNICAST BIT(12)
4950

5051
#define BR_DEFAULT_AGEING_TIME (300 * HZ)
5152

include/uapi/linux/if_link.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ enum {
321321
IFLA_BRPORT_MULTICAST_ROUTER,
322322
IFLA_BRPORT_PAD,
323323
IFLA_BRPORT_MCAST_FLOOD,
324+
IFLA_BRPORT_MCAST_TO_UCAST,
324325
__IFLA_BRPORT_MAX
325326
};
326327
#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)

net/bridge/br_forward.c

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,31 @@ static struct net_bridge_port *maybe_deliver(
174174
return p;
175175
}
176176

177+
static void maybe_deliver_addr(struct net_bridge_port *p, struct sk_buff *skb,
178+
const unsigned char *addr, bool local_orig)
179+
{
180+
struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev;
181+
const unsigned char *src = eth_hdr(skb)->h_source;
182+
183+
if (!should_deliver(p, skb))
184+
return;
185+
186+
/* Even with hairpin, no soliloquies - prevent breaking IPv6 DAD */
187+
if (skb->dev == p->dev && ether_addr_equal(src, addr))
188+
return;
189+
190+
skb = skb_copy(skb, GFP_ATOMIC);
191+
if (!skb) {
192+
dev->stats.tx_dropped++;
193+
return;
194+
}
195+
196+
if (!is_broadcast_ether_addr(addr))
197+
memcpy(eth_hdr(skb)->h_dest, addr, ETH_ALEN);
198+
199+
__br_forward(p, skb, local_orig);
200+
}
201+
177202
/* called under rcu_read_lock */
178203
void br_flood(struct net_bridge *br, struct sk_buff *skb,
179204
enum br_pkt_type pkt_type, bool local_rcv, bool local_orig)
@@ -241,10 +266,20 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
241266
rport = rp ? hlist_entry(rp, struct net_bridge_port, rlist) :
242267
NULL;
243268

244-
port = (unsigned long)lport > (unsigned long)rport ?
245-
lport : rport;
269+
if ((unsigned long)lport > (unsigned long)rport) {
270+
port = lport;
271+
272+
if (port->flags & BR_MULTICAST_TO_UNICAST) {
273+
maybe_deliver_addr(lport, skb, p->eth_addr,
274+
local_orig);
275+
goto delivered;
276+
}
277+
} else {
278+
port = rport;
279+
}
246280

247281
prev = maybe_deliver(prev, port, skb, local_orig);
282+
delivered:
248283
if (IS_ERR(prev))
249284
goto out;
250285
if (prev == port)

net/bridge/br_mdb.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
531531
break;
532532
}
533533

534-
p = br_multicast_new_port_group(port, group, *pp, state);
534+
p = br_multicast_new_port_group(port, group, *pp, state, NULL);
535535
if (unlikely(!p))
536536
return -ENOMEM;
537537
rcu_assign_pointer(*pp, p);

net/bridge/br_multicast.c

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,14 @@ static void br_multicast_add_router(struct net_bridge *br,
4343
static void br_ip4_multicast_leave_group(struct net_bridge *br,
4444
struct net_bridge_port *port,
4545
__be32 group,
46-
__u16 vid);
46+
__u16 vid,
47+
const unsigned char *src);
48+
4749
#if IS_ENABLED(CONFIG_IPV6)
4850
static void br_ip6_multicast_leave_group(struct net_bridge *br,
4951
struct net_bridge_port *port,
5052
const struct in6_addr *group,
51-
__u16 vid);
53+
__u16 vid, const unsigned char *src);
5254
#endif
5355
unsigned int br_mdb_rehash_seq;
5456

@@ -711,7 +713,8 @@ struct net_bridge_port_group *br_multicast_new_port_group(
711713
struct net_bridge_port *port,
712714
struct br_ip *group,
713715
struct net_bridge_port_group __rcu *next,
714-
unsigned char flags)
716+
unsigned char flags,
717+
const unsigned char *src)
715718
{
716719
struct net_bridge_port_group *p;
717720

@@ -726,12 +729,32 @@ struct net_bridge_port_group *br_multicast_new_port_group(
726729
hlist_add_head(&p->mglist, &port->mglist);
727730
setup_timer(&p->timer, br_multicast_port_group_expired,
728731
(unsigned long)p);
732+
733+
if (src)
734+
memcpy(p->eth_addr, src, ETH_ALEN);
735+
else
736+
memset(p->eth_addr, 0xff, ETH_ALEN);
737+
729738
return p;
730739
}
731740

741+
static bool br_port_group_equal(struct net_bridge_port_group *p,
742+
struct net_bridge_port *port,
743+
const unsigned char *src)
744+
{
745+
if (p->port != port)
746+
return false;
747+
748+
if (!(port->flags & BR_MULTICAST_TO_UNICAST))
749+
return true;
750+
751+
return ether_addr_equal(src, p->eth_addr);
752+
}
753+
732754
static int br_multicast_add_group(struct net_bridge *br,
733755
struct net_bridge_port *port,
734-
struct br_ip *group)
756+
struct br_ip *group,
757+
const unsigned char *src)
735758
{
736759
struct net_bridge_port_group __rcu **pp;
737760
struct net_bridge_port_group *p;
@@ -758,13 +781,13 @@ static int br_multicast_add_group(struct net_bridge *br,
758781
for (pp = &mp->ports;
759782
(p = mlock_dereference(*pp, br)) != NULL;
760783
pp = &p->next) {
761-
if (p->port == port)
784+
if (br_port_group_equal(p, port, src))
762785
goto found;
763786
if ((unsigned long)p->port < (unsigned long)port)
764787
break;
765788
}
766789

767-
p = br_multicast_new_port_group(port, group, *pp, 0);
790+
p = br_multicast_new_port_group(port, group, *pp, 0, src);
768791
if (unlikely(!p))
769792
goto err;
770793
rcu_assign_pointer(*pp, p);
@@ -783,7 +806,8 @@ static int br_multicast_add_group(struct net_bridge *br,
783806
static int br_ip4_multicast_add_group(struct net_bridge *br,
784807
struct net_bridge_port *port,
785808
__be32 group,
786-
__u16 vid)
809+
__u16 vid,
810+
const unsigned char *src)
787811
{
788812
struct br_ip br_group;
789813

@@ -794,14 +818,15 @@ static int br_ip4_multicast_add_group(struct net_bridge *br,
794818
br_group.proto = htons(ETH_P_IP);
795819
br_group.vid = vid;
796820

797-
return br_multicast_add_group(br, port, &br_group);
821+
return br_multicast_add_group(br, port, &br_group, src);
798822
}
799823

800824
#if IS_ENABLED(CONFIG_IPV6)
801825
static int br_ip6_multicast_add_group(struct net_bridge *br,
802826
struct net_bridge_port *port,
803827
const struct in6_addr *group,
804-
__u16 vid)
828+
__u16 vid,
829+
const unsigned char *src)
805830
{
806831
struct br_ip br_group;
807832

@@ -812,7 +837,7 @@ static int br_ip6_multicast_add_group(struct net_bridge *br,
812837
br_group.proto = htons(ETH_P_IPV6);
813838
br_group.vid = vid;
814839

815-
return br_multicast_add_group(br, port, &br_group);
840+
return br_multicast_add_group(br, port, &br_group, src);
816841
}
817842
#endif
818843

@@ -1081,6 +1106,7 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
10811106
struct sk_buff *skb,
10821107
u16 vid)
10831108
{
1109+
const unsigned char *src;
10841110
struct igmpv3_report *ih;
10851111
struct igmpv3_grec *grec;
10861112
int i;
@@ -1121,12 +1147,14 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
11211147
continue;
11221148
}
11231149

1150+
src = eth_hdr(skb)->h_source;
11241151
if ((type == IGMPV3_CHANGE_TO_INCLUDE ||
11251152
type == IGMPV3_MODE_IS_INCLUDE) &&
11261153
ntohs(grec->grec_nsrcs) == 0) {
1127-
br_ip4_multicast_leave_group(br, port, group, vid);
1154+
br_ip4_multicast_leave_group(br, port, group, vid, src);
11281155
} else {
1129-
err = br_ip4_multicast_add_group(br, port, group, vid);
1156+
err = br_ip4_multicast_add_group(br, port, group, vid,
1157+
src);
11301158
if (err)
11311159
break;
11321160
}
@@ -1141,6 +1169,7 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
11411169
struct sk_buff *skb,
11421170
u16 vid)
11431171
{
1172+
const unsigned char *src;
11441173
struct icmp6hdr *icmp6h;
11451174
struct mld2_grec *grec;
11461175
int i;
@@ -1188,14 +1217,16 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
11881217
continue;
11891218
}
11901219

1220+
src = eth_hdr(skb)->h_source;
11911221
if ((grec->grec_type == MLD2_CHANGE_TO_INCLUDE ||
11921222
grec->grec_type == MLD2_MODE_IS_INCLUDE) &&
11931223
ntohs(*nsrcs) == 0) {
11941224
br_ip6_multicast_leave_group(br, port, &grec->grec_mca,
1195-
vid);
1225+
vid, src);
11961226
} else {
11971227
err = br_ip6_multicast_add_group(br, port,
1198-
&grec->grec_mca, vid);
1228+
&grec->grec_mca, vid,
1229+
src);
11991230
if (err)
12001231
break;
12011232
}
@@ -1511,7 +1542,8 @@ br_multicast_leave_group(struct net_bridge *br,
15111542
struct net_bridge_port *port,
15121543
struct br_ip *group,
15131544
struct bridge_mcast_other_query *other_query,
1514-
struct bridge_mcast_own_query *own_query)
1545+
struct bridge_mcast_own_query *own_query,
1546+
const unsigned char *src)
15151547
{
15161548
struct net_bridge_mdb_htable *mdb;
15171549
struct net_bridge_mdb_entry *mp;
@@ -1535,7 +1567,7 @@ br_multicast_leave_group(struct net_bridge *br,
15351567
for (pp = &mp->ports;
15361568
(p = mlock_dereference(*pp, br)) != NULL;
15371569
pp = &p->next) {
1538-
if (p->port != port)
1570+
if (!br_port_group_equal(p, port, src))
15391571
continue;
15401572

15411573
rcu_assign_pointer(*pp, p->next);
@@ -1566,7 +1598,7 @@ br_multicast_leave_group(struct net_bridge *br,
15661598
for (p = mlock_dereference(mp->ports, br);
15671599
p != NULL;
15681600
p = mlock_dereference(p->next, br)) {
1569-
if (p->port != port)
1601+
if (!br_port_group_equal(p, port, src))
15701602
continue;
15711603

15721604
if (!hlist_unhashed(&p->mglist) &&
@@ -1617,7 +1649,8 @@ br_multicast_leave_group(struct net_bridge *br,
16171649
static void br_ip4_multicast_leave_group(struct net_bridge *br,
16181650
struct net_bridge_port *port,
16191651
__be32 group,
1620-
__u16 vid)
1652+
__u16 vid,
1653+
const unsigned char *src)
16211654
{
16221655
struct br_ip br_group;
16231656
struct bridge_mcast_own_query *own_query;
@@ -1632,14 +1665,15 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br,
16321665
br_group.vid = vid;
16331666

16341667
br_multicast_leave_group(br, port, &br_group, &br->ip4_other_query,
1635-
own_query);
1668+
own_query, src);
16361669
}
16371670

16381671
#if IS_ENABLED(CONFIG_IPV6)
16391672
static void br_ip6_multicast_leave_group(struct net_bridge *br,
16401673
struct net_bridge_port *port,
16411674
const struct in6_addr *group,
1642-
__u16 vid)
1675+
__u16 vid,
1676+
const unsigned char *src)
16431677
{
16441678
struct br_ip br_group;
16451679
struct bridge_mcast_own_query *own_query;
@@ -1654,7 +1688,7 @@ static void br_ip6_multicast_leave_group(struct net_bridge *br,
16541688
br_group.vid = vid;
16551689

16561690
br_multicast_leave_group(br, port, &br_group, &br->ip6_other_query,
1657-
own_query);
1691+
own_query, src);
16581692
}
16591693
#endif
16601694

@@ -1712,6 +1746,7 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
17121746
u16 vid)
17131747
{
17141748
struct sk_buff *skb_trimmed = NULL;
1749+
const unsigned char *src;
17151750
struct igmphdr *ih;
17161751
int err;
17171752

@@ -1731,13 +1766,14 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
17311766
}
17321767

17331768
ih = igmp_hdr(skb);
1769+
src = eth_hdr(skb)->h_source;
17341770
BR_INPUT_SKB_CB(skb)->igmp = ih->type;
17351771

17361772
switch (ih->type) {
17371773
case IGMP_HOST_MEMBERSHIP_REPORT:
17381774
case IGMPV2_HOST_MEMBERSHIP_REPORT:
17391775
BR_INPUT_SKB_CB(skb)->mrouters_only = 1;
1740-
err = br_ip4_multicast_add_group(br, port, ih->group, vid);
1776+
err = br_ip4_multicast_add_group(br, port, ih->group, vid, src);
17411777
break;
17421778
case IGMPV3_HOST_MEMBERSHIP_REPORT:
17431779
err = br_ip4_multicast_igmp3_report(br, port, skb_trimmed, vid);
@@ -1746,7 +1782,7 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
17461782
err = br_ip4_multicast_query(br, port, skb_trimmed, vid);
17471783
break;
17481784
case IGMP_HOST_LEAVE_MESSAGE:
1749-
br_ip4_multicast_leave_group(br, port, ih->group, vid);
1785+
br_ip4_multicast_leave_group(br, port, ih->group, vid, src);
17501786
break;
17511787
}
17521788

@@ -1766,6 +1802,7 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
17661802
u16 vid)
17671803
{
17681804
struct sk_buff *skb_trimmed = NULL;
1805+
const unsigned char *src;
17691806
struct mld_msg *mld;
17701807
int err;
17711808

@@ -1785,8 +1822,10 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
17851822

17861823
switch (mld->mld_type) {
17871824
case ICMPV6_MGM_REPORT:
1825+
src = eth_hdr(skb)->h_source;
17881826
BR_INPUT_SKB_CB(skb)->mrouters_only = 1;
1789-
err = br_ip6_multicast_add_group(br, port, &mld->mld_mca, vid);
1827+
err = br_ip6_multicast_add_group(br, port, &mld->mld_mca, vid,
1828+
src);
17901829
break;
17911830
case ICMPV6_MLD2_REPORT:
17921831
err = br_ip6_multicast_mld2_report(br, port, skb_trimmed, vid);
@@ -1795,7 +1834,8 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
17951834
err = br_ip6_multicast_query(br, port, skb_trimmed, vid);
17961835
break;
17971836
case ICMPV6_MGM_REDUCTION:
1798-
br_ip6_multicast_leave_group(br, port, &mld->mld_mca, vid);
1837+
src = eth_hdr(skb)->h_source;
1838+
br_ip6_multicast_leave_group(br, port, &mld->mld_mca, vid, src);
17991839
break;
18001840
}
18011841

0 commit comments

Comments
 (0)