--- zzzz-none-000/linux-4.4.60/net/bridge/br_multicast.c 2017-04-08 07:53:53.000000000 +0000 +++ scorpion-7490-727/linux-4.4.60/net/bridge/br_multicast.c 2021-02-04 17:41:59.000000000 +0000 @@ -33,6 +33,10 @@ #include #endif +#ifdef CONFIG_AVM_PA +#include +#endif + #include "br_private.h" static void br_multicast_start_querier(struct net_bridge *br, @@ -42,11 +46,13 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br, struct net_bridge_port *port, __be32 group, + const struct ethhdr *eth, __u16 vid); #if IS_ENABLED(CONFIG_IPV6) static void br_ip6_multicast_leave_group(struct net_bridge *br, struct net_bridge_port *port, const struct in6_addr *group, + const struct ethhdr *eth, __u16 vid); #endif unsigned int br_mdb_rehash_seq; @@ -218,14 +224,48 @@ return maxlen > elasticity ? -EINVAL : 0; } +static void free_recipients(struct net_bridge_port_group *p) +{ +#ifdef CONFIG_AVM_BRIDGE_MULTICAST_TO_UNICAST + struct net_bridge_group_recipient *recp, *temp; + + recp = container_of(p->free_start, struct net_bridge_group_recipient, list); + list_for_each_entry_safe_from(recp, temp, &p->recipients, list) + kfree(recp); +#endif +} + void br_multicast_free_pg(struct rcu_head *head) { struct net_bridge_port_group *p = container_of(head, struct net_bridge_port_group, rcu); + free_recipients(p); kfree(p); } +void delete_recipients(struct net_bridge_port_group *p) +{ +#ifdef CONFIG_AVM_BRIDGE_MULTICAST_TO_UNICAST + struct net_bridge_group_recipient *pos; + list_for_each_entry(pos, &p->recipients, list) { + hlist_del(&pos->fdb_list); + } + /* this works even if recipients is empty, free_recipients() can catch this */ + p->free_start = p->recipients.next; + INIT_LIST_HEAD_RCU(&p->recipients); +#endif +} + +/* must be called under the bridge's multicast lock */ +void br_multicast_delete_pg(struct net_bridge_port_group *p) +{ + hlist_del_init(&p->mglist); + del_timer(&p->timer); + delete_recipients(p); + call_rcu_bh(&p->rcu, br_multicast_free_pg); +} + static void br_multicast_free_group(struct rcu_head *head) { struct net_bridge_mdb_entry *mp = @@ -281,16 +321,20 @@ continue; rcu_assign_pointer(*pp, p->next); - hlist_del_init(&p->mglist); - del_timer(&p->timer); + br_multicast_delete_pg(p); br_mdb_notify(br->dev, p->port, &pg->addr, RTM_DELMDB, p->state); - call_rcu_bh(&p->rcu, br_multicast_free_pg); if (!mp->ports && !mp->mglist && netif_running(br->dev)) mod_timer(&mp->timer, jiffies); +#ifdef CONFIG_AVM_PA + /* Must re-learn multicast sessions because of removed port group, + * otherwise avm_pa continues to forward to that port */ + if (p->addr.proto == __constant_htons(ETH_P_IP)) + avm_pa_flush_multicast_sessions_for_group(p->addr.u.ip4); +#endif return; } @@ -665,14 +709,95 @@ p->state = state; rcu_assign_pointer(p->next, next); hlist_add_head(&p->mglist, &port->mglist); +#ifdef CONFIG_AVM_BRIDGE_MULTICAST_TO_UNICAST + INIT_LIST_HEAD(&p->recipients); +#endif setup_timer(&p->timer, br_multicast_port_group_expired, (unsigned long)p); return p; } +#ifdef CONFIG_AVM_BRIDGE_MULTICAST_TO_UNICAST +/* must be called under the bridge's multicast lock */ +static int add_group_recipient(struct net_bridge *br, + struct net_bridge_port_group *group, + const struct ethhdr *eth) +{ + struct net_bridge_group_recipient *p, *recp; + struct net_bridge_fdb_entry *fdb; + + /* Find the fdb entry that corresponds to the source address. + * Lookup could fail if the port is not in LEARNING state. + */ + fdb = __br_fdb_get(br, eth->h_source, group->addr.vid); + if (!fdb) + return -ENODEV; + + /* never add twice */ + list_for_each_entry(p, &group->recipients, list) { + if (fdb == rcu_dereference(p->recipient)) + return 0; + } + + recp = kzalloc(sizeof(*recp), GFP_ATOMIC); + if (unlikely(!recp)) + return -ENOMEM; + + recp->recipient = fdb; + recp->group = group; + recp->group->num_recipients++; + list_add_rcu(&recp->list, &recp->group->recipients); + /* To be able to remove this fdb from the recipents when it's freed the fdb + * has a list of all net_bridge_group_recipient it is part of */ + hlist_add_head(&recp->fdb_list, &fdb->pg_list); + return 0; +} + + +static void br_multicast_free_recipient(struct rcu_head *head) +{ + struct net_bridge_group_recipient *recp = + container_of(head, struct net_bridge_group_recipient, rcu); + + kfree(recp); +} + +/* Deletes the recipient from all lists and schedules the rcu callback to free it. + * must be called under the bridge's multicast lock */ +void br_multicast_delete_recipient(struct net_bridge_group_recipient *p) +{ + BUG_ON(p->group->num_recipients == 0); + + hlist_del(&p->fdb_list); + list_del_rcu(&p->list); + p->group->num_recipients--; + call_rcu_bh(&p->rcu, &br_multicast_free_recipient); +} + +/* must be called under the bridge's multicast lock */ +static int del_group_recipient(struct net_bridge *br, + struct net_bridge_port_group *group, + const struct ethhdr *eth) +{ + struct net_bridge_group_recipient *p, *temp; + struct net_bridge_fdb_entry *fdb; + + /* find the fdb entry that corresponds to the source address */ + fdb = __br_fdb_get(br, eth->h_source, group->addr.vid); + BUG_ON(fdb == NULL); + + list_for_each_entry_safe(p, temp, &group->recipients, list) { + if (fdb == p->recipient) + br_multicast_delete_recipient(p); + } + return 0; +} +#endif + static int br_multicast_add_group(struct net_bridge *br, struct net_bridge_port *port, - struct br_ip *group) + struct br_ip *group, + const struct ethhdr *eth) { struct net_bridge_mdb_entry *mp; struct net_bridge_port_group *p; @@ -712,6 +837,16 @@ br_mdb_notify(br->dev, port, group, RTM_NEWMDB, MDB_TEMPORARY); found: +#ifdef CONFIG_AVM_BRIDGE_MULTICAST_TO_UNICAST + add_group_recipient(br, p, eth); +#endif +#ifdef CONFIG_AVM_PA + /* Must re-learn multicast sessions because of newly added port group, + * otherwise avm_pa continues to forward only to existing egress ports and + * not this one */ + if (group->proto == __constant_htons(ETH_P_IP)) + avm_pa_flush_multicast_sessions_for_group(group->u.ip4); +#endif mod_timer(&p->timer, now + br->multicast_membership_interval); out: err = 0; @@ -724,6 +859,7 @@ static int br_ip4_multicast_add_group(struct net_bridge *br, struct net_bridge_port *port, __be32 group, + const struct ethhdr *eth, __u16 vid) { struct br_ip br_group; @@ -735,13 +871,14 @@ br_group.proto = htons(ETH_P_IP); br_group.vid = vid; - return br_multicast_add_group(br, port, &br_group); + return br_multicast_add_group(br, port, &br_group, eth); } #if IS_ENABLED(CONFIG_IPV6) static int br_ip6_multicast_add_group(struct net_bridge *br, struct net_bridge_port *port, const struct in6_addr *group, + const struct ethhdr *eth, __u16 vid) { struct br_ip br_group; @@ -753,7 +890,7 @@ br_group.proto = htons(ETH_P_IPV6); br_group.vid = vid; - return br_multicast_add_group(br, port, &br_group); + return br_multicast_add_group(br, port, &br_group, eth); } #endif @@ -832,7 +969,7 @@ if (port) { skb->dev = port->dev; - NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, + BR_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, dev_net(port->dev), NULL, skb, NULL, skb->dev, br_dev_queue_push_xmit); } else { @@ -916,6 +1053,10 @@ void br_multicast_add_port(struct net_bridge_port *port) { +#ifdef CONFIG_AVM_BRIDGE_MULTICAST_TO_UNICAST + port->multicast_to_unicast_threshold = + CONFIG_AVM_BRIDGE_MULTICAST_TO_UNICAST_DEFAULT_THRESHOLD; +#endif port->multicast_router = 1; setup_timer(&port->multicast_router_timer, br_multicast_router_expired, @@ -1001,6 +1142,7 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br, struct net_bridge_port *port, struct sk_buff *skb, + const struct ethhdr *eth, u16 vid) { struct igmpv3_report *ih; @@ -1046,9 +1188,9 @@ if ((type == IGMPV3_CHANGE_TO_INCLUDE || type == IGMPV3_MODE_IS_INCLUDE) && ntohs(grec->grec_nsrcs) == 0) { - br_ip4_multicast_leave_group(br, port, group, vid); + br_ip4_multicast_leave_group(br, port, group, eth, vid); } else { - err = br_ip4_multicast_add_group(br, port, group, vid); + err = br_ip4_multicast_add_group(br, port, group, eth, vid); if (err) break; } @@ -1061,6 +1203,7 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br, struct net_bridge_port *port, struct sk_buff *skb, + const struct ethhdr *eth, u16 vid) { struct icmp6hdr *icmp6h; @@ -1113,11 +1256,9 @@ if ((grec->grec_type == MLD2_CHANGE_TO_INCLUDE || grec->grec_type == MLD2_MODE_IS_INCLUDE) && ntohs(*nsrcs) == 0) { - br_ip6_multicast_leave_group(br, port, &grec->grec_mca, - vid); + br_ip6_multicast_leave_group(br, port, &grec->grec_mca, eth, vid); } else { - err = br_ip6_multicast_add_group(br, port, - &grec->grec_mca, vid); + err = br_ip6_multicast_add_group(br, port, &grec->grec_mca, eth, vid); if (err) break; } @@ -1431,6 +1572,7 @@ br_multicast_leave_group(struct net_bridge *br, struct net_bridge_port *port, struct br_ip *group, + const struct ethhdr *eth, struct bridge_mcast_other_query *other_query, struct bridge_mcast_own_query *own_query) { @@ -1460,9 +1602,7 @@ continue; rcu_assign_pointer(*pp, p->next); - hlist_del_init(&p->mglist); - del_timer(&p->timer); - call_rcu_bh(&p->rcu, br_multicast_free_pg); + br_multicast_delete_pg(p); br_mdb_notify(br->dev, port, group, RTM_DELMDB, p->state); @@ -1522,6 +1662,10 @@ if (p->port != port) continue; +#ifdef CONFIG_AVM_BRIDGE_MULTICAST_TO_UNICAST + del_group_recipient(br, p, eth); +#endif + if (!hlist_unhashed(&p->mglist) && (timer_pending(&p->timer) ? time_after(p->timer.expires, time) : @@ -1538,6 +1682,7 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br, struct net_bridge_port *port, __be32 group, + const struct ethhdr *eth, __u16 vid) { struct br_ip br_group; @@ -1552,7 +1697,7 @@ br_group.proto = htons(ETH_P_IP); br_group.vid = vid; - br_multicast_leave_group(br, port, &br_group, &br->ip4_other_query, + br_multicast_leave_group(br, port, &br_group, eth, &br->ip4_other_query, own_query); } @@ -1560,6 +1705,7 @@ static void br_ip6_multicast_leave_group(struct net_bridge *br, struct net_bridge_port *port, const struct in6_addr *group, + const struct ethhdr *eth, __u16 vid) { struct br_ip br_group; @@ -1574,7 +1720,7 @@ br_group.proto = htons(ETH_P_IPV6); br_group.vid = vid; - br_multicast_leave_group(br, port, &br_group, &br->ip6_other_query, + br_multicast_leave_group(br, port, &br_group, eth, &br->ip6_other_query, own_query); } #endif @@ -1585,10 +1731,12 @@ u16 vid) { struct sk_buff *skb_trimmed = NULL; + const struct ethhdr *eth; struct igmphdr *ih; int err; err = ip_mc_check_igmp(skb, &skb_trimmed); + eth = eth_hdr(skb); if (err == -ENOMSG) { if (!ipv4_is_local_multicast(ip_hdr(skb)->daddr)) @@ -1605,16 +1753,16 @@ case IGMP_HOST_MEMBERSHIP_REPORT: case IGMPV2_HOST_MEMBERSHIP_REPORT: BR_INPUT_SKB_CB(skb)->mrouters_only = 1; - err = br_ip4_multicast_add_group(br, port, ih->group, vid); + err = br_ip4_multicast_add_group(br, port, ih->group, eth, vid); break; case IGMPV3_HOST_MEMBERSHIP_REPORT: - err = br_ip4_multicast_igmp3_report(br, port, skb_trimmed, vid); + err = br_ip4_multicast_igmp3_report(br, port, skb_trimmed, eth, vid); break; case IGMP_HOST_MEMBERSHIP_QUERY: err = br_ip4_multicast_query(br, port, skb_trimmed, vid); break; case IGMP_HOST_LEAVE_MESSAGE: - br_ip4_multicast_leave_group(br, port, ih->group, vid); + br_ip4_multicast_leave_group(br, port, ih->group, eth, vid); break; } @@ -1631,10 +1779,12 @@ u16 vid) { struct sk_buff *skb_trimmed = NULL; + const struct ethhdr *eth; struct mld_msg *mld; int err; err = ipv6_mc_check_mld(skb, &skb_trimmed); + eth = eth_hdr(skb); if (err == -ENOMSG) { if (!ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) @@ -1650,16 +1800,16 @@ switch (mld->mld_type) { case ICMPV6_MGM_REPORT: BR_INPUT_SKB_CB(skb)->mrouters_only = 1; - err = br_ip6_multicast_add_group(br, port, &mld->mld_mca, vid); + err = br_ip6_multicast_add_group(br, port, &mld->mld_mca, eth, vid); break; case ICMPV6_MLD2_REPORT: - err = br_ip6_multicast_mld2_report(br, port, skb_trimmed, vid); + err = br_ip6_multicast_mld2_report(br, port, skb_trimmed, eth, vid); break; case ICMPV6_MGM_QUERY: err = br_ip6_multicast_query(br, port, skb_trimmed, vid); break; case ICMPV6_MGM_REDUCTION: - br_ip6_multicast_leave_group(br, port, &mld->mld_mca, vid); + br_ip6_multicast_leave_group(br, port, &mld->mld_mca, eth, vid); break; } @@ -1806,6 +1956,10 @@ br->mdb = NULL; +#ifdef CONFIG_AVM_PA + avm_pa_flush_multicast_sessions(); +#endif + ver = mdb->ver; for (i = 0; i < mdb->max; i++) { hlist_for_each_entry_safe(mp, n, &mdb->mhash[i],