Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/vxlan/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 126 kB image not shown  

Quelle  vxlan_core.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * VXLAN: Virtual eXtensible Local Area Network
 *
 * Copyright (c) 2012-2013 Vyatta Inc.
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/udp.h>
#include <linux/igmp.h>
#include <linux/if_ether.h>
#include <linux/ethtool.h>
#include <linux/rhashtable.h>
#include <net/arp.h>
#include <net/ndisc.h>
#include <net/gro.h>
#include <net/ipv6_stubs.h>
#include <net/ip.h>
#include <net/icmp.h>
#include <net/rtnetlink.h>
#include <net/inet_ecn.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>
#include <net/netdev_lock.h>
#include <net/tun_proto.h>
#include <net/vxlan.h>
#include <net/nexthop.h>

#if IS_ENABLED(CONFIG_IPV6)
#include <net/ip6_tunnel.h>
#include <net/ip6_checksum.h>
#endif

#include "vxlan_private.h"

#define VXLAN_VERSION "0.1"

#define FDB_AGE_DEFAULT 300 /* 5 min */
#define FDB_AGE_INTERVAL (10 * HZ) /* rescan interval */

/* UDP port for VXLAN traffic.
 * The IANA assigned port is 4789, but the Linux default is 8472
 * for compatibility with early adopters.
 */

static unsigned short vxlan_port __read_mostly = 8472;
module_param_named(udp_port, vxlan_port, ushort, 0444);
MODULE_PARM_DESC(udp_port, "Destination UDP port");

static bool log_ecn_error = true;
module_param(log_ecn_error, bool, 0644);
MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN");

unsigned int vxlan_net_id;

const u8 all_zeros_mac[ETH_ALEN + 2];
static struct rtnl_link_ops vxlan_link_ops;

static int vxlan_sock_add(struct vxlan_dev *vxlan);

static void vxlan_vs_del_dev(struct vxlan_dev *vxlan);

static const struct rhashtable_params vxlan_fdb_rht_params = {
 .head_offset = offsetof(struct vxlan_fdb, rhnode),
 .key_offset = offsetof(struct vxlan_fdb, key),
 .key_len = sizeof(struct vxlan_fdb_key),
 .automatic_shrinking = true,
};

static inline bool vxlan_collect_metadata(struct vxlan_sock *vs)
{
 return vs->flags & VXLAN_F_COLLECT_METADATA ||
        ip_tunnel_collect_metadata();
}

/* Find VXLAN socket based on network namespace, address family, UDP port,
 * enabled unshareable flags and socket device binding (see l3mdev with
 * non-default VRF).
 */

static struct vxlan_sock *vxlan_find_sock(struct net *net, sa_family_t family,
       __be16 port, u32 flags, int ifindex)
{
 struct vxlan_sock *vs;

 flags &= VXLAN_F_RCV_FLAGS;

 hlist_for_each_entry_rcu(vs, vs_head(net, port), hlist) {
  if (inet_sk(vs->sock->sk)->inet_sport == port &&
      vxlan_get_sk_family(vs) == family &&
      vs->flags == flags &&
      vs->sock->sk->sk_bound_dev_if == ifindex)
   return vs;
 }
 return NULL;
}

static struct vxlan_dev *vxlan_vs_find_vni(struct vxlan_sock *vs,
        int ifindex, __be32 vni,
        struct vxlan_vni_node **vninode)
{
 struct vxlan_vni_node *vnode;
 struct vxlan_dev_node *node;

 /* For flow based devices, map all packets to VNI 0 */
 if (vs->flags & VXLAN_F_COLLECT_METADATA &&
     !(vs->flags & VXLAN_F_VNIFILTER))
  vni = 0;

 hlist_for_each_entry_rcu(node, vni_head(vs, vni), hlist) {
  if (!node->vxlan)
   continue;
  vnode = NULL;
  if (node->vxlan->cfg.flags & VXLAN_F_VNIFILTER) {
   vnode = vxlan_vnifilter_lookup(node->vxlan, vni);
   if (!vnode)
    continue;
  } else if (node->vxlan->default_dst.remote_vni != vni) {
   continue;
  }

  if (IS_ENABLED(CONFIG_IPV6)) {
   const struct vxlan_config *cfg = &node->vxlan->cfg;

   if ((cfg->flags & VXLAN_F_IPV6_LINKLOCAL) &&
       cfg->remote_ifindex != ifindex)
    continue;
  }

  if (vninode)
   *vninode = vnode;
  return node->vxlan;
 }

 return NULL;
}

/* Look up VNI in a per net namespace table */
static struct vxlan_dev *vxlan_find_vni(struct net *net, int ifindex,
     __be32 vni, sa_family_t family,
     __be16 port, u32 flags)
{
 struct vxlan_sock *vs;

 vs = vxlan_find_sock(net, family, port, flags, ifindex);
 if (!vs)
  return NULL;

 return vxlan_vs_find_vni(vs, ifindex, vni, NULL);
}

/* Fill in neighbour message in skbuff. */
static int vxlan_fdb_info(struct sk_buff *skb, struct vxlan_dev *vxlan,
     const struct vxlan_fdb *fdb,
     u32 portid, u32 seq, int type, unsigned int flags,
     const struct vxlan_rdst *rdst)
{
 unsigned long now = jiffies;
 struct nda_cacheinfo ci;
 bool send_ip, send_eth;
 struct nlmsghdr *nlh;
 struct nexthop *nh;
 struct ndmsg *ndm;
 int nh_family;
 u32 nh_id;

 nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags);
 if (nlh == NULL)
  return -EMSGSIZE;

 ndm = nlmsg_data(nlh);
 memset(ndm, 0, sizeof(*ndm));

 send_eth = send_ip = true;

 rcu_read_lock();
 nh = rcu_dereference(fdb->nh);
 if (nh) {
  nh_family = nexthop_get_family(nh);
  nh_id = nh->id;
 }
 rcu_read_unlock();

 if (type == RTM_GETNEIGH) {
  if (rdst) {
   send_ip = !vxlan_addr_any(&rdst->remote_ip);
   ndm->ndm_family = send_ip ? rdst->remote_ip.sa.sa_family : AF_INET;
  } else if (nh) {
   ndm->ndm_family = nh_family;
  }
  send_eth = !is_zero_ether_addr(fdb->key.eth_addr);
 } else
  ndm->ndm_family = AF_BRIDGE;
 ndm->ndm_state = fdb->state;
 ndm->ndm_ifindex = vxlan->dev->ifindex;
 ndm->ndm_flags = fdb->flags;
 if (rdst && rdst->offloaded)
  ndm->ndm_flags |= NTF_OFFLOADED;
 ndm->ndm_type = RTN_UNICAST;

 if (!net_eq(dev_net(vxlan->dev), vxlan->net) &&
     nla_put_s32(skb, NDA_LINK_NETNSID,
   peernet2id(dev_net(vxlan->dev), vxlan->net)))
  goto nla_put_failure;

 if (send_eth && nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->key.eth_addr))
  goto nla_put_failure;
 if (nh) {
  if (nla_put_u32(skb, NDA_NH_ID, nh_id))
   goto nla_put_failure;
 } else if (rdst) {
  if (send_ip && vxlan_nla_put_addr(skb, NDA_DST,
        &rdst->remote_ip))
   goto nla_put_failure;

  if (rdst->remote_port &&
      rdst->remote_port != vxlan->cfg.dst_port &&
      nla_put_be16(skb, NDA_PORT, rdst->remote_port))
   goto nla_put_failure;
  if (rdst->remote_vni != vxlan->default_dst.remote_vni &&
      nla_put_u32(skb, NDA_VNI, be32_to_cpu(rdst->remote_vni)))
   goto nla_put_failure;
  if (rdst->remote_ifindex &&
      nla_put_u32(skb, NDA_IFINDEX, rdst->remote_ifindex))
   goto nla_put_failure;
 }

 if ((vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA) && fdb->key.vni &&
     nla_put_u32(skb, NDA_SRC_VNI,
   be32_to_cpu(fdb->key.vni)))
  goto nla_put_failure;

 ci.ndm_used  = jiffies_to_clock_t(now - READ_ONCE(fdb->used));
 ci.ndm_confirmed = 0;
 ci.ndm_updated  = jiffies_to_clock_t(now - READ_ONCE(fdb->updated));
 ci.ndm_refcnt  = 0;

 if (nla_put(skb, NDA_CACHEINFO, sizeof(ci), &ci))
  goto nla_put_failure;

 nlmsg_end(skb, nlh);
 return 0;

nla_put_failure:
 nlmsg_cancel(skb, nlh);
 return -EMSGSIZE;
}

static inline size_t vxlan_nlmsg_size(void)
{
 return NLMSG_ALIGN(sizeof(struct ndmsg))
  + nla_total_size(ETH_ALEN) /* NDA_LLADDR */
  + nla_total_size(sizeof(struct in6_addr)) /* NDA_DST */
  + nla_total_size(sizeof(__be16)) /* NDA_PORT */
  + nla_total_size(sizeof(__be32)) /* NDA_VNI */
  + nla_total_size(sizeof(__u32)) /* NDA_IFINDEX */
  + nla_total_size(sizeof(__s32)) /* NDA_LINK_NETNSID */
  + nla_total_size(sizeof(struct nda_cacheinfo));
}

static void __vxlan_fdb_notify(struct vxlan_dev *vxlan, struct vxlan_fdb *fdb,
          struct vxlan_rdst *rd, int type)
{
 struct net *net = dev_net(vxlan->dev);
 struct sk_buff *skb;
 int err = -ENOBUFS;

 skb = nlmsg_new(vxlan_nlmsg_size(), GFP_ATOMIC);
 if (skb == NULL)
  goto errout;

 err = vxlan_fdb_info(skb, vxlan, fdb, 0, 0, type, 0, rd);
 if (err < 0) {
  /* -EMSGSIZE implies BUG in vxlan_nlmsg_size() */
  WARN_ON(err == -EMSGSIZE);
  kfree_skb(skb);
  goto errout;
 }

 rtnl_notify(skb, net, 0, RTNLGRP_NEIGH, NULL, GFP_ATOMIC);
 return;
errout:
 rtnl_set_sk_err(net, RTNLGRP_NEIGH, err);
}

static void vxlan_fdb_switchdev_notifier_info(const struct vxlan_dev *vxlan,
       const struct vxlan_fdb *fdb,
       const struct vxlan_rdst *rd,
       struct netlink_ext_ack *extack,
       struct switchdev_notifier_vxlan_fdb_info *fdb_info)
{
 fdb_info->info.dev = vxlan->dev;
 fdb_info->info.extack = extack;
 fdb_info->remote_ip = rd->remote_ip;
 fdb_info->remote_port = rd->remote_port;
 fdb_info->remote_vni = rd->remote_vni;
 fdb_info->remote_ifindex = rd->remote_ifindex;
 memcpy(fdb_info->eth_addr, fdb->key.eth_addr, ETH_ALEN);
 fdb_info->vni = fdb->key.vni;
 fdb_info->offloaded = rd->offloaded;
 fdb_info->added_by_user = fdb->flags & NTF_VXLAN_ADDED_BY_USER;
}

static int vxlan_fdb_switchdev_call_notifiers(struct vxlan_dev *vxlan,
           struct vxlan_fdb *fdb,
           struct vxlan_rdst *rd,
           bool adding,
           struct netlink_ext_ack *extack)
{
 struct switchdev_notifier_vxlan_fdb_info info;
 enum switchdev_notifier_type notifier_type;
 int ret;

 if (WARN_ON(!rd))
  return 0;

 notifier_type = adding ? SWITCHDEV_VXLAN_FDB_ADD_TO_DEVICE
          : SWITCHDEV_VXLAN_FDB_DEL_TO_DEVICE;
 vxlan_fdb_switchdev_notifier_info(vxlan, fdb, rd, NULL, &info);
 ret = call_switchdev_notifiers(notifier_type, vxlan->dev,
           &info.info, extack);
 return notifier_to_errno(ret);
}

static int vxlan_fdb_notify(struct vxlan_dev *vxlan, struct vxlan_fdb *fdb,
       struct vxlan_rdst *rd, int type, bool swdev_notify,
       struct netlink_ext_ack *extack)
{
 int err;

 if (swdev_notify && rd) {
  switch (type) {
  case RTM_NEWNEIGH:
   err = vxlan_fdb_switchdev_call_notifiers(vxlan, fdb, rd,
         true, extack);
   if (err)
    return err;
   break;
  case RTM_DELNEIGH:
   vxlan_fdb_switchdev_call_notifiers(vxlan, fdb, rd,
          false, extack);
   break;
  }
 }

 __vxlan_fdb_notify(vxlan, fdb, rd, type);
 return 0;
}

static void vxlan_ip_miss(struct net_device *dev, union vxlan_addr *ipa)
{
 struct vxlan_dev *vxlan = netdev_priv(dev);
 struct vxlan_fdb f = {
  .state = NUD_STALE,
 };
 struct vxlan_rdst remote = {
  .remote_ip = *ipa, /* goes to NDA_DST */
  .remote_vni = cpu_to_be32(VXLAN_N_VID),
 };

 vxlan_fdb_notify(vxlan, &f, &remote, RTM_GETNEIGH, true, NULL);
}

static void vxlan_fdb_miss(struct vxlan_dev *vxlan, const u8 eth_addr[ETH_ALEN])
{
 struct vxlan_fdb f = {
  .state = NUD_STALE,
 };
 struct vxlan_rdst remote = { };

 memcpy(f.key.eth_addr, eth_addr, ETH_ALEN);

 vxlan_fdb_notify(vxlan, &f, &remote, RTM_GETNEIGH, true, NULL);
}

/* Look up Ethernet address in forwarding table */
static struct vxlan_fdb *vxlan_find_mac_rcu(struct vxlan_dev *vxlan,
         const u8 *mac, __be32 vni)
{
 struct vxlan_fdb_key key;

 memset(&key, 0, sizeof(key));
 memcpy(key.eth_addr, mac, sizeof(key.eth_addr));
 if (!(vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA))
  key.vni = vxlan->default_dst.remote_vni;
 else
  key.vni = vni;

 return rhashtable_lookup(&vxlan->fdb_hash_tbl, &key,
     vxlan_fdb_rht_params);
}

static struct vxlan_fdb *vxlan_find_mac_tx(struct vxlan_dev *vxlan,
        const u8 *mac, __be32 vni)
{
 struct vxlan_fdb *f;

 f = vxlan_find_mac_rcu(vxlan, mac, vni);
 if (f) {
  unsigned long now = jiffies;

  if (READ_ONCE(f->used) != now)
   WRITE_ONCE(f->used, now);
 }

 return f;
}

static struct vxlan_fdb *vxlan_find_mac(struct vxlan_dev *vxlan,
     const u8 *mac, __be32 vni)
{
 struct vxlan_fdb *f;

 lockdep_assert_held_once(&vxlan->hash_lock);

 rcu_read_lock();
 f = vxlan_find_mac_rcu(vxlan, mac, vni);
 rcu_read_unlock();

 return f;
}

/* caller should hold vxlan->hash_lock */
static struct vxlan_rdst *vxlan_fdb_find_rdst(struct vxlan_fdb *f,
           union vxlan_addr *ip, __be16 port,
           __be32 vni, __u32 ifindex)
{
 struct vxlan_rdst *rd;

 list_for_each_entry(rd, &f->remotes, list) {
  if (vxlan_addr_equal(&rd->remote_ip, ip) &&
      rd->remote_port == port &&
      rd->remote_vni == vni &&
      rd->remote_ifindex == ifindex)
   return rd;
 }

 return NULL;
}

int vxlan_fdb_find_uc(struct net_device *dev, const u8 *mac, __be32 vni,
        struct switchdev_notifier_vxlan_fdb_info *fdb_info)
{
 struct vxlan_dev *vxlan = netdev_priv(dev);
 u8 eth_addr[ETH_ALEN + 2] = { 0 };
 struct vxlan_rdst *rdst;
 struct vxlan_fdb *f;
 int rc = 0;

 if (is_multicast_ether_addr(mac) ||
     is_zero_ether_addr(mac))
  return -EINVAL;

 ether_addr_copy(eth_addr, mac);

 rcu_read_lock();

 f = vxlan_find_mac_rcu(vxlan, eth_addr, vni);
 if (!f) {
  rc = -ENOENT;
  goto out;
 }

 rdst = first_remote_rcu(f);
 vxlan_fdb_switchdev_notifier_info(vxlan, f, rdst, NULL, fdb_info);

out:
 rcu_read_unlock();
 return rc;
}
EXPORT_SYMBOL_GPL(vxlan_fdb_find_uc);

static int vxlan_fdb_notify_one(struct notifier_block *nb,
    const struct vxlan_dev *vxlan,
    const struct vxlan_fdb *f,
    const struct vxlan_rdst *rdst,
    struct netlink_ext_ack *extack)
{
 struct switchdev_notifier_vxlan_fdb_info fdb_info;
 int rc;

 vxlan_fdb_switchdev_notifier_info(vxlan, f, rdst, extack, &fdb_info);
 rc = nb->notifier_call(nb, SWITCHDEV_VXLAN_FDB_ADD_TO_DEVICE,
          &fdb_info);
 return notifier_to_errno(rc);
}

int vxlan_fdb_replay(const struct net_device *dev, __be32 vni,
       struct notifier_block *nb,
       struct netlink_ext_ack *extack)
{
 struct vxlan_dev *vxlan;
 struct vxlan_rdst *rdst;
 struct vxlan_fdb *f;
 int rc = 0;

 if (!netif_is_vxlan(dev))
  return -EINVAL;
 vxlan = netdev_priv(dev);

 spin_lock_bh(&vxlan->hash_lock);
 hlist_for_each_entry(f, &vxlan->fdb_list, fdb_node) {
  if (f->key.vni == vni) {
   list_for_each_entry(rdst, &f->remotes, list) {
    rc = vxlan_fdb_notify_one(nb, vxlan, f, rdst,
         extack);
    if (rc)
     goto unlock;
   }
  }
 }
 spin_unlock_bh(&vxlan->hash_lock);
 return 0;

unlock:
 spin_unlock_bh(&vxlan->hash_lock);
 return rc;
}
EXPORT_SYMBOL_GPL(vxlan_fdb_replay);

void vxlan_fdb_clear_offload(const struct net_device *dev, __be32 vni)
{
 struct vxlan_dev *vxlan;
 struct vxlan_rdst *rdst;
 struct vxlan_fdb *f;

 if (!netif_is_vxlan(dev))
  return;
 vxlan = netdev_priv(dev);

 spin_lock_bh(&vxlan->hash_lock);
 hlist_for_each_entry(f, &vxlan->fdb_list, fdb_node) {
  if (f->key.vni == vni) {
   list_for_each_entry(rdst, &f->remotes, list)
    rdst->offloaded = false;
  }
 }
 spin_unlock_bh(&vxlan->hash_lock);

}
EXPORT_SYMBOL_GPL(vxlan_fdb_clear_offload);

/* Replace destination of unicast mac */
static int vxlan_fdb_replace(struct vxlan_fdb *f,
        union vxlan_addr *ip, __be16 port, __be32 vni,
        __u32 ifindex, struct vxlan_rdst *oldrd)
{
 struct vxlan_rdst *rd;

 rd = vxlan_fdb_find_rdst(f, ip, port, vni, ifindex);
 if (rd)
  return 0;

 rd = list_first_entry_or_null(&f->remotes, struct vxlan_rdst, list);
 if (!rd)
  return 0;

 *oldrd = *rd;
 dst_cache_reset(&rd->dst_cache);
 rd->remote_ip = *ip;
 rd->remote_port = port;
 rd->remote_vni = vni;
 rd->remote_ifindex = ifindex;
 rd->offloaded = false;
 return 1;
}

/* Add/update destinations for multicast */
static int vxlan_fdb_append(struct vxlan_fdb *f,
       union vxlan_addr *ip, __be16 port, __be32 vni,
       __u32 ifindex, struct vxlan_rdst **rdp)
{
 struct vxlan_rdst *rd;

 rd = vxlan_fdb_find_rdst(f, ip, port, vni, ifindex);
 if (rd)
  return 0;

 rd = kmalloc(sizeof(*rd), GFP_ATOMIC);
 if (rd == NULL)
  return -ENOMEM;

 /* The driver can work correctly without a dst cache, so do not treat
 * dst cache initialization errors as fatal.
 */

 dst_cache_init(&rd->dst_cache, GFP_ATOMIC | __GFP_NOWARN);

 rd->remote_ip = *ip;
 rd->remote_port = port;
 rd->offloaded = false;
 rd->remote_vni = vni;
 rd->remote_ifindex = ifindex;

 list_add_tail_rcu(&rd->list, &f->remotes);

 *rdp = rd;
 return 1;
}

static bool vxlan_parse_gpe_proto(const struct vxlanhdr *hdr, __be16 *protocol)
{
 const struct vxlanhdr_gpe *gpe = (const struct vxlanhdr_gpe *)hdr;

 /* Need to have Next Protocol set for interfaces in GPE mode. */
 if (!gpe->np_applied)
  return false;
 /* "The initial version is 0. If a receiver does not support the
 * version indicated it MUST drop the packet.
 */

 if (gpe->version != 0)
  return false;
 /* "When the O bit is set to 1, the packet is an OAM packet and OAM
 * processing MUST occur." However, we don't implement OAM
 * processing, thus drop the packet.
 */

 if (gpe->oam_flag)
  return false;

 *protocol = tun_p_to_eth_p(gpe->next_protocol);
 if (!*protocol)
  return false;

 return true;
}

static struct vxlanhdr *vxlan_gro_remcsum(struct sk_buff *skb,
       unsigned int off,
       struct vxlanhdr *vh, size_t hdrlen,
       __be32 vni_field,
       struct gro_remcsum *grc,
       bool nopartial)
{
 size_t start, offset;

 if (skb->remcsum_offload)
  return vh;

 if (!NAPI_GRO_CB(skb)->csum_valid)
  return NULL;

 start = vxlan_rco_start(vni_field);
 offset = start + vxlan_rco_offset(vni_field);

 vh = skb_gro_remcsum_process(skb, (void *)vh, off, hdrlen,
         start, offset, grc, nopartial);

 skb->remcsum_offload = 1;

 return vh;
}

static struct vxlanhdr *vxlan_gro_prepare_receive(struct sock *sk,
        struct list_head *head,
        struct sk_buff *skb,
        struct gro_remcsum *grc)
{
 struct sk_buff *p;
 struct vxlanhdr *vh, *vh2;
 unsigned int hlen, off_vx;
 struct vxlan_sock *vs = rcu_dereference_sk_user_data(sk);
 __be32 flags;

 skb_gro_remcsum_init(grc);

 off_vx = skb_gro_offset(skb);
 hlen = off_vx + sizeof(*vh);
 vh = skb_gro_header(skb, hlen, off_vx);
 if (unlikely(!vh))
  return NULL;

 skb_gro_postpull_rcsum(skb, vh, sizeof(struct vxlanhdr));

 flags = vh->vx_flags;

 if ((flags & VXLAN_HF_RCO) && (vs->flags & VXLAN_F_REMCSUM_RX)) {
  vh = vxlan_gro_remcsum(skb, off_vx, vh, sizeof(struct vxlanhdr),
           vh->vx_vni, grc,
           !!(vs->flags &
       VXLAN_F_REMCSUM_NOPARTIAL));

  if (!vh)
   return NULL;
 }

 skb_gro_pull(skb, sizeof(struct vxlanhdr)); /* pull vxlan header */

 list_for_each_entry(p, head, list) {
  if (!NAPI_GRO_CB(p)->same_flow)
   continue;

  vh2 = (struct vxlanhdr *)(p->data + off_vx);
  if (vh->vx_flags != vh2->vx_flags ||
      vh->vx_vni != vh2->vx_vni) {
   NAPI_GRO_CB(p)->same_flow = 0;
   continue;
  }
 }

 return vh;
}

static struct sk_buff *vxlan_gro_receive(struct sock *sk,
      struct list_head *head,
      struct sk_buff *skb)
{
 struct sk_buff *pp = NULL;
 struct gro_remcsum grc;
 int flush = 1;

 if (vxlan_gro_prepare_receive(sk, head, skb, &grc)) {
  pp = call_gro_receive(eth_gro_receive, head, skb);
  flush = 0;
 }
 skb_gro_flush_final_remcsum(skb, pp, flush, &grc);
 return pp;
}

static struct sk_buff *vxlan_gpe_gro_receive(struct sock *sk,
          struct list_head *head,
          struct sk_buff *skb)
{
 const struct packet_offload *ptype;
 struct sk_buff *pp = NULL;
 struct gro_remcsum grc;
 struct vxlanhdr *vh;
 __be16 protocol;
 int flush = 1;

 vh = vxlan_gro_prepare_receive(sk, head, skb, &grc);
 if (vh) {
  if (!vxlan_parse_gpe_proto(vh, &protocol))
   goto out;
  ptype = gro_find_receive_by_type(protocol);
  if (!ptype)
   goto out;
  pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb);
  flush = 0;
 }
out:
 skb_gro_flush_final_remcsum(skb, pp, flush, &grc);
 return pp;
}

static int vxlan_gro_complete(struct sock *sk, struct sk_buff *skb, int nhoff)
{
 /* Sets 'skb->inner_mac_header' since we are always called with
 * 'skb->encapsulation' set.
 */

 return eth_gro_complete(skb, nhoff + sizeof(struct vxlanhdr));
}

static int vxlan_gpe_gro_complete(struct sock *sk, struct sk_buff *skb, int nhoff)
{
 struct vxlanhdr *vh = (struct vxlanhdr *)(skb->data + nhoff);
 const struct packet_offload *ptype;
 int err = -ENOSYS;
 __be16 protocol;

 if (!vxlan_parse_gpe_proto(vh, &protocol))
  return err;
 ptype = gro_find_complete_by_type(protocol);
 if (ptype)
  err = ptype->callbacks.gro_complete(skb, nhoff + sizeof(struct vxlanhdr));
 return err;
}

static struct vxlan_fdb *vxlan_fdb_alloc(struct vxlan_dev *vxlan, const u8 *mac,
      __u16 state, __be32 src_vni,
      __u16 ndm_flags)
{
 struct vxlan_fdb *f;

 f = kmalloc(sizeof(*f), GFP_ATOMIC);
 if (!f)
  return NULL;
 memset(&f->key, 0, sizeof(f->key));
 f->state = state;
 f->flags = ndm_flags;
 f->updated = f->used = jiffies;
 f->key.vni = src_vni;
 f->nh = NULL;
 RCU_INIT_POINTER(f->vdev, vxlan);
 INIT_LIST_HEAD(&f->nh_list);
 INIT_LIST_HEAD(&f->remotes);
 memcpy(f->key.eth_addr, mac, ETH_ALEN);

 return f;
}

static int vxlan_fdb_nh_update(struct vxlan_dev *vxlan, struct vxlan_fdb *fdb,
          u32 nhid, struct netlink_ext_ack *extack)
{
 struct nexthop *old_nh = rtnl_dereference(fdb->nh);
 struct nexthop *nh;
 int err = -EINVAL;

 if (old_nh && old_nh->id == nhid)
  return 0;

 nh = nexthop_find_by_id(vxlan->net, nhid);
 if (!nh) {
  NL_SET_ERR_MSG(extack, "Nexthop id does not exist");
  goto err_inval;
 }

 if (!nexthop_get(nh)) {
  NL_SET_ERR_MSG(extack, "Nexthop has been deleted");
  nh = NULL;
  goto err_inval;
 }
 if (!nexthop_is_fdb(nh)) {
  NL_SET_ERR_MSG(extack, "Nexthop is not a fdb nexthop");
  goto err_inval;
 }

 if (!nexthop_is_multipath(nh)) {
  NL_SET_ERR_MSG(extack, "Nexthop is not a multipath group");
  goto err_inval;
 }

 /* check nexthop group family */
 switch (vxlan->default_dst.remote_ip.sa.sa_family) {
 case AF_INET:
  if (!nexthop_has_v4(nh)) {
   err = -EAFNOSUPPORT;
   NL_SET_ERR_MSG(extack, "Nexthop group family not supported");
   goto err_inval;
  }
  break;
 case AF_INET6:
  if (nexthop_has_v4(nh)) {
   err = -EAFNOSUPPORT;
   NL_SET_ERR_MSG(extack, "Nexthop group family not supported");
   goto err_inval;
  }
 }

 if (old_nh) {
  list_del_rcu(&fdb->nh_list);
  nexthop_put(old_nh);
 }
 rcu_assign_pointer(fdb->nh, nh);
 list_add_tail_rcu(&fdb->nh_list, &nh->fdb_list);
 return 1;

err_inval:
 if (nh)
  nexthop_put(nh);
 return err;
}

int vxlan_fdb_create(struct vxlan_dev *vxlan,
       const u8 *mac, union vxlan_addr *ip,
       __u16 state, __be16 port, __be32 src_vni,
       __be32 vni, __u32 ifindex, __u16 ndm_flags,
       u32 nhid, struct vxlan_fdb **fdb,
       struct netlink_ext_ack *extack)
{
 struct vxlan_rdst *rd = NULL;
 struct vxlan_fdb *f;
 int rc;

 if (vxlan->cfg.addrmax &&
     vxlan->addrcnt >= vxlan->cfg.addrmax)
  return -ENOSPC;

 netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip);
 f = vxlan_fdb_alloc(vxlan, mac, state, src_vni, ndm_flags);
 if (!f)
  return -ENOMEM;

 if (nhid)
  rc = vxlan_fdb_nh_update(vxlan, f, nhid, extack);
 else
  rc = vxlan_fdb_append(f, ip, port, vni, ifindex, &rd);
 if (rc < 0)
  goto errout;

 rc = rhashtable_lookup_insert_fast(&vxlan->fdb_hash_tbl, &f->rhnode,
        vxlan_fdb_rht_params);
 if (rc)
  goto destroy_remote;

 ++vxlan->addrcnt;
 hlist_add_head_rcu(&f->fdb_node, &vxlan->fdb_list);

 *fdb = f;

 return 0;

destroy_remote:
 if (rcu_access_pointer(f->nh)) {
  list_del_rcu(&f->nh_list);
  nexthop_put(rtnl_dereference(f->nh));
 } else {
  list_del(&rd->list);
  dst_cache_destroy(&rd->dst_cache);
  kfree(rd);
 }
errout:
 kfree(f);
 return rc;
}

static void __vxlan_fdb_free(struct vxlan_fdb *f)
{
 struct vxlan_rdst *rd, *nd;
 struct nexthop *nh;

 nh = rcu_dereference_raw(f->nh);
 if (nh) {
  rcu_assign_pointer(f->nh, NULL);
  rcu_assign_pointer(f->vdev, NULL);
  nexthop_put(nh);
 }

 list_for_each_entry_safe(rd, nd, &f->remotes, list) {
  dst_cache_destroy(&rd->dst_cache);
  kfree(rd);
 }
 kfree(f);
}

static void vxlan_fdb_free(struct rcu_head *head)
{
 struct vxlan_fdb *f = container_of(head, struct vxlan_fdb, rcu);

 __vxlan_fdb_free(f);
}

static void vxlan_fdb_destroy(struct vxlan_dev *vxlan, struct vxlan_fdb *f,
         bool do_notify, bool swdev_notify)
{
 struct vxlan_rdst *rd;

 netdev_dbg(vxlan->dev, "delete %pM\n", f->key.eth_addr);

 --vxlan->addrcnt;
 if (do_notify) {
  if (rcu_access_pointer(f->nh))
   vxlan_fdb_notify(vxlan, f, NULL, RTM_DELNEIGH,
      swdev_notify, NULL);
  else
   list_for_each_entry(rd, &f->remotes, list)
    vxlan_fdb_notify(vxlan, f, rd, RTM_DELNEIGH,
       swdev_notify, NULL);
 }

 hlist_del_init_rcu(&f->fdb_node);
 rhashtable_remove_fast(&vxlan->fdb_hash_tbl, &f->rhnode,
          vxlan_fdb_rht_params);
 list_del_rcu(&f->nh_list);
 call_rcu(&f->rcu, vxlan_fdb_free);
}

static void vxlan_dst_free(struct rcu_head *head)
{
 struct vxlan_rdst *rd = container_of(head, struct vxlan_rdst, rcu);

 dst_cache_destroy(&rd->dst_cache);
 kfree(rd);
}

static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan,
         union vxlan_addr *ip,
         __u16 state, __u16 flags,
         __be16 port, __be32 vni,
         __u32 ifindex, __u16 ndm_flags,
         struct vxlan_fdb *f, u32 nhid,
         bool swdev_notify,
         struct netlink_ext_ack *extack)
{
 __u16 fdb_flags = (ndm_flags & ~NTF_USE);
 struct vxlan_rdst *rd = NULL;
 struct vxlan_rdst oldrd;
 int notify = 0;
 int rc = 0;
 int err;

 if (nhid && !rcu_access_pointer(f->nh)) {
  NL_SET_ERR_MSG(extack,
          "Cannot replace an existing non nexthop fdb with a nexthop");
  return -EOPNOTSUPP;
 }

 if (nhid && (flags & NLM_F_APPEND)) {
  NL_SET_ERR_MSG(extack,
          "Cannot append to a nexthop fdb");
  return -EOPNOTSUPP;
 }

 /* Do not allow an externally learned entry to take over an entry added
 * by the user.
 */

 if (!(fdb_flags & NTF_EXT_LEARNED) ||
     !(f->flags & NTF_VXLAN_ADDED_BY_USER)) {
  if (f->state != state) {
   f->state = state;
   notify = 1;
  }
  if (f->flags != fdb_flags) {
   f->flags = fdb_flags;
   notify = 1;
  }
 }

 if ((flags & NLM_F_REPLACE)) {
  /* Only change unicasts */
  if (!(is_multicast_ether_addr(f->key.eth_addr) ||
        is_zero_ether_addr(f->key.eth_addr))) {
   if (nhid) {
    rc = vxlan_fdb_nh_update(vxlan, f, nhid, extack);
    if (rc < 0)
     return rc;
   } else {
    rc = vxlan_fdb_replace(f, ip, port, vni,
             ifindex, &oldrd);
   }
   notify |= rc;
  } else {
   NL_SET_ERR_MSG(extack, "Cannot replace non-unicast fdb entries");
   return -EOPNOTSUPP;
  }
 }
 if ((flags & NLM_F_APPEND) &&
     (is_multicast_ether_addr(f->key.eth_addr) ||
      is_zero_ether_addr(f->key.eth_addr))) {
  rc = vxlan_fdb_append(f, ip, port, vni, ifindex, &rd);

  if (rc < 0)
   return rc;
  notify |= rc;
 }

 if (ndm_flags & NTF_USE)
  WRITE_ONCE(f->updated, jiffies);

 if (notify) {
  if (rd == NULL)
   rd = first_remote_rtnl(f);

  WRITE_ONCE(f->updated, jiffies);
  err = vxlan_fdb_notify(vxlan, f, rd, RTM_NEWNEIGH,
           swdev_notify, extack);
  if (err)
   goto err_notify;
 }

 return 0;

err_notify:
 if (nhid)
  return err;
 if ((flags & NLM_F_REPLACE) && rc)
  *rd = oldrd;
 else if ((flags & NLM_F_APPEND) && rc) {
  list_del_rcu(&rd->list);
  call_rcu(&rd->rcu, vxlan_dst_free);
 }
 return err;
}

static int vxlan_fdb_update_create(struct vxlan_dev *vxlan,
       const u8 *mac, union vxlan_addr *ip,
       __u16 state, __u16 flags,
       __be16 port, __be32 src_vni, __be32 vni,
       __u32 ifindex, __u16 ndm_flags, u32 nhid,
       bool swdev_notify,
       struct netlink_ext_ack *extack)
{
 __u16 fdb_flags = (ndm_flags & ~NTF_USE);
 struct vxlan_fdb *f;
 int rc;

 /* Disallow replace to add a multicast entry */
 if ((flags & NLM_F_REPLACE) &&
     (is_multicast_ether_addr(mac) || is_zero_ether_addr(mac)))
  return -EOPNOTSUPP;

 netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip);
 rc = vxlan_fdb_create(vxlan, mac, ip, state, port, src_vni,
         vni, ifindex, fdb_flags, nhid, &f, extack);
 if (rc < 0)
  return rc;

 rc = vxlan_fdb_notify(vxlan, f, first_remote_rtnl(f), RTM_NEWNEIGH,
         swdev_notify, extack);
 if (rc)
  goto err_notify;

 return 0;

err_notify:
 vxlan_fdb_destroy(vxlan, f, falsefalse);
 return rc;
}

/* Add new entry to forwarding table -- assumes lock held */
int vxlan_fdb_update(struct vxlan_dev *vxlan,
       const u8 *mac, union vxlan_addr *ip,
       __u16 state, __u16 flags,
       __be16 port, __be32 src_vni, __be32 vni,
       __u32 ifindex, __u16 ndm_flags, u32 nhid,
       bool swdev_notify,
       struct netlink_ext_ack *extack)
{
 struct vxlan_fdb *f;

 f = vxlan_find_mac(vxlan, mac, src_vni);
 if (f) {
  if (flags & NLM_F_EXCL) {
   netdev_dbg(vxlan->dev,
       "lost race to create %pM\n", mac);
   return -EEXIST;
  }

  return vxlan_fdb_update_existing(vxlan, ip, state, flags, port,
       vni, ifindex, ndm_flags, f,
       nhid, swdev_notify, extack);
 } else {
  if (!(flags & NLM_F_CREATE))
   return -ENOENT;

  return vxlan_fdb_update_create(vxlan, mac, ip, state, flags,
            port, src_vni, vni, ifindex,
            ndm_flags, nhid, swdev_notify,
            extack);
 }
}

static void vxlan_fdb_dst_destroy(struct vxlan_dev *vxlan, struct vxlan_fdb *f,
      struct vxlan_rdst *rd, bool swdev_notify)
{
 list_del_rcu(&rd->list);
 vxlan_fdb_notify(vxlan, f, rd, RTM_DELNEIGH, swdev_notify, NULL);
 call_rcu(&rd->rcu, vxlan_dst_free);
}

static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan,
      union vxlan_addr *ip, __be16 *port, __be32 *src_vni,
      __be32 *vni, u32 *ifindex, u32 *nhid,
      struct netlink_ext_ack *extack)
{
 struct net *net = dev_net(vxlan->dev);
 int err;

 if (tb[NDA_NH_ID] &&
     (tb[NDA_DST] || tb[NDA_VNI] || tb[NDA_IFINDEX] || tb[NDA_PORT])) {
  NL_SET_ERR_MSG(extack, "DST, VNI, ifindex and port are mutually exclusive with NH_ID");
  return -EINVAL;
 }

 if (tb[NDA_DST]) {
  err = vxlan_nla_get_addr(ip, tb[NDA_DST]);
  if (err) {
   NL_SET_ERR_MSG(extack, "Unsupported address family");
   return err;
  }
 } else {
  union vxlan_addr *remote = &vxlan->default_dst.remote_ip;

  if (remote->sa.sa_family == AF_INET) {
   ip->sin.sin_addr.s_addr = htonl(INADDR_ANY);
   ip->sa.sa_family = AF_INET;
#if IS_ENABLED(CONFIG_IPV6)
  } else {
   ip->sin6.sin6_addr = in6addr_any;
   ip->sa.sa_family = AF_INET6;
#endif
  }
 }

 if (tb[NDA_PORT]) {
  if (nla_len(tb[NDA_PORT]) != sizeof(__be16)) {
   NL_SET_ERR_MSG(extack, "Invalid vxlan port");
   return -EINVAL;
  }
  *port = nla_get_be16(tb[NDA_PORT]);
 } else {
  *port = vxlan->cfg.dst_port;
 }

 if (tb[NDA_VNI]) {
  if (nla_len(tb[NDA_VNI]) != sizeof(u32)) {
   NL_SET_ERR_MSG(extack, "Invalid vni");
   return -EINVAL;
  }
  *vni = cpu_to_be32(nla_get_u32(tb[NDA_VNI]));
 } else {
  *vni = vxlan->default_dst.remote_vni;
 }

 if (tb[NDA_SRC_VNI]) {
  if (nla_len(tb[NDA_SRC_VNI]) != sizeof(u32)) {
   NL_SET_ERR_MSG(extack, "Invalid src vni");
   return -EINVAL;
  }
  *src_vni = cpu_to_be32(nla_get_u32(tb[NDA_SRC_VNI]));
 } else {
  *src_vni = vxlan->default_dst.remote_vni;
 }

 if (tb[NDA_IFINDEX]) {
  struct net_device *tdev;

  if (nla_len(tb[NDA_IFINDEX]) != sizeof(u32)) {
   NL_SET_ERR_MSG(extack, "Invalid ifindex");
   return -EINVAL;
  }
  *ifindex = nla_get_u32(tb[NDA_IFINDEX]);
  tdev = __dev_get_by_index(net, *ifindex);
  if (!tdev) {
   NL_SET_ERR_MSG(extack, "Device not found");
   return -EADDRNOTAVAIL;
  }
 } else {
  *ifindex = 0;
 }

 *nhid = nla_get_u32_default(tb[NDA_NH_ID], 0);

 return 0;
}

/* Add static entry (via netlink) */
static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
    struct net_device *dev,
    const unsigned char *addr, u16 vid, u16 flags,
    bool *notified, struct netlink_ext_ack *extack)
{
 struct vxlan_dev *vxlan = netdev_priv(dev);
 /* struct net *net = dev_net(vxlan->dev); */
 union vxlan_addr ip;
 __be16 port;
 __be32 src_vni, vni;
 u32 ifindex, nhid;
 int err;

 if (!(ndm->ndm_state & (NUD_PERMANENT|NUD_REACHABLE))) {
  pr_info("RTM_NEWNEIGH with invalid state %#x\n",
   ndm->ndm_state);
  return -EINVAL;
 }

 if (!tb || (!tb[NDA_DST] && !tb[NDA_NH_ID]))
  return -EINVAL;

 err = vxlan_fdb_parse(tb, vxlan, &ip, &port, &src_vni, &vni, &ifindex,
         &nhid, extack);
 if (err)
  return err;

 if (vxlan->default_dst.remote_ip.sa.sa_family != ip.sa.sa_family)
  return -EAFNOSUPPORT;

 spin_lock_bh(&vxlan->hash_lock);
 err = vxlan_fdb_update(vxlan, addr, &ip, ndm->ndm_state, flags,
          port, src_vni, vni, ifindex,
          ndm->ndm_flags | NTF_VXLAN_ADDED_BY_USER,
          nhid, true, extack);
 spin_unlock_bh(&vxlan->hash_lock);

 if (!err)
  *notified = true;

 return err;
}

int __vxlan_fdb_delete(struct vxlan_dev *vxlan,
         const unsigned char *addr, union vxlan_addr ip,
         __be16 port, __be32 src_vni, __be32 vni,
         u32 ifindex, bool swdev_notify)
{
 struct vxlan_rdst *rd = NULL;
 struct vxlan_fdb *f;
 int err = -ENOENT;

 f = vxlan_find_mac(vxlan, addr, src_vni);
 if (!f)
  return err;

 if (!vxlan_addr_any(&ip)) {
  rd = vxlan_fdb_find_rdst(f, &ip, port, vni, ifindex);
  if (!rd)
   goto out;
 }

 /* remove a destination if it's not the only one on the list,
 * otherwise destroy the fdb entry
 */

 if (rd && !list_is_singular(&f->remotes)) {
  vxlan_fdb_dst_destroy(vxlan, f, rd, swdev_notify);
  goto out;
 }

 vxlan_fdb_destroy(vxlan, f, true, swdev_notify);

out:
 return 0;
}

/* Delete entry (via netlink) */
static int vxlan_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[],
       struct net_device *dev,
       const unsigned char *addr, u16 vid, bool *notified,
       struct netlink_ext_ack *extack)
{
 struct vxlan_dev *vxlan = netdev_priv(dev);
 union vxlan_addr ip;
 __be32 src_vni, vni;
 u32 ifindex, nhid;
 __be16 port;
 int err;

 err = vxlan_fdb_parse(tb, vxlan, &ip, &port, &src_vni, &vni, &ifindex,
         &nhid, extack);
 if (err)
  return err;

 spin_lock_bh(&vxlan->hash_lock);
 err = __vxlan_fdb_delete(vxlan, addr, ip, port, src_vni, vni, ifindex,
     true);
 spin_unlock_bh(&vxlan->hash_lock);

 if (!err)
  *notified = true;

 return err;
}

/* Dump forwarding table */
static int vxlan_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
     struct net_device *dev,
     struct net_device *filter_dev, int *idx)
{
 struct ndo_fdb_dump_context *ctx = (void *)cb->ctx;
 struct vxlan_dev *vxlan = netdev_priv(dev);
 struct vxlan_fdb *f;
 int err = 0;

 rcu_read_lock();
 hlist_for_each_entry_rcu(f, &vxlan->fdb_list, fdb_node) {
  struct vxlan_rdst *rd;

  if (rcu_access_pointer(f->nh)) {
   if (*idx < ctx->fdb_idx)
    goto skip_nh;
   err = vxlan_fdb_info(skb, vxlan, f,
          NETLINK_CB(cb->skb).portid,
          cb->nlh->nlmsg_seq,
          RTM_NEWNEIGH, NLM_F_MULTI, NULL);
   if (err < 0) {
    rcu_read_unlock();
    goto out;
   }
skip_nh:
   *idx += 1;
   continue;
  }

  list_for_each_entry_rcu(rd, &f->remotes, list) {
   if (*idx < ctx->fdb_idx)
    goto skip;

   err = vxlan_fdb_info(skb, vxlan, f,
          NETLINK_CB(cb->skb).portid,
          cb->nlh->nlmsg_seq,
          RTM_NEWNEIGH, NLM_F_MULTI, rd);
   if (err < 0) {
    rcu_read_unlock();
    goto out;
   }
skip:
   *idx += 1;
  }
 }
 rcu_read_unlock();
out:
 return err;
}

static int vxlan_fdb_get(struct sk_buff *skb,
    struct nlattr *tb[],
    struct net_device *dev,
    const unsigned char *addr,
    u16 vid, u32 portid, u32 seq,
    struct netlink_ext_ack *extack)
{
 struct vxlan_dev *vxlan = netdev_priv(dev);
 struct vxlan_fdb *f;
 __be32 vni;
 int err;

 if (tb[NDA_VNI])
  vni = cpu_to_be32(nla_get_u32(tb[NDA_VNI]));
 else
  vni = vxlan->default_dst.remote_vni;

 rcu_read_lock();

 f = vxlan_find_mac_rcu(vxlan, addr, vni);
 if (!f) {
  NL_SET_ERR_MSG(extack, "Fdb entry not found");
  err = -ENOENT;
  goto errout;
 }

 err = vxlan_fdb_info(skb, vxlan, f, portid, seq,
        RTM_NEWNEIGH, 0, first_remote_rcu(f));
errout:
 rcu_read_unlock();
 return err;
}

/* Watch incoming packets to learn mapping between Ethernet address
 * and Tunnel endpoint.
 */

static enum skb_drop_reason vxlan_snoop(struct net_device *dev,
     union vxlan_addr *src_ip,
     const u8 *src_mac, u32 src_ifindex,
     __be32 vni)
{
 struct vxlan_dev *vxlan = netdev_priv(dev);
 struct vxlan_fdb *f;
 u32 ifindex = 0;

 /* Ignore packets from invalid src-address */
 if (!is_valid_ether_addr(src_mac))
  return SKB_DROP_REASON_MAC_INVALID_SOURCE;

#if IS_ENABLED(CONFIG_IPV6)
 if (src_ip->sa.sa_family == AF_INET6 &&
     (ipv6_addr_type(&src_ip->sin6.sin6_addr) & IPV6_ADDR_LINKLOCAL))
  ifindex = src_ifindex;
#endif

 f = vxlan_find_mac_rcu(vxlan, src_mac, vni);
 if (likely(f)) {
  struct vxlan_rdst *rdst = first_remote_rcu(f);
  unsigned long now = jiffies;

  if (READ_ONCE(f->updated) != now)
   WRITE_ONCE(f->updated, now);

  /* Don't override an fdb with nexthop with a learnt entry */
  if (rcu_access_pointer(f->nh))
   return SKB_DROP_REASON_VXLAN_ENTRY_EXISTS;

  if (likely(vxlan_addr_equal(&rdst->remote_ip, src_ip) &&
      rdst->remote_ifindex == ifindex))
   return SKB_NOT_DROPPED_YET;

  /* Don't migrate static entries, drop packets */
  if (f->state & (NUD_PERMANENT | NUD_NOARP))
   return SKB_DROP_REASON_VXLAN_ENTRY_EXISTS;

  if (net_ratelimit())
   netdev_info(dev,
        "%pM migrated from %pIS to %pIS\n",
        src_mac, &rdst->remote_ip.sa, &src_ip->sa);

  rdst->remote_ip = *src_ip;
  vxlan_fdb_notify(vxlan, f, rdst, RTM_NEWNEIGH, true, NULL);
 } else {
  /* learned new entry */
  spin_lock(&vxlan->hash_lock);

  /* close off race between vxlan_flush and incoming packets */
  if (netif_running(dev))
   vxlan_fdb_update(vxlan, src_mac, src_ip,
      NUD_REACHABLE,
      NLM_F_EXCL|NLM_F_CREATE,
      vxlan->cfg.dst_port,
      vni,
      vxlan->default_dst.remote_vni,
      ifindex, NTF_SELF, 0, true, NULL);
  spin_unlock(&vxlan->hash_lock);
 }

 return SKB_NOT_DROPPED_YET;
}

static bool __vxlan_sock_release_prep(struct vxlan_sock *vs)
{
 ASSERT_RTNL();

 if (!vs)
  return false;
 if (!refcount_dec_and_test(&vs->refcnt))
  return false;

 hlist_del_rcu(&vs->hlist);
 udp_tunnel_notify_del_rx_port(vs->sock,
          (vs->flags & VXLAN_F_GPE) ?
          UDP_TUNNEL_TYPE_VXLAN_GPE :
          UDP_TUNNEL_TYPE_VXLAN);

 return true;
}

static void vxlan_sock_release(struct vxlan_dev *vxlan)
{
 struct vxlan_sock *sock4 = rtnl_dereference(vxlan->vn4_sock);
#if IS_ENABLED(CONFIG_IPV6)
 struct vxlan_sock *sock6 = rtnl_dereference(vxlan->vn6_sock);

 RCU_INIT_POINTER(vxlan->vn6_sock, NULL);
#endif

 RCU_INIT_POINTER(vxlan->vn4_sock, NULL);
 synchronize_net();

 if (vxlan->cfg.flags & VXLAN_F_VNIFILTER)
  vxlan_vs_del_vnigrp(vxlan);
 else
  vxlan_vs_del_dev(vxlan);

 if (__vxlan_sock_release_prep(sock4)) {
  udp_tunnel_sock_release(sock4->sock);
  kfree(sock4);
 }

#if IS_ENABLED(CONFIG_IPV6)
 if (__vxlan_sock_release_prep(sock6)) {
  udp_tunnel_sock_release(sock6->sock);
  kfree(sock6);
 }
#endif
}

static enum skb_drop_reason vxlan_remcsum(struct sk_buff *skb, u32 vxflags)
{
 const struct vxlanhdr *vh = vxlan_hdr(skb);
 enum skb_drop_reason reason;
 size_t start, offset;

 if (!(vh->vx_flags & VXLAN_HF_RCO) || skb->remcsum_offload)
  return SKB_NOT_DROPPED_YET;

 start = vxlan_rco_start(vh->vx_vni);
 offset = start + vxlan_rco_offset(vh->vx_vni);

 reason = pskb_may_pull_reason(skb, offset + sizeof(u16));
 if (reason)
  return reason;

 skb_remcsum_process(skb, (void *)(vxlan_hdr(skb) + 1), start, offset,
       !!(vxflags & VXLAN_F_REMCSUM_NOPARTIAL));
 return SKB_NOT_DROPPED_YET;
}

static void vxlan_parse_gbp_hdr(struct sk_buff *skb, u32 vxflags,
    struct vxlan_metadata *md)
{
 const struct vxlanhdr *vh = vxlan_hdr(skb);
 const struct vxlanhdr_gbp *gbp;
 struct metadata_dst *tun_dst;

 gbp = (const struct vxlanhdr_gbp *)vh;

 if (!(vh->vx_flags & VXLAN_HF_GBP))
  return;

 md->gbp = ntohs(gbp->policy_id);

 tun_dst = (struct metadata_dst *)skb_dst(skb);
 if (tun_dst) {
  __set_bit(IP_TUNNEL_VXLAN_OPT_BIT,
     tun_dst->u.tun_info.key.tun_flags);
  tun_dst->u.tun_info.options_len = sizeof(*md);
 }
 if (gbp->dont_learn)
  md->gbp |= VXLAN_GBP_DONT_LEARN;

 if (gbp->policy_applied)
  md->gbp |= VXLAN_GBP_POLICY_APPLIED;

 /* In flow-based mode, GBP is carried in dst_metadata */
 if (!(vxflags & VXLAN_F_COLLECT_METADATA))
  skb->mark = md->gbp;
}

static enum skb_drop_reason vxlan_set_mac(struct vxlan_dev *vxlan,
       struct vxlan_sock *vs,
       struct sk_buff *skb, __be32 vni)
{
 union vxlan_addr saddr;
 u32 ifindex = skb->dev->ifindex;

 skb_reset_mac_header(skb);
 skb->protocol = eth_type_trans(skb, vxlan->dev);
 skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);

 /* Ignore packet loops (and multicast echo) */
 if (ether_addr_equal(eth_hdr(skb)->h_source, vxlan->dev->dev_addr))
  return SKB_DROP_REASON_LOCAL_MAC;

 /* Get address from the outer IP header */
 if (vxlan_get_sk_family(vs) == AF_INET) {
  saddr.sin.sin_addr.s_addr = ip_hdr(skb)->saddr;
  saddr.sa.sa_family = AF_INET;
#if IS_ENABLED(CONFIG_IPV6)
 } else {
  saddr.sin6.sin6_addr = ipv6_hdr(skb)->saddr;
  saddr.sa.sa_family = AF_INET6;
#endif
 }

 if (!(vxlan->cfg.flags & VXLAN_F_LEARN))
  return SKB_NOT_DROPPED_YET;

 return vxlan_snoop(skb->dev, &saddr, eth_hdr(skb)->h_source,
      ifindex, vni);
}

static bool vxlan_ecn_decapsulate(struct vxlan_sock *vs, void *oiph,
      struct sk_buff *skb)
{
 int err = 0;

 if (vxlan_get_sk_family(vs) == AF_INET)
  err = IP_ECN_decapsulate(oiph, skb);
#if IS_ENABLED(CONFIG_IPV6)
 else
  err = IP6_ECN_decapsulate(oiph, skb);
#endif

 if (unlikely(err) && log_ecn_error) {
  if (vxlan_get_sk_family(vs) == AF_INET)
   net_info_ratelimited("non-ECT from %pI4 with TOS=%#x\n",
          &((struct iphdr *)oiph)->saddr,
          ((struct iphdr *)oiph)->tos);
  else
   net_info_ratelimited("non-ECT from %pI6\n",
          &((struct ipv6hdr *)oiph)->saddr);
 }
 return err <= 1;
}

static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
{
 struct vxlan_vni_node *vninode = NULL;
 const struct vxlanhdr *vh;
 struct vxlan_dev *vxlan;
 struct vxlan_sock *vs;
 struct vxlan_metadata _md;
 struct vxlan_metadata *md = &_md;
 __be16 protocol = htons(ETH_P_TEB);
 enum skb_drop_reason reason;
 bool raw_proto = false;
 void *oiph;
 __be32 vni = 0;
 int nh;

 /* Need UDP and VXLAN header to be present */
 reason = pskb_may_pull_reason(skb, VXLAN_HLEN);
 if (reason)
  goto drop;

 vh = vxlan_hdr(skb);
 /* VNI flag always required to be set */
 if (!(vh->vx_flags & VXLAN_HF_VNI)) {
  netdev_dbg(skb->dev, "invalid vxlan flags=%#x vni=%#x\n",
      ntohl(vh->vx_flags), ntohl(vh->vx_vni));
  reason = SKB_DROP_REASON_VXLAN_INVALID_HDR;
  /* Return non vxlan pkt */
  goto drop;
 }

 vs = rcu_dereference_sk_user_data(sk);
 if (!vs)
  goto drop;

 vni = vxlan_vni(vh->vx_vni);

 vxlan = vxlan_vs_find_vni(vs, skb->dev->ifindex, vni, &vninode);
 if (!vxlan) {
  reason = SKB_DROP_REASON_VXLAN_VNI_NOT_FOUND;
  goto drop;
 }

 if (vh->vx_flags & vxlan->cfg.reserved_bits.vx_flags ||
     vh->vx_vni & vxlan->cfg.reserved_bits.vx_vni) {
  /* If the header uses bits besides those enabled by the
 * netdevice configuration, treat this as a malformed packet.
 * This behavior diverges from VXLAN RFC (RFC7348) which
 * stipulates that bits in reserved in reserved fields are to be
 * ignored. The approach here maintains compatibility with
 * previous stack code, and also is more robust and provides a
 * little more security in adding extensions to VXLAN.
 */

  reason = SKB_DROP_REASON_VXLAN_INVALID_HDR;
  DEV_STATS_INC(vxlan->dev, rx_frame_errors);
  DEV_STATS_INC(vxlan->dev, rx_errors);
  vxlan_vnifilter_count(vxlan, vni, vninode,
          VXLAN_VNI_STATS_RX_ERRORS, 0);
  goto drop;
 }

 if (vxlan->cfg.flags & VXLAN_F_GPE) {
  if (!vxlan_parse_gpe_proto(vh, &protocol))
   goto drop;
  raw_proto = true;
 }

 if (__iptunnel_pull_header(skb, VXLAN_HLEN, protocol, raw_proto,
       !net_eq(vxlan->net, dev_net(vxlan->dev)))) {
  reason = SKB_DROP_REASON_NOMEM;
  goto drop;
 }

 if (vxlan->cfg.flags & VXLAN_F_REMCSUM_RX) {
  reason = vxlan_remcsum(skb, vxlan->cfg.flags);
  if (unlikely(reason))
   goto drop;
 }

 if (vxlan_collect_metadata(vs)) {
  IP_TUNNEL_DECLARE_FLAGS(flags) = { };
  struct metadata_dst *tun_dst;

  __set_bit(IP_TUNNEL_KEY_BIT, flags);
  tun_dst = udp_tun_rx_dst(skb, vxlan_get_sk_family(vs), flags,
      key32_to_tunnel_id(vni), sizeof(*md));

  if (!tun_dst) {
   reason = SKB_DROP_REASON_NOMEM;
   goto drop;
  }

  md = ip_tunnel_info_opts(&tun_dst->u.tun_info);

  skb_dst_set(skb, (struct dst_entry *)tun_dst);
 } else {
  memset(md, 0, sizeof(*md));
 }

 if (vxlan->cfg.flags & VXLAN_F_GBP)
  vxlan_parse_gbp_hdr(skb, vxlan->cfg.flags, md);
 /* Note that GBP and GPE can never be active together. This is
 * ensured in vxlan_dev_configure.
 */


 if (!raw_proto) {
  reason = vxlan_set_mac(vxlan, vs, skb, vni);
  if (reason)
   goto drop;
 } else {
  skb_reset_mac_header(skb);
  skb->dev = vxlan->dev;
  skb->pkt_type = PACKET_HOST;
 }

 /* Save offset of outer header relative to skb->head,
 * because we are going to reset the network header to the inner header
 * and might change skb->head.
 */

 nh = skb_network_header(skb) - skb->head;

 skb_reset_network_header(skb);

 reason = pskb_inet_may_pull_reason(skb);
 if (reason) {
  DEV_STATS_INC(vxlan->dev, rx_length_errors);
  DEV_STATS_INC(vxlan->dev, rx_errors);
  vxlan_vnifilter_count(vxlan, vni, vninode,
          VXLAN_VNI_STATS_RX_ERRORS, 0);
  goto drop;
 }

 /* Get the outer header. */
 oiph = skb->head + nh;

 if (!vxlan_ecn_decapsulate(vs, oiph, skb)) {
  reason = SKB_DROP_REASON_IP_TUNNEL_ECN;
  DEV_STATS_INC(vxlan->dev, rx_frame_errors);
  DEV_STATS_INC(vxlan->dev, rx_errors);
  vxlan_vnifilter_count(vxlan, vni, vninode,
          VXLAN_VNI_STATS_RX_ERRORS, 0);
  goto drop;
 }

 rcu_read_lock();

 if (unlikely(!(vxlan->dev->flags & IFF_UP))) {
  rcu_read_unlock();
  dev_dstats_rx_dropped(vxlan->dev);
  vxlan_vnifilter_count(vxlan, vni, vninode,
          VXLAN_VNI_STATS_RX_DROPS, 0);
  reason = SKB_DROP_REASON_DEV_READY;
  goto drop;
 }

 dev_dstats_rx_add(vxlan->dev, skb->len);
 vxlan_vnifilter_count(vxlan, vni, vninode, VXLAN_VNI_STATS_RX, skb->len);
 gro_cells_receive(&vxlan->gro_cells, skb);

 rcu_read_unlock();

 return 0;

drop:
 reason = reason ?: SKB_DROP_REASON_NOT_SPECIFIED;
 /* Consume bad packet */
 kfree_skb_reason(skb, reason);
 return 0;
}

static int vxlan_err_lookup(struct sock *sk, struct sk_buff *skb)
{
 struct vxlan_dev *vxlan;
 struct vxlan_sock *vs;
 struct vxlanhdr *hdr;
 __be32 vni;

 if (!pskb_may_pull(skb, skb_transport_offset(skb) + VXLAN_HLEN))
  return -EINVAL;

 hdr = vxlan_hdr(skb);

 if (!(hdr->vx_flags & VXLAN_HF_VNI))
  return -EINVAL;

 vs = rcu_dereference_sk_user_data(sk);
 if (!vs)
  return -ENOENT;

 vni = vxlan_vni(hdr->vx_vni);
 vxlan = vxlan_vs_find_vni(vs, skb->dev->ifindex, vni, NULL);
 if (!vxlan)
  return -ENOENT;

 return 0;
}

static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
{
 struct vxlan_dev *vxlan = netdev_priv(dev);
 struct arphdr *parp;
 u8 *arpptr, *sha;
 __be32 sip, tip;
 struct neighbour *n;

 if (dev->flags & IFF_NOARP)
  goto out;

 if (!pskb_may_pull(skb, arp_hdr_len(dev))) {
  dev_dstats_tx_dropped(dev);
  vxlan_vnifilter_count(vxlan, vni, NULL,
          VXLAN_VNI_STATS_TX_DROPS, 0);
  goto out;
 }
 parp = arp_hdr(skb);

 if ((parp->ar_hrd != htons(ARPHRD_ETHER) &&
      parp->ar_hrd != htons(ARPHRD_IEEE802)) ||
     parp->ar_pro != htons(ETH_P_IP) ||
     parp->ar_op != htons(ARPOP_REQUEST) ||
     parp->ar_hln != dev->addr_len ||
     parp->ar_pln != 4)
  goto out;
 arpptr = (u8 *)parp + sizeof(struct arphdr);
 sha = arpptr;
 arpptr += dev->addr_len; /* sha */
 memcpy(&sip, arpptr, sizeof(sip));
 arpptr += sizeof(sip);
 arpptr += dev->addr_len; /* tha */
 memcpy(&tip, arpptr, sizeof(tip));

 if (ipv4_is_loopback(tip) ||
     ipv4_is_multicast(tip))
  goto out;

 n = neigh_lookup(&arp_tbl, &tip, dev);

 if (n) {
  struct vxlan_rdst *rdst = NULL;
  struct vxlan_fdb *f;
  struct sk_buff *reply;

  if (!(READ_ONCE(n->nud_state) & NUD_CONNECTED)) {
   neigh_release(n);
   goto out;
  }

  rcu_read_lock();
  f = vxlan_find_mac_tx(vxlan, n->ha, vni);
  if (f)
   rdst = first_remote_rcu(f);
  if (rdst && vxlan_addr_any(&rdst->remote_ip)) {
   /* bridge-local neighbor */
   neigh_release(n);
   rcu_read_unlock();
   goto out;
  }
  rcu_read_unlock();

  reply = arp_create(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha,
    n->ha, sha);

  neigh_release(n);

  if (reply == NULL)
   goto out;

  skb_reset_mac_header(reply);
  __skb_pull(reply, skb_network_offset(reply));
  reply->ip_summed = CHECKSUM_UNNECESSARY;
  reply->pkt_type = PACKET_HOST;

  if (netif_rx(reply) == NET_RX_DROP) {
   dev_dstats_rx_dropped(dev);
   vxlan_vnifilter_count(vxlan, vni, NULL,
           VXLAN_VNI_STATS_RX_DROPS, 0);
  }

 } else if (vxlan->cfg.flags & VXLAN_F_L3MISS) {
  union vxlan_addr ipa = {
   .sin.sin_addr.s_addr = tip,
   .sin.sin_family = AF_INET,
  };

  vxlan_ip_miss(dev, &ipa);
 }
out:
 consume_skb(skb);
 return NETDEV_TX_OK;
}

#if IS_ENABLED(CONFIG_IPV6)
static struct sk_buff *vxlan_na_create(struct sk_buff *request,
 struct neighbour *n, bool isrouter)
{
 struct net_device *dev = request->dev;
 struct sk_buff *reply;
 struct nd_msg *ns, *na;
 struct ipv6hdr *pip6;
 u8 *daddr;
 int na_olen = 8; /* opt hdr + ETH_ALEN for target */
 int ns_olen;
 int i, len;

 if (dev == NULL || !pskb_may_pull(request, request->len))
  return NULL;

 len = LL_RESERVED_SPACE(dev) + sizeof(struct ipv6hdr) +
  sizeof(*na) + na_olen + dev->needed_tailroom;
 reply = alloc_skb(len, GFP_ATOMIC);
 if (reply == NULL)
  return NULL;

 reply->protocol = htons(ETH_P_IPV6);
 reply->dev = dev;
 skb_reserve(reply, LL_RESERVED_SPACE(request->dev));
 skb_push(reply, sizeof(struct ethhdr));
 skb_reset_mac_header(reply);

 ns = (struct nd_msg *)(ipv6_hdr(request) + 1);

 daddr = eth_hdr(request)->h_source;
 ns_olen = request->len - skb_network_offset(request) -
  sizeof(struct ipv6hdr) - sizeof(*ns);
 for (i = 0; i < ns_olen-1; i += (ns->opt[i+1]<<3)) {
  if (!ns->opt[i + 1]) {
   kfree_skb(reply);
   return NULL;
  }
  if (ns->opt[i] == ND_OPT_SOURCE_LL_ADDR) {
   daddr = ns->opt + i + sizeof(struct nd_opt_hdr);
   break;
  }
 }

 /* Ethernet header */
 ether_addr_copy(eth_hdr(reply)->h_dest, daddr);
 ether_addr_copy(eth_hdr(reply)->h_source, n->ha);
 eth_hdr(reply)->h_proto = htons(ETH_P_IPV6);
 reply->protocol = htons(ETH_P_IPV6);

 skb_pull(reply, sizeof(struct ethhdr));
 skb_reset_network_header(reply);
 skb_put(reply, sizeof(struct ipv6hdr));

 /* IPv6 header */

 pip6 = ipv6_hdr(reply);
 memset(pip6, 0, sizeof(struct ipv6hdr));
 pip6->version = 6;
 pip6->priority = ipv6_hdr(request)->priority;
 pip6->nexthdr = IPPROTO_ICMPV6;
 pip6->hop_limit = 255;
 pip6->daddr = ipv6_hdr(request)->saddr;
 pip6->saddr = *(struct in6_addr *)n->primary_key;

 skb_pull(reply, sizeof(struct ipv6hdr));
 skb_reset_transport_header(reply);

 /* Neighbor Advertisement */
 na = skb_put_zero(reply, sizeof(*na) + na_olen);
 na->icmph.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT;
 na->icmph.icmp6_router = isrouter;
 na->icmph.icmp6_override = 1;
 na->icmph.icmp6_solicited = 1;
 na->target = ns->target;
 ether_addr_copy(&na->opt[2], n->ha);
 na->opt[0] = ND_OPT_TARGET_LL_ADDR;
 na->opt[1] = na_olen >> 3;

 na->icmph.icmp6_cksum = csum_ipv6_magic(&pip6->saddr,
  &pip6->daddr, sizeof(*na)+na_olen, IPPROTO_ICMPV6,
  csum_partial(na, sizeof(*na)+na_olen, 0));

 pip6->payload_len = htons(sizeof(*na)+na_olen);

 skb_push(reply, sizeof(struct ipv6hdr));

 reply->ip_summed = CHECKSUM_UNNECESSARY;

 return reply;
}

static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
{
 struct vxlan_dev *vxlan = netdev_priv(dev);
 const struct in6_addr *daddr;
 const struct ipv6hdr *iphdr;
 struct inet6_dev *in6_dev;
 struct neighbour *n;
 struct nd_msg *msg;

 rcu_read_lock();
 in6_dev = __in6_dev_get(dev);
 if (!in6_dev)
  goto out;

 iphdr = ipv6_hdr(skb);
 daddr = &iphdr->daddr;
 msg = (struct nd_msg *)(iphdr + 1);

 if (ipv6_addr_loopback(daddr) ||
     ipv6_addr_is_multicast(&msg->target))
  goto out;

 n = neigh_lookup(ipv6_stub->nd_tbl, &msg->target, dev);

 if (n) {
  struct vxlan_rdst *rdst = NULL;
  struct vxlan_fdb *f;
  struct sk_buff *reply;

  if (!(READ_ONCE(n->nud_state) & NUD_CONNECTED)) {
   neigh_release(n);
   goto out;
  }

  f = vxlan_find_mac_tx(vxlan, n->ha, vni);
  if (f)
   rdst = first_remote_rcu(f);
  if (rdst && vxlan_addr_any(&rdst->remote_ip)) {
   /* bridge-local neighbor */
   neigh_release(n);
   goto out;
  }

  reply = vxlan_na_create(skb, n,
     !!(f ? f->flags & NTF_ROUTER : 0));

  neigh_release(n);

  if (reply == NULL)
   goto out;

  if (netif_rx(reply) == NET_RX_DROP) {
   dev_dstats_rx_dropped(dev);
   vxlan_vnifilter_count(vxlan, vni, NULL,
           VXLAN_VNI_STATS_RX_DROPS, 0);
  }
 } else if (vxlan->cfg.flags & VXLAN_F_L3MISS) {
  union vxlan_addr ipa = {
   .sin6.sin6_addr = msg->target,
   .sin6.sin6_family = AF_INET6,
  };

  vxlan_ip_miss(dev, &ipa);
 }

out:
 rcu_read_unlock();
 consume_skb(skb);
 return NETDEV_TX_OK;
}
#endif

static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
{
 struct vxlan_dev *vxlan = netdev_priv(dev);
 struct neighbour *n;

 if (is_multicast_ether_addr(eth_hdr(skb)->h_dest))
  return false;

 n = NULL;
 switch (ntohs(eth_hdr(skb)->h_proto)) {
 case ETH_P_IP:
 {
  struct iphdr *pip;

  if (!pskb_may_pull(skb, sizeof(struct iphdr)))
   return false;
  pip = ip_hdr(skb);
  n = neigh_lookup(&arp_tbl, &pip->daddr, dev);
  if (!n && (vxlan->cfg.flags & VXLAN_F_L3MISS)) {
   union vxlan_addr ipa = {
    .sin.sin_addr.s_addr = pip->daddr,
    .sin.sin_family = AF_INET,
   };

   vxlan_ip_miss(dev, &ipa);
   return false;
  }

  break;
 }
#if IS_ENABLED(CONFIG_IPV6)
 case ETH_P_IPV6:
 {
  struct ipv6hdr *pip6;

  if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
   return false;
  pip6 = ipv6_hdr(skb);
  n = neigh_lookup(ipv6_stub->nd_tbl, &pip6->daddr, dev);
  if (!n && (vxlan->cfg.flags & VXLAN_F_L3MISS)) {
   union vxlan_addr ipa = {
    .sin6.sin6_addr = pip6->daddr,
    .sin6.sin6_family = AF_INET6,
   };

   vxlan_ip_miss(dev, &ipa);
   return false;
  }

  break;
 }
#endif
 default:
  return false;
 }

 if (n) {
  bool diff;

  diff = !ether_addr_equal(eth_hdr(skb)->h_dest, n->ha);
  if (diff) {
   memcpy(eth_hdr(skb)->h_source, eth_hdr(skb)->h_dest,
    dev->addr_len);
   memcpy(eth_hdr(skb)->h_dest, n->ha, dev->addr_len);
  }
  neigh_release(n);
  return diff;
 }

 return false;
}

static int vxlan_build_gpe_hdr(struct vxlanhdr *vxh, __be16 protocol)
{
 struct vxlanhdr_gpe *gpe = (struct vxlanhdr_gpe *)vxh;

 gpe->np_applied = 1;
 gpe->next_protocol = tun_p_from_eth_p(protocol);
 if (!gpe->next_protocol)
  return -EPFNOSUPPORT;
 return 0;
}

static int vxlan_build_skb(struct sk_buff *skb, struct dst_entry *dst,
      int iphdr_len, __be32 vni,
      struct vxlan_metadata *md, u32 vxflags,
      bool udp_sum)
{
 struct vxlanhdr *vxh;
 int min_headroom;
 int err;
 int type = udp_sum ? SKB_GSO_UDP_TUNNEL_CSUM : SKB_GSO_UDP_TUNNEL;
 __be16 inner_protocol = htons(ETH_P_TEB);

 if ((vxflags & VXLAN_F_REMCSUM_TX) &&
     skb->ip_summed == CHECKSUM_PARTIAL) {
  int csum_start = skb_checksum_start_offset(skb);

  if (csum_start <= VXLAN_MAX_REMCSUM_START &&
      !(csum_start & VXLAN_RCO_SHIFT_MASK) &&
      (skb->csum_offset == offsetof(struct udphdr, check) ||
       skb->csum_offset == offsetof(struct tcphdr, check)))
   type |= SKB_GSO_TUNNEL_REMCSUM;
 }

 min_headroom = LL_RESERVED_SPACE(dst->dev) + dst->header_len
   + VXLAN_HLEN + iphdr_len;

 /* Need space for new headers (invalidates iph ptr) */
 err = skb_cow_head(skb, min_headroom);
 if (unlikely(err))
  return err;

 err = iptunnel_handle_offloads(skb, type);
 if (err)
  return err;

 vxh = __skb_push(skb, sizeof(*vxh));
 vxh->vx_flags = VXLAN_HF_VNI;
 vxh->vx_vni = vxlan_vni_field(vni);

 if (type & SKB_GSO_TUNNEL_REMCSUM) {
  unsigned int start;

  start = skb_checksum_start_offset(skb) - sizeof(struct vxlanhdr);
  vxh->vx_vni |= vxlan_compute_rco(start, skb->csum_offset);
  vxh->vx_flags |= VXLAN_HF_RCO;

  if (!skb_is_gso(skb)) {
   skb->ip_summed = CHECKSUM_NONE;
   skb->encapsulation = 0;
  }
 }

 if (vxflags & VXLAN_F_GBP)
  vxlan_build_gbp_hdr(vxh, md);
 if (vxflags & VXLAN_F_GPE) {
  err = vxlan_build_gpe_hdr(vxh, skb->protocol);
  if (err < 0)
   return err;
  inner_protocol = skb->protocol;
 }

 skb_set_inner_protocol(skb, inner_protocol);
 return 0;
}

/* Bypass encapsulation if the destination is local */
static void vxlan_encap_bypass(struct sk_buff *skb, struct vxlan_dev *src_vxlan,
          struct vxlan_dev *dst_vxlan, __be32 vni,
          bool snoop)
{
 union vxlan_addr loopback;
 union vxlan_addr *remote_ip = &dst_vxlan->default_dst.remote_ip;
 unsigned int len = skb->len;
 struct net_device *dev;

 skb->pkt_type = PACKET_HOST;
 skb->encapsulation = 0;
 skb->dev = dst_vxlan->dev;
 __skb_pull(skb, skb_network_offset(skb));

 if (remote_ip->sa.sa_family == AF_INET) {
  loopback.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  loopback.sa.sa_family =  AF_INET;
#if IS_ENABLED(CONFIG_IPV6)
 } else {
  loopback.sin6.sin6_addr = in6addr_loopback;
  loopback.sa.sa_family =  AF_INET6;
#endif
 }

 rcu_read_lock();
 dev = skb->dev;
 if (unlikely(!(dev->flags & IFF_UP))) {
  kfree_skb_reason(skb, SKB_DROP_REASON_DEV_READY);
  goto drop;
 }

 if ((dst_vxlan->cfg.flags & VXLAN_F_LEARN) && snoop)
  vxlan_snoop(dev, &loopback, eth_hdr(skb)->h_source, 0, vni);

 dev_dstats_tx_add(src_vxlan->dev, len);
 vxlan_vnifilter_count(src_vxlan, vni, NULL, VXLAN_VNI_STATS_TX, len);

 if (__netif_rx(skb) == NET_RX_SUCCESS) {
  dev_dstats_rx_add(dst_vxlan->dev, len);
  vxlan_vnifilter_count(dst_vxlan, vni, NULL, VXLAN_VNI_STATS_RX,
          len);
 } else {
drop:
  dev_dstats_rx_dropped(dev);
  vxlan_vnifilter_count(dst_vxlan, vni, NULL,
          VXLAN_VNI_STATS_RX_DROPS, 0);
 }
 rcu_read_unlock();
}

static int encap_bypass_if_local(struct sk_buff *skb, struct net_device *dev,
     struct vxlan_dev *vxlan,
     int addr_family,
     __be16 dst_port, int dst_ifindex, __be32 vni,
     struct dst_entry *dst,
     u32 rt_flags)
{
#if IS_ENABLED(CONFIG_IPV6)
 /* IPv6 rt-flags are checked against RTF_LOCAL, but the value of
 * RTF_LOCAL is equal to RTCF_LOCAL. So to keep code simple
 * we can use RTCF_LOCAL which works for ipv4 and ipv6 route entry.
 */

 BUILD_BUG_ON(RTCF_LOCAL != RTF_LOCAL);
#endif
 /* Bypass encapsulation if the destination is local */
 if (rt_flags & RTCF_LOCAL &&
     !(rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) &&
     vxlan->cfg.flags & VXLAN_F_LOCALBYPASS) {
  struct vxlan_dev *dst_vxlan;

  dst_release(dst);
  dst_vxlan = vxlan_find_vni(vxlan->net, dst_ifindex, vni,
        addr_family, dst_port,
        vxlan->cfg.flags);
  if (!dst_vxlan) {
   DEV_STATS_INC(dev, tx_errors);
   vxlan_vnifilter_count(vxlan, vni, NULL,
           VXLAN_VNI_STATS_TX_ERRORS, 0);
   kfree_skb_reason(skb, SKB_DROP_REASON_VXLAN_VNI_NOT_FOUND);

   return -ENOENT;
  }
  vxlan_encap_bypass(skb, vxlan, dst_vxlan, vni, true);
  return 1;
 }

 return 0;
}

void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
      __be32 default_vni, struct vxlan_rdst *rdst, bool did_rsc)
{
 struct dst_cache *dst_cache;
 struct ip_tunnel_info *info;
 struct ip_tunnel_key *pkey;
 struct ip_tunnel_key key;
 struct vxlan_dev *vxlan = netdev_priv(dev);
 const struct iphdr *old_iph;
 struct vxlan_metadata _md;
 struct vxlan_metadata *md = &_md;
 unsigned int pkt_len = skb->len;
 __be16 src_port = 0, dst_port;
 struct dst_entry *ndst = NULL;
 int addr_family;
 __u8 tos, ttl;
 int ifindex;
 int err;
 u32 flags = vxlan->cfg.flags;
 bool use_cache;
 bool udp_sum = false;
 bool xnet = !net_eq(vxlan->net, dev_net(vxlan->dev));
 enum skb_drop_reason reason;
 bool no_eth_encap;
 __be32 vni = 0;

 no_eth_encap = flags & VXLAN_F_GPE && skb->protocol != htons(ETH_P_TEB);
 reason = skb_vlan_inet_prepare(skb, no_eth_encap);
 if (reason)
  goto drop;

 reason = SKB_DROP_REASON_NOT_SPECIFIED;
 old_iph = ip_hdr(skb);

 info = skb_tunnel_info(skb);
 use_cache = ip_tunnel_dst_cache_usable(skb, info);

 if (rdst) {
  memset(&key, 0, sizeof(key));
  pkey = &key;

  if (vxlan_addr_any(&rdst->remote_ip)) {
   if (did_rsc) {
    /* short-circuited back to local bridge */
    vxlan_encap_bypass(skb, vxlan, vxlan,
         default_vni, true);
    return;
   }
   goto drop;
  }

  addr_family = vxlan->cfg.saddr.sa.sa_family;
  dst_port = rdst->remote_port ? rdst->remote_port : vxlan->cfg.dst_port;
  vni = (rdst->remote_vni) ? : default_vni;
  ifindex = rdst->remote_ifindex;

  if (addr_family == AF_INET) {
   key.u.ipv4.src = vxlan->cfg.saddr.sin.sin_addr.s_addr;
   key.u.ipv4.dst = rdst->remote_ip.sin.sin_addr.s_addr;
  } else {
   key.u.ipv6.src = vxlan->cfg.saddr.sin6.sin6_addr;
   key.u.ipv6.dst = rdst->remote_ip.sin6.sin6_addr;
  }

  dst_cache = &rdst->dst_cache;
  md->gbp = skb->mark;
  if (flags & VXLAN_F_TTL_INHERIT) {
   ttl = ip_tunnel_get_ttl(old_iph, skb);
  } else {
   ttl = vxlan->cfg.ttl;
   if (!ttl && vxlan_addr_multicast(&rdst->remote_ip))
    ttl = 1;
  }
  tos = vxlan->cfg.tos;
  if (tos == 1)
   tos = ip_tunnel_get_dsfield(old_iph, skb);
  if (tos && !info)
   use_cache = false;

  if (addr_family == AF_INET)
   udp_sum = !(flags & VXLAN_F_UDP_ZERO_CSUM_TX);
  else
   udp_sum = !(flags & VXLAN_F_UDP_ZERO_CSUM6_TX);
#if IS_ENABLED(CONFIG_IPV6)
  switch (vxlan->cfg.label_policy) {
  case VXLAN_LABEL_FIXED:
   key.label = vxlan->cfg.label;
   break;
  case VXLAN_LABEL_INHERIT:
   key.label = ip_tunnel_get_flowlabel(old_iph, skb);
   break;
  default:
   DEBUG_NET_WARN_ON_ONCE(1);
   goto drop;
  }
#endif
 } else {
  if (!info) {
   WARN_ONCE(1, "%s: Missing encapsulation instructions\n",
      dev->name);
   goto drop;
  }
  pkey = &info->key;
  addr_family = ip_tunnel_info_af(info);
  dst_port = info->key.tp_dst ? : vxlan->cfg.dst_port;
  vni = tunnel_id_to_key32(info->key.tun_id);
  ifindex = 0;
  dst_cache = &info->dst_cache;
  if (test_bit(IP_TUNNEL_VXLAN_OPT_BIT, info->key.tun_flags)) {
   if (info->options_len < sizeof(*md))
    goto drop;
   md = ip_tunnel_info_opts(info);
  }
  ttl = info->key.ttl;
  tos = info->key.tos;
  udp_sum = test_bit(IP_TUNNEL_CSUM_BIT, info->key.tun_flags);
 }
 src_port = udp_flow_src_port(dev_net(dev), skb, vxlan->cfg.port_min,
         vxlan->cfg.port_max, true);

 rcu_read_lock();
 if (addr_family == AF_INET) {
  struct vxlan_sock *sock4 = rcu_dereference(vxlan->vn4_sock);
  u16 ipcb_flags = 0;
  struct rtable *rt;
  __be16 df = 0;
  __be32 saddr;

  if (!ifindex)
   ifindex = sock4->sock->sk->sk_bound_dev_if;

  rt = udp_tunnel_dst_lookup(skb, dev, vxlan->net, ifindex,
        &saddr, pkey, src_port, dst_port,
        tos, use_cache ? dst_cache : NULL);
  if (IS_ERR(rt)) {
   err = PTR_ERR(rt);
   reason = SKB_DROP_REASON_IP_OUTNOROUTES;
   goto tx_error;
  }

  if (flags & VXLAN_F_MC_ROUTE)
   ipcb_flags |= IPSKB_MCROUTE;

  if (!info) {
   /* Bypass encapsulation if the destination is local */
   err = encap_bypass_if_local(skb, dev, vxlan, AF_INET,
          dst_port, ifindex, vni,
          &rt->dst, rt->rt_flags);
   if (err)
    goto out_unlock;

   if (vxlan->cfg.df == VXLAN_DF_SET) {
    df = htons(IP_DF);
   } else if (vxlan->cfg.df == VXLAN_DF_INHERIT) {
    struct ethhdr *eth = eth_hdr(skb);

    if (ntohs(eth->h_proto) == ETH_P_IPV6 ||
        (ntohs(eth->h_proto) == ETH_P_IP &&
         old_iph->frag_off & htons(IP_DF)))
     df = htons(IP_DF);
   }
  } else if (test_bit(IP_TUNNEL_DONT_FRAGMENT_BIT,
        info->key.tun_flags)) {
   df = htons(IP_DF);
  }

  ndst = &rt->dst;
  err = skb_tunnel_check_pmtu(skb, ndst, vxlan_headroom(flags & VXLAN_F_GPE),
         netif_is_any_bridge_port(dev));
  if (err < 0) {
   goto tx_error;
  } else if (err) {
   if (info) {
    struct ip_tunnel_info *unclone;

    unclone = skb_tunnel_info_unclone(skb);
    if (unlikely(!unclone))
     goto tx_error;

    unclone->key.u.ipv4.src = pkey->u.ipv4.dst;
    unclone->key.u.ipv4.dst = saddr;
   }
   vxlan_encap_bypass(skb, vxlan, vxlan, vni, false);
   dst_release(ndst);
   goto out_unlock;
  }

  tos = ip_tunnel_ecn_encap(tos, old_iph, skb);
  ttl = ttl ? : ip4_dst_hoplimit(&rt->dst);
  err = vxlan_build_skb(skb, ndst, sizeof(struct iphdr),
          vni, md, flags, udp_sum);
  if (err < 0) {
   reason = SKB_DROP_REASON_NOMEM;
   goto tx_error;
  }

  udp_tunnel_xmit_skb(rt, sock4->sock->sk, skb, saddr,
        pkey->u.ipv4.dst, tos, ttl, df,
        src_port, dst_port, xnet, !udp_sum,
        ipcb_flags);
#if IS_ENABLED(CONFIG_IPV6)
 } else {
  struct vxlan_sock *sock6 = rcu_dereference(vxlan->vn6_sock);
  struct in6_addr saddr;
  u16 ip6cb_flags = 0;

  if (!ifindex)
   ifindex = sock6->sock->sk->sk_bound_dev_if;

  ndst = udp_tunnel6_dst_lookup(skb, dev, vxlan->net, sock6->sock,
           ifindex, &saddr, pkey,
           src_port, dst_port, tos,
           use_cache ? dst_cache : NULL);
  if (IS_ERR(ndst)) {
   err = PTR_ERR(ndst);
   ndst = NULL;
   reason = SKB_DROP_REASON_IP_OUTNOROUTES;
   goto tx_error;
  }

  if (flags & VXLAN_F_MC_ROUTE)
   ip6cb_flags |= IP6SKB_MCROUTE;

  if (!info) {
   u32 rt6i_flags = dst_rt6_info(ndst)->rt6i_flags;

   err = encap_bypass_if_local(skb, dev, vxlan, AF_INET6,
          dst_port, ifindex, vni,
          ndst, rt6i_flags);
   if (err)
    goto out_unlock;
  }

  err = skb_tunnel_check_pmtu(skb, ndst,
         vxlan_headroom((flags & VXLAN_F_GPE) | VXLAN_F_IPV6),
         netif_is_any_bridge_port(dev));
  if (err < 0) {
   goto tx_error;
  } else if (err) {
   if (info) {
    struct ip_tunnel_info *unclone;

    unclone = skb_tunnel_info_unclone(skb);
    if (unlikely(!unclone))
     goto tx_error;

    unclone->key.u.ipv6.src = pkey->u.ipv6.dst;
    unclone->key.u.ipv6.dst = saddr;
   }

   vxlan_encap_bypass(skb, vxlan, vxlan, vni, false);
   dst_release(ndst);
   goto out_unlock;
  }

  tos = ip_tunnel_ecn_encap(tos, old_iph, skb);
  ttl = ttl ? : ip6_dst_hoplimit(ndst);
  skb_scrub_packet(skb, xnet);
  err = vxlan_build_skb(skb, ndst, sizeof(struct ipv6hdr),
          vni, md, flags, udp_sum);
  if (err < 0) {
   reason = SKB_DROP_REASON_NOMEM;
   goto tx_error;
  }

  udp_tunnel6_xmit_skb(ndst, sock6->sock->sk, skb, dev,
         &saddr, &pkey->u.ipv6.dst, tos, ttl,
         pkey->label, src_port, dst_port, !udp_sum,
         ip6cb_flags);
#endif
 }
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=89 G=93

¤ Dauer der Verarbeitung: 0.29 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Diese beiden folgenden Angebotsgruppen bietet das Unternehmen

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.