@@ -190,6 +190,11 @@ void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
const struct in6_addr *daddr,
const struct in6_addr *solicited_addr,
bool router, bool solicited, bool override, bool inc_opt);
+void ndisc_fill_addr_option(struct sk_buff *skb, int type, const void *data);
+struct sk_buff *ndisc_alloc_skb(struct net_device *dev, int len);
+void ndisc_send_skb(struct sk_buff *skb,
+ const struct in6_addr *daddr,
+ const struct in6_addr *saddr);
void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target);
@@ -1651,6 +1651,104 @@ 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)
+{
+ struct sk_buff *skb;
+ struct nd_msg *msg;
+
+ /* TODO: How to replace source MAC address in the link layer header to
+ * be that of the device on behalf of which we are replying? That is
+ * needed to meet the BR_PROXYARP_WIFI expectations.
+ */
+ skb = ndisc_alloc_skb(dev, sizeof(*msg) + ndisc_opt_addr_space(dev));
+ if (!skb)
+ return;
+
+ 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
+ * addresses be the target ll addr and src_addr.
+ */
+ ndisc_fill_addr_option(skb, ND_OPT_TARGET_LL_ADDR, target_lladdr);
+ ndisc_send_skb(skb, daddr, solicited_addr);
+}
+
+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;
+ struct net_bridge_fdb_entry *f;
+
+ 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)) {
+ neigh_release(n);
+ return;
+ }
+
+ f = __br_fdb_get(br, n->ha, vid);
+ if (f && ((p->flags & BR_PROXYARP) ||
+ (f->dst && (f->dst->flags & BR_PROXYARP_WIFI)))) {
+ bool override = false, solicited = true;
+ bool dad = ipv6_addr_any(saddr);
+ const struct in6_addr *daddr_na = saddr;
+
+ if (dad && !ipv6_addr_is_solict_mult(daddr))
+ return;
+
+ if (dad) {
+ override = true;
+ solicited = false;
+ daddr_na = &in6addr_linklocal_allnodes;
+ }
+
+ br_ndisc_send_na(dev, daddr_na, &msg->target, n->ha, solicited,
+ override);
+ BR_INPUT_SKB_CB(skb)->proxyarp_replied = true;
+ }
+
+ neigh_release(n);
+}
+
static int br_multicast_ipv6_rcv(struct net_bridge *br,
struct net_bridge_port *port,
struct sk_buff *skb,
@@ -1671,9 +1769,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 +1781,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 +1817,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;
@@ -148,7 +148,7 @@ struct neigh_table nd_tbl = {
.gc_thresh3 = 1024,
};
-static void ndisc_fill_addr_option(struct sk_buff *skb, int type, void *data)
+void ndisc_fill_addr_option(struct sk_buff *skb, int type, const void *data)
{
int pad = ndisc_addr_option_pad(skb->dev->type);
int data_len = skb->dev->addr_len;
@@ -375,8 +375,7 @@ static void pndisc_destructor(struct pneigh_entry *n)
ipv6_dev_mc_dec(dev, &maddr);
}
-static struct sk_buff *ndisc_alloc_skb(struct net_device *dev,
- int len)
+struct sk_buff *ndisc_alloc_skb(struct net_device *dev, int len)
{
int hlen = LL_RESERVED_SPACE(dev);
int tlen = dev->needed_tailroom;
@@ -425,9 +424,9 @@ static void ip6_nd_hdr(struct sk_buff *skb,
hdr->daddr = *daddr;
}
-static void ndisc_send_skb(struct sk_buff *skb,
- const struct in6_addr *daddr,
- const struct in6_addr *saddr)
+void ndisc_send_skb(struct sk_buff *skb,
+ const struct in6_addr *daddr,
+ const struct in6_addr *saddr)
{
struct dst_entry *dst = skb_dst(skb);
struct net *net = dev_net(skb->dev);