blob: fc99d4d9cf3a61e132aae7c6363cc198860565e1 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**************************************************************************
*/
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/version.h>
#include <net/addrconf.h>
#include <net/dst.h>
#include <net/flow.h>
#include <net/ipv6.h>
#include <net/route.h>
#include <net/vxlan.h>
#include <nss_api_if.h>
#include "nss_vxlanmgr.h"
#include "nss_vxlanmgr_tun_stats.h"
/*
* VxLAN context
*/
extern struct nss_vxlanmgr_ctx vxlan_ctx;
/*
* nss_vxlanmgr_tunnel_ctx_dev_get()
* Find VxLAN tunnel context using netdev.
* Context lock must be held before calling this API.
*/
struct nss_vxlanmgr_tun_ctx *nss_vxlanmgr_tunnel_ctx_dev_get(struct net_device *dev)
{
struct nss_vxlanmgr_tun_ctx *tun_ctx;
list_for_each_entry(tun_ctx, &vxlan_ctx.list, head) {
if (tun_ctx->dev == dev) {
return tun_ctx;
}
}
return NULL;
}
/*
* nss_vxlanmgr_tunnel_tx_msg()
* Transmit VxLAN tunnel operation messages asynchronously.
*/
static nss_tx_status_t nss_vxlanmgr_tunnel_tx_msg(struct nss_ctx_instance *ctx,
struct nss_vxlan_msg *msg,
uint32_t if_num,
enum nss_vxlan_msg_type type,
uint32_t len)
{
nss_vxlan_msg_init(msg, if_num, type, len, NULL, NULL);
return nss_vxlan_tx_msg(ctx, msg);
}
/*
* nss_vxlanmgr_tunnel_tx_msg_sync()
* Transmit VxLAN tunnel operation messages.
*/
static nss_tx_status_t nss_vxlanmgr_tunnel_tx_msg_sync(struct nss_ctx_instance *ctx,
struct nss_vxlan_msg *msg,
uint32_t if_num,
enum nss_vxlan_msg_type type,
uint32_t len)
{
nss_vxlan_msg_init(msg, if_num, type, len, NULL, NULL);
return nss_vxlan_tx_msg_sync(ctx, msg);
}
/*
* nss_vxlanmgr_tunnel_flags_parse()
* Function to parse vxlan flags.
*/
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 5, 7))
static uint16_t nss_vxlanmgr_tunnel_flags_parse(struct vxlan_dev *priv)
{
uint16_t flags = 0;
uint32_t priv_flags = priv->flags;
if (priv_flags & VXLAN_F_RSC)
return flags;
if (priv_flags & VXLAN_F_GBP)
flags |= NSS_VXLAN_RULE_FLAG_GBP_ENABLED;
if (priv_flags & VXLAN_F_IPV6) {
flags |= NSS_VXLAN_RULE_FLAG_IPV6;
if (!(priv_flags & VXLAN_F_UDP_ZERO_CSUM6_TX))
flags |= NSS_VXLAN_RULE_FLAG_ENCAP_L4_CSUM_REQUIRED;
} else {
flags |= NSS_VXLAN_RULE_FLAG_IPV4;
if (priv_flags & VXLAN_F_UDP_CSUM)
flags |= NSS_VXLAN_RULE_FLAG_ENCAP_L4_CSUM_REQUIRED;
}
if (priv->cfg.tos == 1)
flags |= NSS_VXLAN_RULE_FLAG_INHERIT_TOS;
return (flags | NSS_VXLAN_RULE_FLAG_UDP);
}
#else
static uint16_t nss_vxlanmgr_tunnel_flags_parse(struct vxlan_dev *priv)
{
uint16_t flags = 0;
struct vxlan_config *cfg = &priv->cfg;
uint32_t priv_flags = cfg->flags;
if (priv_flags & VXLAN_F_RSC)
return flags;
if (priv_flags & VXLAN_F_GPE)
return flags;
if (priv_flags & VXLAN_F_GBP)
flags |= NSS_VXLAN_RULE_FLAG_GBP_ENABLED;
if (priv_flags & VXLAN_F_IPV6) {
flags |= NSS_VXLAN_RULE_FLAG_IPV6;
if (!(priv_flags & VXLAN_F_UDP_ZERO_CSUM6_TX))
flags |= NSS_VXLAN_RULE_FLAG_ENCAP_L4_CSUM_REQUIRED;
} else {
flags |= NSS_VXLAN_RULE_FLAG_IPV4;
if (!(priv_flags & VXLAN_F_UDP_ZERO_CSUM_TX))
flags |= NSS_VXLAN_RULE_FLAG_ENCAP_L4_CSUM_REQUIRED;
}
if (cfg->tos == 1)
flags |= NSS_VXLAN_RULE_FLAG_INHERIT_TOS;
return (flags | NSS_VXLAN_RULE_FLAG_UDP);
}
#endif
/*
* nss_vxlanmgr_tunnel_fill_src_ip()
* Return src_ip using route lookup.
*/
static bool nss_vxlanmgr_tunnel_fill_src_ip(struct vxlan_dev *vxlan,
union vxlan_addr *src_ip,
union vxlan_addr *rem_ip,
sa_family_t sa_family,
uint32_t *new_src_ip)
{
struct flowi4 fl4;
struct flowi6 fl6;
struct rtable *rt = NULL;
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 5, 7))
struct dst_entry *dst = NULL;
int err;
#else
const struct in6_addr *final_dst = NULL;
struct dst_entry *dentry;
#endif
/*
* IPv4
*/
if (sa_family == AF_INET) {
if (src_ip->sin.sin_addr.s_addr == htonl(INADDR_ANY)) {
/*
* Lookup
*/
memset(&fl4, 0, sizeof(fl4));
fl4.flowi4_proto = IPPROTO_UDP;
fl4.daddr = rem_ip->sin.sin_addr.s_addr;
fl4.saddr = src_ip->sin.sin_addr.s_addr;
rt = ip_route_output_key(vxlan->net, &fl4);
if (IS_ERR(rt)) {
nss_vxlanmgr_warn("No route available.\n");
return false;
}
new_src_ip[0] = fl4.saddr;
return true;
}
new_src_ip[0] = src_ip->sin.sin_addr.s_addr;
return true;
}
/*
* IPv6
*/
if (ipv6_addr_any(&src_ip->sin6.sin6_addr)) {
/*
* Lookup
*/
memset(&fl6, 0, sizeof(fl6));
fl6.flowi6_proto = IPPROTO_UDP;
fl6.daddr = rem_ip->sin6.sin6_addr;
fl6.saddr = src_ip->sin6.sin6_addr;
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 5, 7))
err = ipv6_stub->ipv6_dst_lookup(vxlan->net,
vxlan->vn6_sock->sock->sk, &dst, &fl6);
if (err < 0) {
#else
dentry = ipv6_stub->ipv6_dst_lookup_flow(vxlan->net,
vxlan->vn6_sock->sock->sk, &fl6, final_dst);
if (!dentry) {
#endif
nss_vxlanmgr_warn("No route, drop packet.\n");
return false;
}
memcpy(new_src_ip, &fl6.saddr, sizeof(struct in6_addr));
return true;
}
memcpy(new_src_ip, &src_ip->sin6.sin6_addr, sizeof(struct in6_addr));
return true;
}
/*
* nss_vxlanmgr_tunnel_mac_del()
* VxLAN tunnel mac delete messages.
*/
static nss_tx_status_t nss_vxlanmgr_tunnel_mac_del(struct nss_vxlanmgr_tun_ctx *tun_ctx,
struct vxlan_fdb_event *vfe)
{
struct net_device *dev;
struct nss_vxlan_mac_msg *mac_del_msg;
struct nss_vxlan_msg vxlanmsg;
struct vxlan_config *cfg;
struct vxlan_dev *priv;
union vxlan_addr *remote_ip, *src_ip;
uint32_t i, inner_ifnum;
nss_tx_status_t status = NSS_TX_FAILURE;
dev = vfe->dev;
dev_hold(dev);
spin_lock_bh(&vxlan_ctx.tun_lock);
inner_ifnum = tun_ctx->inner_ifnum;
spin_unlock_bh(&vxlan_ctx.tun_lock);
/*
* Only non-zero mac entries should be sent to NSS.
*/
if (is_zero_ether_addr(vfe->eth_addr)) {
nss_vxlanmgr_trace("Only non-zero mac entries should be sent to NSS.\n");
goto done;
}
memset(&vxlanmsg, 0, sizeof(struct nss_vxlan_msg));
priv = netdev_priv(dev);
/*
* Set MAC rule message
*/
mac_del_msg = &vxlanmsg.msg.mac_del;
mac_del_msg->vni = vxlan_get_vni(priv);
ether_addr_copy((uint8_t *)mac_del_msg->mac_addr, (uint8_t *)vfe->eth_addr);
cfg = &priv->cfg;
src_ip = &cfg->saddr;
remote_ip = &vfe->rdst->remote_ip;
if (remote_ip->sa.sa_family == AF_INET){
if (remote_ip->sin.sin_addr.s_addr == htonl(INADDR_ANY)) {
nss_vxlanmgr_warn("%px: MAC deletion failed for unknown remote\n", dev);
goto done;
}
memcpy(&mac_del_msg->encap.dest_ip, &remote_ip->sin.sin_addr, sizeof(struct in_addr));
memcpy(&mac_del_msg->encap.src_ip, &src_ip->sin.sin_addr, sizeof(struct in_addr));
} else {
if (ipv6_addr_any(&remote_ip->sin6.sin6_addr)) {
nss_vxlanmgr_warn("%px: MAC deletion failed for unknown remote\n", dev);
goto done;
}
memcpy(&mac_del_msg->encap.dest_ip, &remote_ip->sin6.sin6_addr, sizeof(struct in6_addr));
memcpy(&mac_del_msg->encap.src_ip, &src_ip->sin6.sin6_addr, sizeof(struct in6_addr));
}
/*
* Send MAC del message asynchronously as it is called by chain
* notifier in atomic context from the vxlan driver.
*/
status = nss_vxlanmgr_tunnel_tx_msg(vxlan_ctx.nss_ctx,
&vxlanmsg,
inner_ifnum,
NSS_VXLAN_MSG_TYPE_MAC_DEL,
sizeof(struct nss_vxlan_mac_msg));
if (status != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: MAC deletion failed %d\n", dev, status);
}
spin_lock_bh(&vxlan_ctx.tun_lock);
for (i = 0; i < NSS_VXLAN_MACDB_ENTRIES_MAX; i++) {
if (ether_addr_equal((uint8_t *)&tun_ctx->stats->mac_stats[i][0], (uint8_t *)vfe->eth_addr)) {
tun_ctx->stats->mac_stats[i][0] = 0;
tun_ctx->stats->mac_stats[i][1] = 0;
break;
}
}
spin_unlock_bh(&vxlan_ctx.tun_lock);
done:
dev_put(dev);
return status;
}
/*
* nss_vxlanmgr_tunnel_mac_add()
* VxLAN tunnel mac add messages.
*/
static nss_tx_status_t nss_vxlanmgr_tunnel_mac_add(struct nss_vxlanmgr_tun_ctx *tun_ctx,
struct vxlan_fdb_event *vfe)
{
struct net_device *dev;
struct nss_vxlan_mac_msg *mac_add_msg;
struct nss_vxlan_msg vxlanmsg;
struct vxlan_config *cfg;
struct vxlan_dev *priv;
union vxlan_addr *remote_ip, *src_ip;
uint32_t i, inner_ifnum;
uint32_t new_src_ip[4] = {0};
nss_tx_status_t status = NSS_TX_FAILURE;
dev = vfe->dev;
dev_hold(dev);
spin_lock_bh(&vxlan_ctx.tun_lock);
inner_ifnum = tun_ctx->inner_ifnum;
spin_unlock_bh(&vxlan_ctx.tun_lock);
/*
* Only non-zero mac entries should be sent to NSS.
*/
if (is_zero_ether_addr(vfe->eth_addr)) {
nss_vxlanmgr_trace("Only non-zero mac entries should be sent to NSS.\n");
goto done;
}
memset(&vxlanmsg, 0, sizeof(struct nss_vxlan_msg));
priv = netdev_priv(dev);
/*
* Set MAC rule message
*/
mac_add_msg = &vxlanmsg.msg.mac_add;
mac_add_msg->vni = vxlan_get_vni(priv);
ether_addr_copy((uint8_t *)mac_add_msg->mac_addr, (uint8_t *)vfe->eth_addr);
cfg = &priv->cfg;
src_ip = &cfg->saddr;
remote_ip = &vfe->rdst->remote_ip;
if (remote_ip->sa.sa_family == AF_INET){
if (remote_ip->sin.sin_addr.s_addr == htonl(INADDR_ANY)) {
nss_vxlanmgr_warn("%px: MAC addition failed for unknown remote\n", dev);
goto done;
}
memcpy(&mac_add_msg->encap.dest_ip[0], &remote_ip->sin.sin_addr, sizeof(struct in_addr));
if (!nss_vxlanmgr_tunnel_fill_src_ip(priv, src_ip, remote_ip, AF_INET, new_src_ip)) {
nss_vxlanmgr_warn("%px: MAC addition failed for unknown source\n", dev);
goto done;
}
mac_add_msg->encap.src_ip[0] = new_src_ip[0];
} else {
if (ipv6_addr_any(&remote_ip->sin6.sin6_addr)) {
nss_vxlanmgr_warn("%px: MAC addition failed for unknown remote\n", dev);
goto done;
}
memcpy(mac_add_msg->encap.dest_ip, &remote_ip->sin6.sin6_addr, sizeof(struct in6_addr));
if (!nss_vxlanmgr_tunnel_fill_src_ip(priv, src_ip, remote_ip, AF_INET6, new_src_ip)) {
nss_vxlanmgr_warn("%px: MAC addition failed for unknown source\n", dev);
goto done;
}
memcpy(mac_add_msg->encap.src_ip, new_src_ip, sizeof(struct in6_addr));
}
/*
* Send MAC add message asynchronously as it is called by chain
* notifier in atomic context from the vxlan driver.
*/
status = nss_vxlanmgr_tunnel_tx_msg(vxlan_ctx.nss_ctx,
&vxlanmsg,
inner_ifnum,
NSS_VXLAN_MSG_TYPE_MAC_ADD,
sizeof(struct nss_vxlan_mac_msg));
if (status != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: MAC addition failed %d\n", dev, status);
goto done;
}
spin_lock_bh(&vxlan_ctx.tun_lock);
for (i = 0; i < NSS_VXLAN_MACDB_ENTRIES_MAX; i++) {
if (!tun_ctx->stats->mac_stats[i][0]) {
ether_addr_copy((uint8_t *)&tun_ctx->stats->mac_stats[i][0],
(uint8_t *)vfe->eth_addr);
break;
}
}
spin_unlock_bh(&vxlan_ctx.tun_lock);
done:
dev_put(dev);
return status;
}
/*
* nss_vxlanmgr_tunnel_fdb_event()
* Event handler for VxLAN fdb updates
*/
static int nss_vxlanmgr_tunnel_fdb_event(struct notifier_block *nb, unsigned long event, void *data)
{
struct vxlan_fdb_event *vfe;
struct nss_vxlanmgr_tun_ctx *tun_ctx;
vfe = (struct vxlan_fdb_event *)data;
spin_lock_bh(&vxlan_ctx.tun_lock);
tun_ctx = nss_vxlanmgr_tunnel_ctx_dev_get(vfe->dev);
if (!tun_ctx) {
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_warn("%px: Invalid tunnel context\n", vfe->dev);
return NOTIFY_DONE;
}
spin_unlock_bh(&vxlan_ctx.tun_lock);
switch(event) {
case RTM_DELNEIGH:
nss_vxlanmgr_tunnel_mac_del(tun_ctx, vfe);
break;
case RTM_NEWNEIGH:
nss_vxlanmgr_tunnel_mac_add(tun_ctx, vfe);
break;
default:
nss_vxlanmgr_warn("%lu: Unknown FDB event received.\n", event);
}
return NOTIFY_DONE;
}
/*
* Notifier to receive fdb events from VxLAN
*/
static struct notifier_block nss_vxlanmgr_tunnel_fdb_notifier = {
.notifier_call = nss_vxlanmgr_tunnel_fdb_event,
};
/*
* nss_vxlanmgr_tunnel_inner_stats()
* Update vxlan netdev stats with inner node stats
*/
static void nss_vxlanmgr_tunnel_inner_stats(struct nss_vxlanmgr_tun_ctx *tun_ctx, struct nss_vxlan_msg *nvm)
{
struct nss_vxlan_stats_msg *stats;
struct pcpu_sw_netstats *tstats;
struct net_device *dev;
struct net_device_stats *netdev_stats;
uint32_t i;
uint64_t dropped = 0;
stats = &nvm->msg.stats;
dev = tun_ctx->dev;
dev_hold(dev);
netdev_stats = (struct net_device_stats *)&dev->stats;
/*
* Only look at the tx_packets/tx_bytes for both host_inner/outer interfaces.
* rx_bytes/rx_packets are increased when the packet is received by the node.
* Therefore, it includes both transmitted/dropped packets. tx_bytes/tx_packets
* reflect successfully transmitted packets.
*/
for (i = 0; i < NSS_MAX_NUM_PRI; i++) {
dropped += stats->node_stats.rx_dropped[i];
}
tstats = this_cpu_ptr(dev->tstats);
u64_stats_update_begin(&tstats->syncp);
tstats->tx_packets += stats->node_stats.tx_packets;
tstats->tx_bytes += stats->node_stats.tx_bytes;
u64_stats_update_end(&tstats->syncp);
netdev_stats->tx_dropped += dropped;
dev_put(dev);
}
/*
* nss_vxlanmgr_tunnel_outer_stats()
* Update vxlan netdev stats with outer node stats
*/
static void nss_vxlanmgr_tunnel_outer_stats(struct nss_vxlanmgr_tun_ctx *tun_ctx, struct nss_vxlan_msg *nvm)
{
struct nss_vxlan_stats_msg *stats;
struct pcpu_sw_netstats *tstats;
struct net_device *dev;
struct net_device_stats *netdev_stats;
uint32_t i;
uint64_t dropped = 0;
stats = &nvm->msg.stats;
dev = tun_ctx->dev;
dev_hold(dev);
netdev_stats = (struct net_device_stats *)&dev->stats;
/*
* Only look at the tx_packets/tx_bytes for both host_inner/outer interfaces.
* rx_bytes/rx_packets are increased when the packet is received by the node.
* Therefore, it includes both transmitted/dropped packets. tx_bytes/tx_packets
* reflect successfully transmitted packets.
*/
for (i = 0; i < NSS_MAX_NUM_PRI; i++) {
dropped += stats->node_stats.rx_dropped[i];
}
tstats = this_cpu_ptr(dev->tstats);
u64_stats_update_begin(&tstats->syncp);
tstats->rx_packets += stats->node_stats.tx_packets;
tstats->rx_bytes += stats->node_stats.tx_bytes;
u64_stats_update_end(&tstats->syncp);
netdev_stats->rx_dropped += dropped;
dev_put(dev);
}
/*
* nss_vxlanmgr_tunnel_fdb_update()
* Update vxlan fdb entries
*/
static void nss_vxlanmgr_tunnel_fdb_update(struct nss_vxlanmgr_tun_ctx *tun_ctx, struct nss_vxlan_msg *nvm)
{
uint8_t *mac;
uint16_t i, nentries;
struct vxlan_dev *priv;
struct nss_vxlan_macdb_stats_msg *db_stats;
db_stats = &nvm->msg.db_stats;
nentries = db_stats->cnt;
priv = netdev_priv(tun_ctx->dev);
dev_hold(tun_ctx->dev);
if (nentries > NSS_VXLAN_MACDB_ENTRIES_PER_MSG) {
nss_vxlanmgr_warn("%px: No more than 20 entries allowed per message.\n", tun_ctx->dev);
dev_put(tun_ctx->dev);
return;
}
for (i = 0; i < nentries; i++) {
if (likely(db_stats->entry[i].hits)) {
mac = (uint8_t *)db_stats->entry[i].mac;
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 5, 7))
vxlan_fdb_update_mac(priv, mac);
#else
vxlan_fdb_update_mac(priv, mac, tun_ctx->vni);
#endif
}
}
dev_put(tun_ctx->dev);
}
/*
* nss_vxlanmgr_tunnel_inner_notifier()
* Notifier for vxlan tunnel encap node
*/
static void nss_vxlanmgr_tunnel_inner_notifier(void *app_data, struct nss_cmn_msg *ncm)
{
struct net_device *dev = (struct net_device *)app_data;
struct nss_vxlanmgr_tun_ctx *tun_ctx;
struct nss_vxlan_msg *nvm;
if (!ncm) {
nss_vxlanmgr_info("%px: NULL msg received.\n", dev);
return;
}
spin_lock_bh(&vxlan_ctx.tun_lock);
tun_ctx = nss_vxlanmgr_tunnel_ctx_dev_get(dev);
if (!tun_ctx) {
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_warn("%px: Invalid tunnel context\n", dev);
return;
}
nvm = (struct nss_vxlan_msg *)ncm;
switch (nvm->cm.type) {
case NSS_VXLAN_MSG_TYPE_STATS_SYNC:
nss_vxlanmgr_tunnel_inner_stats(tun_ctx, nvm);
nss_vxlanmgr_tun_stats_sync(tun_ctx, nvm);
break;
case NSS_VXLAN_MSG_TYPE_MACDB_STATS:
nss_vxlanmgr_tunnel_fdb_update(tun_ctx, nvm);
nss_vxlanmgr_tun_macdb_stats_sync(tun_ctx, nvm);
break;
default:
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_info("%px: Unknown Event from NSS", dev);
return;
}
spin_unlock_bh(&vxlan_ctx.tun_lock);
}
/*
* nss_vxlanmgr_tunnel_outer_notifier()
* Notifier for vxlan tunnel decap node
*/
static void nss_vxlanmgr_tunnel_outer_notifier(void *app_data, struct nss_cmn_msg *ncm)
{
struct net_device *dev = (struct net_device *)app_data;
struct nss_vxlanmgr_tun_ctx *tun_ctx;
struct nss_vxlan_msg *nvm;
if (!ncm) {
nss_vxlanmgr_info("%px: NULL msg received.\n", dev);
return;
}
spin_lock_bh(&vxlan_ctx.tun_lock);
tun_ctx = nss_vxlanmgr_tunnel_ctx_dev_get(dev);
if (!tun_ctx) {
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_warn("%px: Invalid tunnel context\n", dev);
return;
}
nvm = (struct nss_vxlan_msg *)ncm;
switch (nvm->cm.type) {
case NSS_VXLAN_MSG_TYPE_STATS_SYNC:
nss_vxlanmgr_tunnel_outer_stats(tun_ctx, nvm);
nss_vxlanmgr_tun_stats_sync(tun_ctx, nvm);
break;
default:
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_info("%px: Unknown Event from NSS", dev);
return;
}
spin_unlock_bh(&vxlan_ctx.tun_lock);
}
/*
* nss_vxlanmgr_tunnel_inner_recv()
* Receives a pkt from NSS
*/
static void nss_vxlanmgr_tunnel_inner_recv(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
dev_hold(dev);
nss_vxlanmgr_info("%px: (vxlan packet) Exception packet received.\n", dev);
/*
* These are decapped and exceptioned packets.
*/
skb->protocol = eth_type_trans(skb, dev);
netif_receive_skb(skb);
dev_put(dev);
return;
}
/*
* nss_vxlanmgr_tunnel_outer_recv()
* Receives a pkt from NSS
*/
static void nss_vxlanmgr_tunnel_outer_recv(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct iphdr *iph;
size_t l3_hdr_size;
struct nss_vxlanmgr_tun_ctx *tun_ctx;
nss_vxlanmgr_info("%px: (vxlan packet) Exception packet received.\n", dev);
spin_lock_bh(&vxlan_ctx.tun_lock);
tun_ctx = nss_vxlanmgr_tunnel_ctx_dev_get(dev);
if (!tun_ctx) {
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_warn("%px: Invalid tunnel context\n", dev);
dev_kfree_skb_any(skb);
return;
}
iph = (struct iphdr *)skb->data;
switch (iph->version) {
case 4:
l3_hdr_size = sizeof(struct iphdr);
skb->protocol = htons(ETH_P_IP);
break;
case 6:
l3_hdr_size = sizeof(struct ipv6hdr);
skb->protocol = htons(ETH_P_IPV6);
break;
default:
tun_ctx->stats->host_packet_drop++;
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_trace("%px: Skb received with unknown IP version: %d.\n", dev, iph->version);
dev_kfree_skb_any(skb);
return;
}
/*
* VxLAN encapsulated packet exceptioned, remove the encapsulation
* and transmit on VxLAN interface.
*/
if (unlikely(!pskb_may_pull(skb, (l3_hdr_size + sizeof(struct udphdr)
+ sizeof(struct vxlanhdr))))) {
tun_ctx->stats->host_packet_drop++;
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_trace("%px: pskb_may_pull failed for skb:%px\n", dev, skb);
dev_kfree_skb_any(skb);
return;
}
skb_pull(skb, (l3_hdr_size + sizeof(struct udphdr) + sizeof(struct vxlanhdr)));
/*
* Inner ethernet payload.
*/
if (unlikely(!pskb_may_pull(skb, sizeof(struct ethhdr)))) {
tun_ctx->stats->host_packet_drop++;
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_trace("%px: pskb_may_pull failed for skb:%px\n", dev, skb);
dev_kfree_skb_any(skb);
return;
}
spin_unlock_bh(&vxlan_ctx.tun_lock);
skb->dev = dev;
skb_reset_mac_header(skb);
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
skb_reset_mac_len(skb);
dev_queue_xmit(skb);
}
/*
* nss_vxlanmgr_tunnel_deconfig()
* Function to send dynamic interface disable message
*/
int nss_vxlanmgr_tunnel_deconfig(struct net_device *dev)
{
struct nss_vxlanmgr_tun_ctx *tun_ctx;
uint32_t inner_ifnum, outer_ifnum;
struct nss_vxlan_msg vxlanmsg;
nss_tx_status_t ret;
dev_hold(dev);
spin_lock_bh(&vxlan_ctx.tun_lock);
tun_ctx = nss_vxlanmgr_tunnel_ctx_dev_get(dev);
if (!tun_ctx) {
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_warn("%px: Invalid tunnel context\n", dev);
goto done;
}
inner_ifnum = tun_ctx->inner_ifnum;
outer_ifnum = tun_ctx->outer_ifnum;
spin_unlock_bh(&vxlan_ctx.tun_lock);
memset(&vxlanmsg, 0, sizeof(struct nss_vxlan_msg));
ret = nss_vxlanmgr_tunnel_tx_msg_sync(vxlan_ctx.nss_ctx,
&vxlanmsg,
inner_ifnum,
NSS_VXLAN_MSG_TYPE_TUN_DISABLE, 0);
if (ret != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: Sending configuration to inner interface failed: %d\n", dev, ret);
goto done;
}
ret = nss_vxlanmgr_tunnel_tx_msg_sync(vxlan_ctx.nss_ctx,
&vxlanmsg,
outer_ifnum,
NSS_VXLAN_MSG_TYPE_TUN_DISABLE, 0);
if (ret != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: Sending configuration to outer interface failed: %d\n", dev, ret);
}
done:
dev_put(dev);
return NOTIFY_DONE;
}
/*
* nss_vxlanmgr_tunnel_config()
* Function to send dynamic interface enable message
*/
int nss_vxlanmgr_tunnel_config(struct net_device *dev)
{
uint32_t inner_ifnum, outer_ifnum;
struct nss_vxlanmgr_tun_ctx *tun_ctx;
struct nss_vxlan_msg vxlanmsg;
nss_tx_status_t ret;
dev_hold(dev);
spin_lock_bh(&vxlan_ctx.tun_lock);
tun_ctx = nss_vxlanmgr_tunnel_ctx_dev_get(dev);
if (!tun_ctx) {
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_warn("%px: Invalid tunnel context\n", dev);
goto done;
}
inner_ifnum = tun_ctx->inner_ifnum;
outer_ifnum = tun_ctx->outer_ifnum;
spin_unlock_bh(&vxlan_ctx.tun_lock);
memset(&vxlanmsg, 0, sizeof(struct nss_vxlan_msg));
ret = nss_vxlanmgr_tunnel_tx_msg_sync(vxlan_ctx.nss_ctx,
&vxlanmsg,
inner_ifnum,
NSS_VXLAN_MSG_TYPE_TUN_ENABLE, 0);
if (ret != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: Sending configuration to inner interface failed: %d\n", dev, ret);
goto done;
}
ret = nss_vxlanmgr_tunnel_tx_msg_sync(vxlan_ctx.nss_ctx,
&vxlanmsg,
outer_ifnum,
NSS_VXLAN_MSG_TYPE_TUN_ENABLE, 0);
if (ret != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: Sending configuration to outer interface failed: %d\n", dev, ret);
/*
* Disable inner node.
*/
nss_vxlanmgr_tunnel_tx_msg_sync(vxlan_ctx.nss_ctx,
&vxlanmsg,
inner_ifnum,
NSS_VXLAN_MSG_TYPE_TUN_DISABLE, 0);
}
done:
dev_put(dev);
return NOTIFY_DONE;
}
/*
* nss_vxlanmgr_tunnel_destroy()
* Function to unregister and destroy dynamic interfaces.
*/
int nss_vxlanmgr_tunnel_destroy(struct net_device *dev)
{
uint32_t inner_ifnum, outer_ifnum;
struct nss_vxlanmgr_tun_ctx *tun_ctx;
struct nss_vxlan_msg vxlanmsg;
nss_tx_status_t ret;
dev_hold(dev);
spin_lock_bh(&vxlan_ctx.tun_lock);
if (!vxlan_ctx.tun_count) {
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_warn("%px: No more tunnels to destroy.\n", dev);
goto done;
}
tun_ctx = nss_vxlanmgr_tunnel_ctx_dev_get(dev);
if (!tun_ctx) {
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_warn("%px: Invalid tunnel context\n", dev);
goto done;
}
inner_ifnum = tun_ctx->inner_ifnum;
outer_ifnum = tun_ctx->outer_ifnum;
/*
* Remove tunnel from global list.
*/
list_del(&tun_ctx->head);
/*
* Decrement interface count.
*/
vxlan_ctx.tun_count--;
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_tun_stats_deinit(tun_ctx);
nss_vxlanmgr_tun_stats_dentry_remove(tun_ctx);
kfree(tun_ctx);
if (!vxlan_ctx.tun_count) {
/*
* Unregister fdb notifier chain if
* all vxlan tunnels are destroyed.
*/
vxlan_fdb_unregister_notify(&nss_vxlanmgr_tunnel_fdb_notifier);
}
nss_vxlanmgr_info("%px: VxLAN interface count is #%d\n", dev, vxlan_ctx.tun_count);
memset(&vxlanmsg, 0, sizeof(struct nss_vxlan_msg));
ret = nss_vxlanmgr_tunnel_tx_msg_sync(vxlan_ctx.nss_ctx,
&vxlanmsg,
inner_ifnum,
NSS_VXLAN_MSG_TYPE_TUN_UNCONFIGURE, 0);
if (ret != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: Sending configuration to inner interface failed: %d\n", dev, ret);
}
if (!nss_vxlan_unregister_if(inner_ifnum)) {
nss_vxlanmgr_warn("%px: Inner interface not found\n", dev);
}
ret = nss_dynamic_interface_dealloc_node(inner_ifnum,
NSS_DYNAMIC_INTERFACE_TYPE_VXLAN_INNER);
if (ret != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: Failed to dealloc inner: %d\n", dev, ret);
}
ret = nss_vxlanmgr_tunnel_tx_msg_sync(vxlan_ctx.nss_ctx,
&vxlanmsg,
outer_ifnum,
NSS_VXLAN_MSG_TYPE_TUN_UNCONFIGURE, 0);
if (ret != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: Sending configuration to outer interface failed: %d\n", dev, ret);
}
if (!nss_vxlan_unregister_if(outer_ifnum)) {
nss_vxlanmgr_warn("%px: Outer interface not found\n", dev);
}
ret = nss_dynamic_interface_dealloc_node(outer_ifnum,
NSS_DYNAMIC_INTERFACE_TYPE_VXLAN_OUTER);
if (ret != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: Failed to dealloc outer: %d\n", dev, ret);
}
done:
dev_put(dev);
return NOTIFY_DONE;
}
/*
* nss_vxlanmgr_tunnel_create()
* Function to create and register dynamic interfaces.
*/
int nss_vxlanmgr_tunnel_create(struct net_device *dev)
{
struct vxlan_dev *priv;
struct nss_vxlan_msg vxlanmsg;
struct nss_vxlanmgr_tun_ctx *tun_ctx;
struct nss_vxlan_rule_msg *vxlan_cfg;
struct nss_ctx_instance *nss_ctx;
uint32_t inner_ifnum, outer_ifnum;
uint16_t parse_flags;
nss_tx_status_t ret;
spin_lock_bh(&vxlan_ctx.tun_lock);
if (vxlan_ctx.tun_count == NSS_VXLAN_MAX_TUNNELS) {
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_warn("%px: Max number of vxlan interfaces supported is %d\n", dev, NSS_VXLAN_MAX_TUNNELS);
return NOTIFY_DONE;
}
spin_unlock_bh(&vxlan_ctx.tun_lock);
dev_hold(dev);
priv = netdev_priv(dev);
parse_flags = nss_vxlanmgr_tunnel_flags_parse(priv);
/*
* Check if the tunnel is supported.
*/
if (!parse_flags) {
nss_vxlanmgr_warn("%px: Tunnel offload not supported\n", dev);
goto ctx_alloc_fail;
}
tun_ctx = kzalloc(sizeof(struct nss_vxlanmgr_tun_ctx), GFP_ATOMIC);
if (!tun_ctx) {
nss_vxlanmgr_warn("Failed to allocate memory for tun_ctx\n");
goto ctx_alloc_fail;
}
tun_ctx->dev = dev;
tun_ctx->vxlan_ctx = &vxlan_ctx;
INIT_LIST_HEAD(&tun_ctx->head);
inner_ifnum = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_VXLAN_INNER);
if (inner_ifnum < 0) {
nss_vxlanmgr_warn("%px: Inner interface allocation failed.\n", dev);
goto inner_alloc_fail;
}
tun_ctx->inner_ifnum = inner_ifnum;
outer_ifnum = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_VXLAN_OUTER);
if (outer_ifnum < 0) {
nss_vxlanmgr_warn("%px: Outer interface allocation failed.\n", dev);
goto outer_alloc_fail;
}
tun_ctx->outer_ifnum = outer_ifnum;
/*
* Register vxlan tunnel with NSS
*/
nss_ctx = nss_vxlan_register_if(inner_ifnum, NSS_DYNAMIC_INTERFACE_TYPE_VXLAN_INNER,
nss_vxlanmgr_tunnel_inner_recv,
nss_vxlanmgr_tunnel_inner_notifier, dev, 0);
if (!nss_ctx) {
nss_vxlanmgr_warn("%px: Failed to register inner iface\n", dev);
goto inner_reg_fail;
}
nss_ctx = nss_vxlan_register_if(outer_ifnum, NSS_DYNAMIC_INTERFACE_TYPE_VXLAN_OUTER,
nss_vxlanmgr_tunnel_outer_recv,
nss_vxlanmgr_tunnel_outer_notifier, dev, 0);
if (!nss_ctx) {
nss_vxlanmgr_warn("%px: Failed to register outer iface\n", dev);
goto outer_reg_fail;
}
nss_vxlanmgr_trace("%px: Successfully registered inner and outer iface for VxLAN\n", dev);
memset(&vxlanmsg, 0, sizeof(struct nss_vxlan_msg));
vxlan_cfg = &vxlanmsg.msg.vxlan_create;
vxlan_cfg->vni = vxlan_get_vni(priv);
vxlan_cfg->tunnel_flags = parse_flags;
vxlan_cfg->src_port_min = priv->cfg.port_min;
vxlan_cfg->src_port_max = priv->cfg.port_max;
vxlan_cfg->dest_port = priv->cfg.dst_port;
vxlan_cfg->tos = priv->cfg.tos;
vxlan_cfg->ttl = (priv->cfg.ttl ? priv->cfg.ttl : IPDEFTTL);
vxlan_cfg->sibling_if_num = outer_ifnum;
ret = nss_vxlanmgr_tunnel_tx_msg_sync(vxlan_ctx.nss_ctx,
&vxlanmsg,
inner_ifnum,
NSS_VXLAN_MSG_TYPE_TUN_CONFIGURE,
sizeof(struct nss_vxlan_rule_msg));
if (ret != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: Sending configuration to inner interface failed: %d\n", dev, ret);
goto config_fail;
}
vxlan_cfg->sibling_if_num = inner_ifnum;
ret = nss_vxlanmgr_tunnel_tx_msg_sync(vxlan_ctx.nss_ctx,
&vxlanmsg,
outer_ifnum,
NSS_VXLAN_MSG_TYPE_TUN_CONFIGURE,
sizeof(struct nss_vxlan_rule_msg));
if (ret != NSS_TX_SUCCESS) {
nss_vxlanmgr_warn("%px: Sending configuration to outer interface failed: %d\n", dev, ret);
goto config_fail;
}
if (!nss_vxlanmgr_tun_stats_dentry_create(tun_ctx)) {
nss_vxlanmgr_warn("%px: Tun stats dentry init failed\n", vxlan_ctx.nss_ctx);
goto config_fail;
}
if (!nss_vxlanmgr_tun_stats_init(tun_ctx)) {
nss_vxlanmgr_warn("%px: Tun stats init failed\n", vxlan_ctx.nss_ctx);
goto config_fail;
}
tun_ctx->vni = vxlan_cfg->vni;
tun_ctx->tunnel_flags = vxlan_cfg->tunnel_flags;
tun_ctx->flow_label = vxlan_cfg->flow_label;
tun_ctx->src_port_min = vxlan_cfg->src_port_min;
tun_ctx->src_port_max = vxlan_cfg->src_port_max;
tun_ctx->dest_port = vxlan_cfg->dest_port;
tun_ctx->tos = vxlan_cfg->tos;
tun_ctx->ttl = vxlan_cfg->ttl;
spin_lock_bh(&vxlan_ctx.tun_lock);
/*
* Add tunnel to global list.
*/
list_add(&tun_ctx->head, &vxlan_ctx.list);
if (!vxlan_ctx.tun_count) {
/*
* Register with fdb notifier chain
* when first tunnel is created.
*/
vxlan_fdb_register_notify(&nss_vxlanmgr_tunnel_fdb_notifier);
}
/*
* Increment vxlan tunnel interface count
*/
vxlan_ctx.tun_count++;
spin_unlock_bh(&vxlan_ctx.tun_lock);
nss_vxlanmgr_info("%px: VxLAN interface count is #%d\n", dev, vxlan_ctx.tun_count);
dev_put(dev);
return NOTIFY_DONE;
config_fail:
nss_vxlan_unregister_if(outer_ifnum);
outer_reg_fail:
nss_vxlan_unregister_if(inner_ifnum);
inner_reg_fail:
ret = nss_dynamic_interface_dealloc_node(outer_ifnum, NSS_DYNAMIC_INTERFACE_TYPE_VXLAN_OUTER);
if (ret != NSS_TX_SUCCESS)
nss_vxlanmgr_warn("%px: Outer interface dealloc failed: %d\n", dev, ret);
outer_alloc_fail:
ret = nss_dynamic_interface_dealloc_node(inner_ifnum, NSS_DYNAMIC_INTERFACE_TYPE_VXLAN_INNER);
if (ret != NSS_TX_SUCCESS)
nss_vxlanmgr_warn("%px: Inner interface dealloc failed: %d\n", dev, ret);
inner_alloc_fail:
kfree(tun_ctx);
ctx_alloc_fail:
dev_put(dev);
return NOTIFY_DONE;
}