diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index c465876..c30d3e7 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -30,6 +30,7 @@
 #include <net/ipv6.h>
 #include <net/mld.h>
 #include <net/ip6_checksum.h>
+#include <net/ip6_route.h>
 #include <net/addrconf.h>
 #endif
 
@@ -1651,6 +1652,252 @@ err_out:
 }
 
 #if IS_ENABLED(CONFIG_IPV6)
+void 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;
+
+	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);
+	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, skb, NULL, dst->dev,
+		      dest_hw ? dev_queue_xmit : dst_output);
+	if (!err) {
+		ICMP6MSGOUT_INC_STATS(net, idev, type);
+		ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUTMSGS);
+	}
+
+	rcu_read_unlock();
+	return;
+
+out:
+	kfree_skb(skb);
+}
+
+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;
+	}
+
+	br_ndisc_send_na(dev, daddr_na, &msg->target, n->ha, solicited,
+			 override, dest_hw);
+	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,
@@ -1671,9 +1918,9 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
 	ip6h = ipv6_hdr(skb);
 
 	/*
-	 * We're interested in MLD messages only.
+	 * For the interested messages:
 	 *  - Version is 6
-	 *  - MLD has always Router Alert hop-by-hop option
+	 *  - If MLD, always has Router Alert hop-by-hop option
 	 *  - But we do not support jumbrograms.
 	 */
 	if (ip6h->version != 6)
@@ -1683,8 +1930,7 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
 	if (!ipv6_addr_is_ll_all_nodes(&ip6h->daddr))
 		BR_INPUT_SKB_CB(skb)->mrouters_only = 1;
 
-	if (ip6h->nexthdr != IPPROTO_HOPOPTS ||
-	    ip6h->payload_len == 0)
+	if (ip6h->payload_len == 0)
 		return 0;
 
 	len = ntohs(ip6h->payload_len) + sizeof(*ip6h);
@@ -1720,7 +1966,14 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
 	case ICMPV6_MGM_REPORT:
 	case ICMPV6_MGM_REDUCTION:
 	case ICMPV6_MLD2_REPORT:
+		if (ip6h->nexthdr != IPPROTO_HOPOPTS) {
+			err = 0;
+			goto out;
+		}
 		break;
+	case NDISC_NEIGHBOUR_SOLICITATION:
+		br_do_proxy_ndisc(skb2, br, vid, port);
+		/* FALL THROUGH */
 	default:
 		err = 0;
 		goto out;
