--- zzzz-none-000/linux-4.4.271/net/bridge/br_multicast.c 2021-06-03 06:22:09.000000000 +0000 +++ hawkeye-5590-750/linux-4.4.271/net/bridge/br_multicast.c 2023-04-19 10:22:30.000000000 +0000 @@ -30,9 +30,12 @@ #include #include #include +#include #include #endif +#include + #include "br_private.h" static void br_multicast_start_querier(struct net_bridge *br, @@ -42,11 +45,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 +223,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 +320,18 @@ 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); + /* Must re-learn multicast sessions because of removed port group, + * otherwise avm_pa continues to forward to that port */ + if (p->addr.proto == htons(ETH_P_IP)) + avm_pa_flush_multicast_sessions_for_group(p->addr.u.ip4); return; } @@ -665,14 +706,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 +834,14 @@ 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 + /* 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 == htons(ETH_P_IP)) + avm_pa_flush_multicast_sessions_for_group(group->u.ip4); mod_timer(&p->timer, now + br->multicast_membership_interval); out: err = 0; @@ -724,6 +854,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 +866,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 +885,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 +964,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 +1048,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 +1137,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; @@ -1048,9 +1185,9 @@ if ((type == IGMPV3_CHANGE_TO_INCLUDE || type == IGMPV3_MODE_IS_INCLUDE) && 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; } @@ -1063,6 +1200,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; @@ -1119,10 +1257,9 @@ grec->grec_type == MLD2_MODE_IS_INCLUDE) && nsrcs == 0) { br_ip6_multicast_leave_group(br, port, &grec->grec_mca, - vid); + 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; } @@ -1435,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) { @@ -1464,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); @@ -1526,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) : @@ -1542,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; @@ -1556,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); } @@ -1564,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; @@ -1578,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 @@ -1589,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)) @@ -1609,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; } @@ -1629,16 +1773,279 @@ } #if IS_ENABLED(CONFIG_IPV6) +static int br_ndisc_send_na_finish(struct net *net, struct sock *sk, + struct sk_buff *skb) +{ + return dev_queue_xmit(skb); +} + +static int br_ndisc_send_na(struct net_device *dev, + const struct in6_addr *daddr, + const struct in6_addr *solicited_addr, + const u8 *target_lladdr, bool solicited, + bool override, const u8 *dest_hw) +{ + struct sk_buff *skb; + struct nd_msg *msg; + int hlen = LL_RESERVED_SPACE(dev); + int tlen = dev->needed_tailroom; + struct dst_entry *dst; + struct net *net = dev_net(dev); + struct sock *sk = net->ipv6.ndisc_sk; + struct inet6_dev *idev; + int err; + struct ipv6hdr *hdr; + struct icmp6hdr *icmp6h; + u8 type; + const struct in6_addr *saddr = solicited_addr; + int pad, data_len, space; + u8 *opt; + + skb = alloc_skb(hlen + sizeof(struct ipv6hdr) + sizeof(*msg) + + ndisc_opt_addr_space(dev) + tlen, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + skb->protocol = htons(ETH_P_IPV6); + skb->dev = dev; + + skb_reserve(skb, hlen + sizeof(struct ipv6hdr)); + skb_reset_transport_header(skb); + + /* Manually assign socket ownership as we avoid calling + * sock_alloc_send_pskb() to bypass wmem buffer limits + */ + skb_set_owner_w(skb, sk); + + msg = (struct nd_msg *)skb_put(skb, sizeof(*msg)); + *msg = (struct nd_msg) { + .icmph = { + .icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT, + .icmp6_router = false, + .icmp6_solicited = solicited, + .icmp6_override = override, + }, + .target = *solicited_addr, + }; + + /* We are replying on behalf of other entity. Let that entity's + * address be the target ll addr and src_addr. + */ + pad = ndisc_addr_option_pad(skb->dev->type); + data_len = skb->dev->addr_len; + space = ndisc_opt_addr_space(skb->dev); + opt = skb_put(skb, space); + + opt[0] = ND_OPT_TARGET_LL_ADDR; + opt[1] = space >> 3; + + memset(opt + 2, 0, pad); + opt += pad; + space -= pad; + + memcpy(opt + 2, target_lladdr, dev->addr_len); + data_len += 2; + opt += data_len; + space -= data_len; + if (space > 0) + memset(opt, 0, space); + + dst = skb_dst(skb); + icmp6h = icmp6_hdr(skb); + + type = icmp6h->icmp6_type; + + if (!dst) { + struct flowi6 fl6; + + icmpv6_flow_init(sk, &fl6, type, saddr, daddr, + skb->dev->ifindex); + dst = icmp6_dst_alloc(skb->dev, &fl6); + if (IS_ERR(dst)) + goto out; + + skb_dst_set(skb, dst); + } + + icmp6h->icmp6_cksum = csum_ipv6_magic(saddr, daddr, skb->len, + IPPROTO_ICMPV6, + csum_partial(icmp6h, + skb->len, 0)); + + skb_push(skb, sizeof(*hdr)); + skb_reset_network_header(skb); + hdr = ipv6_hdr(skb); + + ip6_flow_hdr(hdr, 0, 0); + + hdr->payload_len = htons(skb->len - sizeof(*hdr)); + hdr->nexthdr = IPPROTO_ICMPV6; + hdr->hop_limit = inet6_sk(sk)->hop_limit; + + hdr->saddr = *saddr; + hdr->daddr = *daddr; + + /* We are replying on behalf of another entity. Use that entity's + * address as the source link layer address if we have all the needed + * information to build the link layer header. + */ + if (dest_hw && + dev_hard_header(skb, dev, ETH_P_IPV6, dest_hw, target_lladdr, + skb->len) < 0) + goto out; + + rcu_read_lock(); + idev = __in6_dev_get(dst->dev); + IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUT, skb->len); + + err = NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT, net, sk, skb, NULL, + dst->dev, dest_hw ? br_ndisc_send_na_finish : dst_output); + + if (!err) { + ICMP6MSGOUT_INC_STATS(net, idev, type); + ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUTMSGS); + } + + rcu_read_unlock(); + return 0; + +out: + kfree_skb(skb); + return -EINVAL; +} + +static const u8 *br_get_ndisc_lladdr(const u8 *opt, int opt_len, + unsigned int alen) +{ + const struct nd_opt_hdr *nd_opt = (const struct nd_opt_hdr *)opt; + + while (opt_len > sizeof(struct nd_opt_hdr)) { + int l; + + l = nd_opt->nd_opt_len << 3; + if (opt_len < l || l == 0) + return NULL; + + if (nd_opt->nd_opt_type == ND_OPT_SOURCE_LL_ADDR) { + if (l >= 2 + alen) + return (const u8 *)(nd_opt + 1); + } + + opt_len -= l; + nd_opt = ((void *)nd_opt) + l; + } + + return NULL; +} + +static void br_do_proxy_ndisc(struct sk_buff *skb, struct net_bridge *br, + u16 vid, struct net_bridge_port *p) +{ + struct net_device *dev = br->dev; + struct nd_msg *msg; + const struct ipv6hdr *iphdr; + const struct in6_addr *saddr, *daddr; + struct neighbour *n, *n_sender = NULL; + struct net_bridge_fdb_entry *f; + int ndoptlen; + bool override = false, solicited = true; + bool dad; + const struct in6_addr *daddr_na; + const u8 *dest_hw = NULL; + + BR_INPUT_SKB_CB(skb)->proxyarp_replied = false; + + if (!p) + return; + + if (!pskb_may_pull(skb, skb->len)) + return; + + iphdr = ipv6_hdr(skb); + saddr = &iphdr->saddr; + daddr = &iphdr->daddr; + + msg = (struct nd_msg *)skb_transport_header(skb); + if (msg->icmph.icmp6_code != 0 || + msg->icmph.icmp6_type != NDISC_NEIGHBOUR_SOLICITATION) + return; + + if (ipv6_addr_loopback(daddr) || + ipv6_addr_is_multicast(&msg->target)) + return; + + n = neigh_lookup(&nd_tbl, &msg->target, dev); + if (!n) + return; + + if (!(n->nud_state & NUD_VALID)) + goto out; + + f = __br_fdb_get(br, n->ha, vid); + if (!f) + goto out; + + if (!(p->flags & BR_PROXYARP) && + !(f->dst && (f->dst->flags & BR_PROXYARP_WIFI))) + goto out; + + dad = ipv6_addr_any(saddr); + daddr_na = saddr; + + if (dad && !ipv6_addr_is_solict_mult(daddr)) + goto out; + + if (dad) { + override = true; + solicited = false; + daddr_na = &in6addr_linklocal_allnodes; + } + + if (!(p->flags & BR_PROXYARP)) { + ndoptlen = skb_tail_pointer(skb) - + (skb_transport_header(skb) + + offsetof(struct nd_msg, opt)); + dest_hw = br_get_ndisc_lladdr(msg->opt, ndoptlen, + dev->addr_len); + if (!dest_hw && !dad) { + n_sender = neigh_lookup(&nd_tbl, saddr, dev); + if (n_sender) + dest_hw = n_sender->ha; + } + + if (dest_hw && is_multicast_ether_addr(dest_hw)) + dest_hw = NULL; + } + + if (br_ndisc_send_na(dev, daddr_na, &msg->target, n->ha, solicited, + override, dest_hw)) + goto out; + + BR_INPUT_SKB_CB(skb)->proxyarp_replied = true; + +out: + neigh_release(n); + if (n_sender) + neigh_release(n_sender); +} + static int br_multicast_ipv6_rcv(struct net_bridge *br, struct net_bridge_port *port, struct sk_buff *skb, 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 && + icmp6_hdr(skb)->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) { + br_do_proxy_ndisc(skb, br, vid, port); + } if (err == -ENOMSG) { if (!ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) @@ -1654,16 +2061,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; } @@ -1810,6 +2217,8 @@ br->mdb = NULL; + avm_pa_flush_multicast_sessions(); + ver = mdb->ver; for (i = 0; i < mdb->max; i++) { hlist_for_each_entry_safe(mp, n, &mdb->mhash[i],