--- zzzz-none-000/linux-4.4.271/net/bridge/br_multicast.c 2021-06-03 06:22:09.000000000 +0000 +++ maple-fsgw-759/linux-4.4.271/net/bridge/br_multicast.c 2023-12-20 10:37:40.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,12 +45,14 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br, struct net_bridge_port *port, __be32 group, - __u16 vid); + __u16 vid, + const unsigned char *src); + #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, - __u16 vid); + __u16 vid, const unsigned char *src); #endif unsigned int br_mdb_rehash_seq; @@ -291,6 +296,10 @@ 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; } @@ -652,7 +661,8 @@ struct net_bridge_port *port, struct br_ip *group, struct net_bridge_port_group __rcu *next, - unsigned char state) + unsigned char state, + const unsigned char *src) { struct net_bridge_port_group *p; @@ -667,12 +677,32 @@ hlist_add_head(&p->mglist, &port->mglist); setup_timer(&p->timer, br_multicast_port_group_expired, (unsigned long)p); + + if (src) + memcpy(p->eth_addr, src, ETH_ALEN); + else + memset(p->eth_addr, 0xff, ETH_ALEN); + return p; } +static bool br_port_group_equal(struct net_bridge_port_group *p, + struct net_bridge_port *port, + const unsigned char *src) +{ + if (p->port != port) + return false; + + if (!(port->flags & BR_MULTICAST_TO_UNICAST)) + return true; + + return ether_addr_equal(src, p->eth_addr); +} + static int br_multicast_add_group(struct net_bridge *br, struct net_bridge_port *port, - struct br_ip *group) + struct br_ip *group, + const unsigned char *src) { struct net_bridge_mdb_entry *mp; struct net_bridge_port_group *p; @@ -699,19 +729,24 @@ for (pp = &mp->ports; (p = mlock_dereference(*pp, br)) != NULL; pp = &p->next) { - if (p->port == port) + if (br_port_group_equal(p, port, src)) goto found; if ((unsigned long)p->port < (unsigned long)port) break; } - p = br_multicast_new_port_group(port, group, *pp, MDB_TEMPORARY); + p = br_multicast_new_port_group(port, group, *pp, MDB_TEMPORARY, src); if (unlikely(!p)) goto err; rcu_assign_pointer(*pp, p); br_mdb_notify(br->dev, port, group, RTM_NEWMDB, MDB_TEMPORARY); found: + /* 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,7 +759,8 @@ static int br_ip4_multicast_add_group(struct net_bridge *br, struct net_bridge_port *port, __be32 group, - __u16 vid) + __u16 vid, + const unsigned char *src) { struct br_ip br_group; @@ -735,14 +771,15 @@ 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, src); } #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, - __u16 vid) + __u16 vid, + const unsigned char *src) { struct br_ip br_group; @@ -753,7 +790,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, src); } #endif @@ -832,7 +869,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 { @@ -1003,6 +1040,7 @@ struct sk_buff *skb, u16 vid) { + const unsigned char *src; struct igmpv3_report *ih; struct igmpv3_grec *grec; int i; @@ -1045,12 +1083,14 @@ continue; } + src = eth_hdr(skb)->h_source; 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, vid, src); } else { - err = br_ip4_multicast_add_group(br, port, group, vid); + err = br_ip4_multicast_add_group(br, port, group, vid, + src); if (err) break; } @@ -1065,6 +1105,7 @@ struct sk_buff *skb, u16 vid) { + const unsigned char *src; struct icmp6hdr *icmp6h; struct mld2_grec *grec; int i; @@ -1115,14 +1156,16 @@ continue; } + src = eth_hdr(skb)->h_source; if ((grec->grec_type == MLD2_CHANGE_TO_INCLUDE || grec->grec_type == MLD2_MODE_IS_INCLUDE) && nsrcs == 0) { br_ip6_multicast_leave_group(br, port, &grec->grec_mca, - vid); + vid, src); } else { err = br_ip6_multicast_add_group(br, port, - &grec->grec_mca, vid); + &grec->grec_mca, vid, + src); if (err) break; } @@ -1436,7 +1479,8 @@ struct net_bridge_port *port, struct br_ip *group, struct bridge_mcast_other_query *other_query, - struct bridge_mcast_own_query *own_query) + struct bridge_mcast_own_query *own_query, + const unsigned char *src) { struct net_bridge_mdb_htable *mdb; struct net_bridge_mdb_entry *mp; @@ -1460,7 +1504,7 @@ for (pp = &mp->ports; (p = mlock_dereference(*pp, br)) != NULL; pp = &p->next) { - if (p->port != port) + if (!br_port_group_equal(p, port, src)) continue; rcu_assign_pointer(*pp, p->next); @@ -1491,7 +1535,7 @@ for (p = mlock_dereference(mp->ports, br); p != NULL; p = mlock_dereference(p->next, br)) { - if (p->port != port) + if (!br_port_group_equal(p, port, src)) continue; if (!hlist_unhashed(&p->mglist) && @@ -1542,7 +1586,8 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br, struct net_bridge_port *port, __be32 group, - __u16 vid) + __u16 vid, + const unsigned char *src) { struct br_ip br_group; struct bridge_mcast_own_query *own_query; @@ -1557,14 +1602,15 @@ br_group.vid = vid; br_multicast_leave_group(br, port, &br_group, &br->ip4_other_query, - own_query); + own_query, src); } #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, - __u16 vid) + __u16 vid, + const unsigned char *src) { struct br_ip br_group; struct bridge_mcast_own_query *own_query; @@ -1579,7 +1625,7 @@ br_group.vid = vid; br_multicast_leave_group(br, port, &br_group, &br->ip6_other_query, - own_query); + own_query, src); } #endif @@ -1589,6 +1635,7 @@ u16 vid) { struct sk_buff *skb_trimmed = NULL; + const unsigned char *src; struct igmphdr *ih; int err; @@ -1602,6 +1649,7 @@ return err; } + src = eth_hdr(skb)->h_source; BR_INPUT_SKB_CB(skb)->igmp = 1; ih = igmp_hdr(skb); @@ -1609,7 +1657,7 @@ 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, vid, src); break; case IGMPV3_HOST_MEMBERSHIP_REPORT: err = br_ip4_multicast_igmp3_report(br, port, skb_trimmed, vid); @@ -1618,7 +1666,7 @@ 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, vid, src); break; } @@ -1629,17 +1677,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 unsigned char *src; struct mld_msg *mld; int err; err = ipv6_mc_check_mld(skb, &skb_trimmed); + 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)) BR_INPUT_SKB_CB(skb)->mrouters_only = 1; @@ -1653,8 +1963,10 @@ switch (mld->mld_type) { case ICMPV6_MGM_REPORT: + src = eth_hdr(skb)->h_source; 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, vid, + src); break; case ICMPV6_MLD2_REPORT: err = br_ip6_multicast_mld2_report(br, port, skb_trimmed, vid); @@ -1663,7 +1975,8 @@ 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); + src = eth_hdr(skb)->h_source; + br_ip6_multicast_leave_group(br, port, &mld->mld_mca, vid, src); break; } @@ -1810,6 +2123,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],