162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Handle bridge arp/nd proxy/suppress 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2017 Cumulus Networks 662306a36Sopenharmony_ci * Copyright (c) 2017 Roopa Prabhu <roopa@cumulusnetworks.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Authors: 962306a36Sopenharmony_ci * Roopa Prabhu <roopa@cumulusnetworks.com> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <linux/netdevice.h> 1462306a36Sopenharmony_ci#include <linux/etherdevice.h> 1562306a36Sopenharmony_ci#include <linux/neighbour.h> 1662306a36Sopenharmony_ci#include <net/arp.h> 1762306a36Sopenharmony_ci#include <linux/if_vlan.h> 1862306a36Sopenharmony_ci#include <linux/inetdevice.h> 1962306a36Sopenharmony_ci#include <net/addrconf.h> 2062306a36Sopenharmony_ci#include <net/ipv6_stubs.h> 2162306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 2262306a36Sopenharmony_ci#include <net/ip6_checksum.h> 2362306a36Sopenharmony_ci#endif 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include "br_private.h" 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_civoid br_recalculate_neigh_suppress_enabled(struct net_bridge *br) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci struct net_bridge_port *p; 3062306a36Sopenharmony_ci bool neigh_suppress = false; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci list_for_each_entry(p, &br->port_list, list) { 3362306a36Sopenharmony_ci if (p->flags & (BR_NEIGH_SUPPRESS | BR_NEIGH_VLAN_SUPPRESS)) { 3462306a36Sopenharmony_ci neigh_suppress = true; 3562306a36Sopenharmony_ci break; 3662306a36Sopenharmony_ci } 3762306a36Sopenharmony_ci } 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci br_opt_toggle(br, BROPT_NEIGH_SUPPRESS_ENABLED, neigh_suppress); 4062306a36Sopenharmony_ci} 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_INET) 4362306a36Sopenharmony_cistatic void br_arp_send(struct net_bridge *br, struct net_bridge_port *p, 4462306a36Sopenharmony_ci struct net_device *dev, __be32 dest_ip, __be32 src_ip, 4562306a36Sopenharmony_ci const unsigned char *dest_hw, 4662306a36Sopenharmony_ci const unsigned char *src_hw, 4762306a36Sopenharmony_ci const unsigned char *target_hw, 4862306a36Sopenharmony_ci __be16 vlan_proto, u16 vlan_tci) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci struct net_bridge_vlan_group *vg; 5162306a36Sopenharmony_ci struct sk_buff *skb; 5262306a36Sopenharmony_ci u16 pvid; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci netdev_dbg(dev, "arp send dev %s dst %pI4 dst_hw %pM src %pI4 src_hw %pM\n", 5562306a36Sopenharmony_ci dev->name, &dest_ip, dest_hw, &src_ip, src_hw); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci if (!vlan_tci) { 5862306a36Sopenharmony_ci arp_send(ARPOP_REPLY, ETH_P_ARP, dest_ip, dev, src_ip, 5962306a36Sopenharmony_ci dest_hw, src_hw, target_hw); 6062306a36Sopenharmony_ci return; 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci skb = arp_create(ARPOP_REPLY, ETH_P_ARP, dest_ip, dev, src_ip, 6462306a36Sopenharmony_ci dest_hw, src_hw, target_hw); 6562306a36Sopenharmony_ci if (!skb) 6662306a36Sopenharmony_ci return; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci if (p) 6962306a36Sopenharmony_ci vg = nbp_vlan_group_rcu(p); 7062306a36Sopenharmony_ci else 7162306a36Sopenharmony_ci vg = br_vlan_group_rcu(br); 7262306a36Sopenharmony_ci pvid = br_get_pvid(vg); 7362306a36Sopenharmony_ci if (pvid == (vlan_tci & VLAN_VID_MASK)) 7462306a36Sopenharmony_ci vlan_tci = 0; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (vlan_tci) 7762306a36Sopenharmony_ci __vlan_hwaccel_put_tag(skb, vlan_proto, vlan_tci); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (p) { 8062306a36Sopenharmony_ci arp_xmit(skb); 8162306a36Sopenharmony_ci } else { 8262306a36Sopenharmony_ci skb_reset_mac_header(skb); 8362306a36Sopenharmony_ci __skb_pull(skb, skb_network_offset(skb)); 8462306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_UNNECESSARY; 8562306a36Sopenharmony_ci skb->pkt_type = PACKET_HOST; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci netif_rx(skb); 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic int br_chk_addr_ip(struct net_device *dev, 9262306a36Sopenharmony_ci struct netdev_nested_priv *priv) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci __be32 ip = *(__be32 *)priv->data; 9562306a36Sopenharmony_ci struct in_device *in_dev; 9662306a36Sopenharmony_ci __be32 addr = 0; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci in_dev = __in_dev_get_rcu(dev); 9962306a36Sopenharmony_ci if (in_dev) 10062306a36Sopenharmony_ci addr = inet_confirm_addr(dev_net(dev), in_dev, 0, ip, 10162306a36Sopenharmony_ci RT_SCOPE_HOST); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci if (addr == ip) 10462306a36Sopenharmony_ci return 1; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci return 0; 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic bool br_is_local_ip(struct net_device *dev, __be32 ip) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci struct netdev_nested_priv priv = { 11262306a36Sopenharmony_ci .data = (void *)&ip, 11362306a36Sopenharmony_ci }; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci if (br_chk_addr_ip(dev, &priv)) 11662306a36Sopenharmony_ci return true; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci /* check if ip is configured on upper dev */ 11962306a36Sopenharmony_ci if (netdev_walk_all_upper_dev_rcu(dev, br_chk_addr_ip, &priv)) 12062306a36Sopenharmony_ci return true; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci return false; 12362306a36Sopenharmony_ci} 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_civoid br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, 12662306a36Sopenharmony_ci u16 vid, struct net_bridge_port *p) 12762306a36Sopenharmony_ci{ 12862306a36Sopenharmony_ci struct net_device *dev = br->dev; 12962306a36Sopenharmony_ci struct net_device *vlandev = dev; 13062306a36Sopenharmony_ci struct neighbour *n; 13162306a36Sopenharmony_ci struct arphdr *parp; 13262306a36Sopenharmony_ci u8 *arpptr, *sha; 13362306a36Sopenharmony_ci __be32 sip, tip; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci BR_INPUT_SKB_CB(skb)->proxyarp_replied = 0; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if ((dev->flags & IFF_NOARP) || 13862306a36Sopenharmony_ci !pskb_may_pull(skb, arp_hdr_len(dev))) 13962306a36Sopenharmony_ci return; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci parp = arp_hdr(skb); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci if (parp->ar_pro != htons(ETH_P_IP) || 14462306a36Sopenharmony_ci parp->ar_hln != dev->addr_len || 14562306a36Sopenharmony_ci parp->ar_pln != 4) 14662306a36Sopenharmony_ci return; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci arpptr = (u8 *)parp + sizeof(struct arphdr); 14962306a36Sopenharmony_ci sha = arpptr; 15062306a36Sopenharmony_ci arpptr += dev->addr_len; /* sha */ 15162306a36Sopenharmony_ci memcpy(&sip, arpptr, sizeof(sip)); 15262306a36Sopenharmony_ci arpptr += sizeof(sip); 15362306a36Sopenharmony_ci arpptr += dev->addr_len; /* tha */ 15462306a36Sopenharmony_ci memcpy(&tip, arpptr, sizeof(tip)); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (ipv4_is_loopback(tip) || 15762306a36Sopenharmony_ci ipv4_is_multicast(tip)) 15862306a36Sopenharmony_ci return; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci if (br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED)) { 16162306a36Sopenharmony_ci if (br_is_neigh_suppress_enabled(p, vid)) 16262306a36Sopenharmony_ci return; 16362306a36Sopenharmony_ci if (parp->ar_op != htons(ARPOP_RREQUEST) && 16462306a36Sopenharmony_ci parp->ar_op != htons(ARPOP_RREPLY) && 16562306a36Sopenharmony_ci (ipv4_is_zeronet(sip) || sip == tip)) { 16662306a36Sopenharmony_ci /* prevent flooding to neigh suppress ports */ 16762306a36Sopenharmony_ci BR_INPUT_SKB_CB(skb)->proxyarp_replied = 1; 16862306a36Sopenharmony_ci return; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci if (parp->ar_op != htons(ARPOP_REQUEST)) 17362306a36Sopenharmony_ci return; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci if (vid != 0) { 17662306a36Sopenharmony_ci vlandev = __vlan_find_dev_deep_rcu(br->dev, skb->vlan_proto, 17762306a36Sopenharmony_ci vid); 17862306a36Sopenharmony_ci if (!vlandev) 17962306a36Sopenharmony_ci return; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci if (br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED) && 18362306a36Sopenharmony_ci br_is_local_ip(vlandev, tip)) { 18462306a36Sopenharmony_ci /* its our local ip, so don't proxy reply 18562306a36Sopenharmony_ci * and don't forward to neigh suppress ports 18662306a36Sopenharmony_ci */ 18762306a36Sopenharmony_ci BR_INPUT_SKB_CB(skb)->proxyarp_replied = 1; 18862306a36Sopenharmony_ci return; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci n = neigh_lookup(&arp_tbl, &tip, vlandev); 19262306a36Sopenharmony_ci if (n) { 19362306a36Sopenharmony_ci struct net_bridge_fdb_entry *f; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if (!(READ_ONCE(n->nud_state) & NUD_VALID)) { 19662306a36Sopenharmony_ci neigh_release(n); 19762306a36Sopenharmony_ci return; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci f = br_fdb_find_rcu(br, n->ha, vid); 20162306a36Sopenharmony_ci if (f) { 20262306a36Sopenharmony_ci bool replied = false; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci if ((p && (p->flags & BR_PROXYARP)) || 20562306a36Sopenharmony_ci (f->dst && (f->dst->flags & BR_PROXYARP_WIFI)) || 20662306a36Sopenharmony_ci br_is_neigh_suppress_enabled(f->dst, vid)) { 20762306a36Sopenharmony_ci if (!vid) 20862306a36Sopenharmony_ci br_arp_send(br, p, skb->dev, sip, tip, 20962306a36Sopenharmony_ci sha, n->ha, sha, 0, 0); 21062306a36Sopenharmony_ci else 21162306a36Sopenharmony_ci br_arp_send(br, p, skb->dev, sip, tip, 21262306a36Sopenharmony_ci sha, n->ha, sha, 21362306a36Sopenharmony_ci skb->vlan_proto, 21462306a36Sopenharmony_ci skb_vlan_tag_get(skb)); 21562306a36Sopenharmony_ci replied = true; 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci /* If we have replied or as long as we know the 21962306a36Sopenharmony_ci * mac, indicate to arp replied 22062306a36Sopenharmony_ci */ 22162306a36Sopenharmony_ci if (replied || 22262306a36Sopenharmony_ci br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED)) 22362306a36Sopenharmony_ci BR_INPUT_SKB_CB(skb)->proxyarp_replied = 1; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci neigh_release(n); 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci#endif 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 23262306a36Sopenharmony_cistruct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb, struct nd_msg *msg) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci struct nd_msg *m; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci m = skb_header_pointer(skb, skb_network_offset(skb) + 23762306a36Sopenharmony_ci sizeof(struct ipv6hdr), sizeof(*msg), msg); 23862306a36Sopenharmony_ci if (!m) 23962306a36Sopenharmony_ci return NULL; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci if (m->icmph.icmp6_code != 0 || 24262306a36Sopenharmony_ci (m->icmph.icmp6_type != NDISC_NEIGHBOUR_SOLICITATION && 24362306a36Sopenharmony_ci m->icmph.icmp6_type != NDISC_NEIGHBOUR_ADVERTISEMENT)) 24462306a36Sopenharmony_ci return NULL; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci return m; 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistatic void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, 25062306a36Sopenharmony_ci struct sk_buff *request, struct neighbour *n, 25162306a36Sopenharmony_ci __be16 vlan_proto, u16 vlan_tci, struct nd_msg *ns) 25262306a36Sopenharmony_ci{ 25362306a36Sopenharmony_ci struct net_device *dev = request->dev; 25462306a36Sopenharmony_ci struct net_bridge_vlan_group *vg; 25562306a36Sopenharmony_ci struct sk_buff *reply; 25662306a36Sopenharmony_ci struct nd_msg *na; 25762306a36Sopenharmony_ci struct ipv6hdr *pip6; 25862306a36Sopenharmony_ci int na_olen = 8; /* opt hdr + ETH_ALEN for target */ 25962306a36Sopenharmony_ci int ns_olen; 26062306a36Sopenharmony_ci int i, len; 26162306a36Sopenharmony_ci u8 *daddr; 26262306a36Sopenharmony_ci u16 pvid; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci if (!dev) 26562306a36Sopenharmony_ci return; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci len = LL_RESERVED_SPACE(dev) + sizeof(struct ipv6hdr) + 26862306a36Sopenharmony_ci sizeof(*na) + na_olen + dev->needed_tailroom; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci reply = alloc_skb(len, GFP_ATOMIC); 27162306a36Sopenharmony_ci if (!reply) 27262306a36Sopenharmony_ci return; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci reply->protocol = htons(ETH_P_IPV6); 27562306a36Sopenharmony_ci reply->dev = dev; 27662306a36Sopenharmony_ci skb_reserve(reply, LL_RESERVED_SPACE(dev)); 27762306a36Sopenharmony_ci skb_push(reply, sizeof(struct ethhdr)); 27862306a36Sopenharmony_ci skb_set_mac_header(reply, 0); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci daddr = eth_hdr(request)->h_source; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* Do we need option processing ? */ 28362306a36Sopenharmony_ci ns_olen = request->len - (skb_network_offset(request) + 28462306a36Sopenharmony_ci sizeof(struct ipv6hdr)) - sizeof(*ns); 28562306a36Sopenharmony_ci for (i = 0; i < ns_olen - 1; i += (ns->opt[i + 1] << 3)) { 28662306a36Sopenharmony_ci if (!ns->opt[i + 1]) { 28762306a36Sopenharmony_ci kfree_skb(reply); 28862306a36Sopenharmony_ci return; 28962306a36Sopenharmony_ci } 29062306a36Sopenharmony_ci if (ns->opt[i] == ND_OPT_SOURCE_LL_ADDR) { 29162306a36Sopenharmony_ci daddr = ns->opt + i + sizeof(struct nd_opt_hdr); 29262306a36Sopenharmony_ci break; 29362306a36Sopenharmony_ci } 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci /* Ethernet header */ 29762306a36Sopenharmony_ci ether_addr_copy(eth_hdr(reply)->h_dest, daddr); 29862306a36Sopenharmony_ci ether_addr_copy(eth_hdr(reply)->h_source, n->ha); 29962306a36Sopenharmony_ci eth_hdr(reply)->h_proto = htons(ETH_P_IPV6); 30062306a36Sopenharmony_ci reply->protocol = htons(ETH_P_IPV6); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci skb_pull(reply, sizeof(struct ethhdr)); 30362306a36Sopenharmony_ci skb_set_network_header(reply, 0); 30462306a36Sopenharmony_ci skb_put(reply, sizeof(struct ipv6hdr)); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci /* IPv6 header */ 30762306a36Sopenharmony_ci pip6 = ipv6_hdr(reply); 30862306a36Sopenharmony_ci memset(pip6, 0, sizeof(struct ipv6hdr)); 30962306a36Sopenharmony_ci pip6->version = 6; 31062306a36Sopenharmony_ci pip6->priority = ipv6_hdr(request)->priority; 31162306a36Sopenharmony_ci pip6->nexthdr = IPPROTO_ICMPV6; 31262306a36Sopenharmony_ci pip6->hop_limit = 255; 31362306a36Sopenharmony_ci pip6->daddr = ipv6_hdr(request)->saddr; 31462306a36Sopenharmony_ci pip6->saddr = *(struct in6_addr *)n->primary_key; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci skb_pull(reply, sizeof(struct ipv6hdr)); 31762306a36Sopenharmony_ci skb_set_transport_header(reply, 0); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci na = (struct nd_msg *)skb_put(reply, sizeof(*na) + na_olen); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci /* Neighbor Advertisement */ 32262306a36Sopenharmony_ci memset(na, 0, sizeof(*na) + na_olen); 32362306a36Sopenharmony_ci na->icmph.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT; 32462306a36Sopenharmony_ci na->icmph.icmp6_router = (n->flags & NTF_ROUTER) ? 1 : 0; 32562306a36Sopenharmony_ci na->icmph.icmp6_override = 1; 32662306a36Sopenharmony_ci na->icmph.icmp6_solicited = 1; 32762306a36Sopenharmony_ci na->target = ns->target; 32862306a36Sopenharmony_ci ether_addr_copy(&na->opt[2], n->ha); 32962306a36Sopenharmony_ci na->opt[0] = ND_OPT_TARGET_LL_ADDR; 33062306a36Sopenharmony_ci na->opt[1] = na_olen >> 3; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci na->icmph.icmp6_cksum = csum_ipv6_magic(&pip6->saddr, 33362306a36Sopenharmony_ci &pip6->daddr, 33462306a36Sopenharmony_ci sizeof(*na) + na_olen, 33562306a36Sopenharmony_ci IPPROTO_ICMPV6, 33662306a36Sopenharmony_ci csum_partial(na, sizeof(*na) + na_olen, 0)); 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci pip6->payload_len = htons(sizeof(*na) + na_olen); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci skb_push(reply, sizeof(struct ipv6hdr)); 34162306a36Sopenharmony_ci skb_push(reply, sizeof(struct ethhdr)); 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci reply->ip_summed = CHECKSUM_UNNECESSARY; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci if (p) 34662306a36Sopenharmony_ci vg = nbp_vlan_group_rcu(p); 34762306a36Sopenharmony_ci else 34862306a36Sopenharmony_ci vg = br_vlan_group_rcu(br); 34962306a36Sopenharmony_ci pvid = br_get_pvid(vg); 35062306a36Sopenharmony_ci if (pvid == (vlan_tci & VLAN_VID_MASK)) 35162306a36Sopenharmony_ci vlan_tci = 0; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci if (vlan_tci) 35462306a36Sopenharmony_ci __vlan_hwaccel_put_tag(reply, vlan_proto, vlan_tci); 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci netdev_dbg(dev, "nd send dev %s dst %pI6 dst_hw %pM src %pI6 src_hw %pM\n", 35762306a36Sopenharmony_ci dev->name, &pip6->daddr, daddr, &pip6->saddr, n->ha); 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci if (p) { 36062306a36Sopenharmony_ci dev_queue_xmit(reply); 36162306a36Sopenharmony_ci } else { 36262306a36Sopenharmony_ci skb_reset_mac_header(reply); 36362306a36Sopenharmony_ci __skb_pull(reply, skb_network_offset(reply)); 36462306a36Sopenharmony_ci reply->ip_summed = CHECKSUM_UNNECESSARY; 36562306a36Sopenharmony_ci reply->pkt_type = PACKET_HOST; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci netif_rx(reply); 36862306a36Sopenharmony_ci } 36962306a36Sopenharmony_ci} 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_cistatic int br_chk_addr_ip6(struct net_device *dev, 37262306a36Sopenharmony_ci struct netdev_nested_priv *priv) 37362306a36Sopenharmony_ci{ 37462306a36Sopenharmony_ci struct in6_addr *addr = (struct in6_addr *)priv->data; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci if (ipv6_chk_addr(dev_net(dev), addr, dev, 0)) 37762306a36Sopenharmony_ci return 1; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci return 0; 38062306a36Sopenharmony_ci} 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_cistatic bool br_is_local_ip6(struct net_device *dev, struct in6_addr *addr) 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci{ 38562306a36Sopenharmony_ci struct netdev_nested_priv priv = { 38662306a36Sopenharmony_ci .data = (void *)addr, 38762306a36Sopenharmony_ci }; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci if (br_chk_addr_ip6(dev, &priv)) 39062306a36Sopenharmony_ci return true; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci /* check if ip is configured on upper dev */ 39362306a36Sopenharmony_ci if (netdev_walk_all_upper_dev_rcu(dev, br_chk_addr_ip6, &priv)) 39462306a36Sopenharmony_ci return true; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci return false; 39762306a36Sopenharmony_ci} 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_civoid br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br, 40062306a36Sopenharmony_ci u16 vid, struct net_bridge_port *p, struct nd_msg *msg) 40162306a36Sopenharmony_ci{ 40262306a36Sopenharmony_ci struct net_device *dev = br->dev; 40362306a36Sopenharmony_ci struct net_device *vlandev = NULL; 40462306a36Sopenharmony_ci struct in6_addr *saddr, *daddr; 40562306a36Sopenharmony_ci struct ipv6hdr *iphdr; 40662306a36Sopenharmony_ci struct neighbour *n; 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci BR_INPUT_SKB_CB(skb)->proxyarp_replied = 0; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci if (br_is_neigh_suppress_enabled(p, vid)) 41162306a36Sopenharmony_ci return; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci if (msg->icmph.icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT && 41462306a36Sopenharmony_ci !msg->icmph.icmp6_solicited) { 41562306a36Sopenharmony_ci /* prevent flooding to neigh suppress ports */ 41662306a36Sopenharmony_ci BR_INPUT_SKB_CB(skb)->proxyarp_replied = 1; 41762306a36Sopenharmony_ci return; 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci if (msg->icmph.icmp6_type != NDISC_NEIGHBOUR_SOLICITATION) 42162306a36Sopenharmony_ci return; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci iphdr = ipv6_hdr(skb); 42462306a36Sopenharmony_ci saddr = &iphdr->saddr; 42562306a36Sopenharmony_ci daddr = &iphdr->daddr; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci if (ipv6_addr_any(saddr) || !ipv6_addr_cmp(saddr, daddr)) { 42862306a36Sopenharmony_ci /* prevent flooding to neigh suppress ports */ 42962306a36Sopenharmony_ci BR_INPUT_SKB_CB(skb)->proxyarp_replied = 1; 43062306a36Sopenharmony_ci return; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci if (vid != 0) { 43462306a36Sopenharmony_ci /* build neigh table lookup on the vlan device */ 43562306a36Sopenharmony_ci vlandev = __vlan_find_dev_deep_rcu(br->dev, skb->vlan_proto, 43662306a36Sopenharmony_ci vid); 43762306a36Sopenharmony_ci if (!vlandev) 43862306a36Sopenharmony_ci return; 43962306a36Sopenharmony_ci } else { 44062306a36Sopenharmony_ci vlandev = dev; 44162306a36Sopenharmony_ci } 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci if (br_is_local_ip6(vlandev, &msg->target)) { 44462306a36Sopenharmony_ci /* its our own ip, so don't proxy reply 44562306a36Sopenharmony_ci * and don't forward to arp suppress ports 44662306a36Sopenharmony_ci */ 44762306a36Sopenharmony_ci BR_INPUT_SKB_CB(skb)->proxyarp_replied = 1; 44862306a36Sopenharmony_ci return; 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci n = neigh_lookup(ipv6_stub->nd_tbl, &msg->target, vlandev); 45262306a36Sopenharmony_ci if (n) { 45362306a36Sopenharmony_ci struct net_bridge_fdb_entry *f; 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci if (!(READ_ONCE(n->nud_state) & NUD_VALID)) { 45662306a36Sopenharmony_ci neigh_release(n); 45762306a36Sopenharmony_ci return; 45862306a36Sopenharmony_ci } 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci f = br_fdb_find_rcu(br, n->ha, vid); 46162306a36Sopenharmony_ci if (f) { 46262306a36Sopenharmony_ci bool replied = false; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci if (br_is_neigh_suppress_enabled(f->dst, vid)) { 46562306a36Sopenharmony_ci if (vid != 0) 46662306a36Sopenharmony_ci br_nd_send(br, p, skb, n, 46762306a36Sopenharmony_ci skb->vlan_proto, 46862306a36Sopenharmony_ci skb_vlan_tag_get(skb), msg); 46962306a36Sopenharmony_ci else 47062306a36Sopenharmony_ci br_nd_send(br, p, skb, n, 0, 0, msg); 47162306a36Sopenharmony_ci replied = true; 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci /* If we have replied or as long as we know the 47562306a36Sopenharmony_ci * mac, indicate to NEIGH_SUPPRESS ports that we 47662306a36Sopenharmony_ci * have replied 47762306a36Sopenharmony_ci */ 47862306a36Sopenharmony_ci if (replied || 47962306a36Sopenharmony_ci br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED)) 48062306a36Sopenharmony_ci BR_INPUT_SKB_CB(skb)->proxyarp_replied = 1; 48162306a36Sopenharmony_ci } 48262306a36Sopenharmony_ci neigh_release(n); 48362306a36Sopenharmony_ci } 48462306a36Sopenharmony_ci} 48562306a36Sopenharmony_ci#endif 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_cibool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid) 48862306a36Sopenharmony_ci{ 48962306a36Sopenharmony_ci if (!p) 49062306a36Sopenharmony_ci return false; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci if (!vid) 49362306a36Sopenharmony_ci return !!(p->flags & BR_NEIGH_SUPPRESS); 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci if (p->flags & BR_NEIGH_VLAN_SUPPRESS) { 49662306a36Sopenharmony_ci struct net_bridge_vlan_group *vg = nbp_vlan_group_rcu(p); 49762306a36Sopenharmony_ci struct net_bridge_vlan *v; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci v = br_vlan_find(vg, vid); 50062306a36Sopenharmony_ci if (!v) 50162306a36Sopenharmony_ci return false; 50262306a36Sopenharmony_ci return !!(v->priv_flags & BR_VLFLAG_NEIGH_SUPPRESS_ENABLED); 50362306a36Sopenharmony_ci } else { 50462306a36Sopenharmony_ci return !!(p->flags & BR_NEIGH_SUPPRESS); 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci} 507