Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


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.27 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge