blob: eb82663d7314a1ee65fdb546663fdbcc56f78302 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2017-2020 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.
**************************************************************************
*/
/*
* nss_connnmgr_gre_v6.c
*
* This file implements client for GRE implementation.
*/
#include <linux/version.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/etherdevice.h>
#include <net/route.h>
#include <net/ip6_route.h>
#include <net/ip6_tunnel.h>
#include <net/ip_tunnels.h>
#include <net/addrconf.h>
#include <net/gre.h>
#include <nss_api_if.h>
#include "nss_connmgr_gre_public.h"
#include "nss_connmgr_gre.h"
/*
* nss_connmgr_gre_v6_route_lookup()
* Find IPv6 route for the IP address
*/
static inline struct rt6_info *nss_connmgr_gre_v6_route_lookup(struct net *net, struct in6_addr *addr)
{
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0))
return rt6_lookup(net, addr, NULL, 0, 0);
#else
return rt6_lookup(net, addr, NULL, 0, 0, 0);
#endif
}
/*
* nss_connmgr_gre_v6_get_tx_dev()
* Find tx interface for the IP address.
*/
static struct net_device *nss_connmgr_gre_v6_get_tx_dev(uint8_t *dest_ip)
{
struct rt6_info *rt;
struct in6_addr ipv6_addr;
struct net_device *dev;
memcpy(ipv6_addr.s6_addr, dest_ip, 16);
rt = nss_connmgr_gre_v6_route_lookup(&init_net, &ipv6_addr);
if (!rt) {
return NULL;
}
dev = rt->dst.dev;
if (!dev) {
ip6_rt_put(rt);
return NULL;
}
dev_hold(dev);
ip6_rt_put(rt);
return dev;
}
/*
* nss_connmgr_gre_v6_get_mac_address()
* Find source and destination MAC address for source and destination IP
* address.
*/
static int nss_connmgr_gre_v6_get_mac_address(uint8_t *src_ip, uint8_t *dest_ip,
uint8_t *src_mac, uint8_t *dest_mac)
{
struct neighbour *neigh;
struct rt6_info *rt;
struct in6_addr src_addr, dst_addr, mc_dst_addr;
struct net_device *local_dev;
memcpy(src_addr.s6_addr, src_ip, 16);
memcpy(dst_addr.s6_addr, dest_ip, 16);
/*
* Find src MAC address
*/
local_dev = (struct net_device *)ipv6_dev_find(&init_net, &src_addr, 1);
if (!local_dev) {
nss_connmgr_gre_warning("Unable to find local dev for %pI6", src_ip);
return GRE_ERR_NO_LOCAL_NETDEV;
}
ether_addr_copy(src_mac, local_dev->dev_addr);
dev_put(local_dev);
/*
* Find dest MAC address
*/
rt = nss_connmgr_gre_v6_route_lookup(&init_net, &dst_addr);
if (!rt) {
nss_connmgr_gre_warning("Unable to find route lookup for %pI6", dest_ip);
return GRE_ERR_NEIGH_LOOKUP;
}
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,6,0))
neigh = rt->dst.ops->neigh_lookup(&rt->dst, &dst_addr);
#else
neigh = rt->dst.ops->neigh_lookup(&rt->dst, NULL, &dst_addr);
#endif
if (neigh) {
if (!(neigh->nud_state & NUD_VALID) || !is_valid_ether_addr(neigh->ha)) {
nss_connmgr_gre_warning("neigh state is either invalid (%x) or mac address is null (%pM) for %pI6", neigh->nud_state, neigh->ha, dest_ip);
neigh_release(neigh);
neigh = NULL;
}
}
if (!neigh) {
/*
* Issue a Neighbour soliciation request
*/
nss_connmgr_gre_info("Issue Neighbour solicitation request\n");
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0))
ndisc_send_ns(local_dev, &dst_addr, &mc_dst_addr, &src_addr);
#else
ndisc_send_ns(local_dev, &dst_addr, &mc_dst_addr, &src_addr, 0);
#endif
msleep(2000);
/*
* Release hold on existing route entry, and find the route entry again
*/
ip6_rt_put(rt);
rt = nss_connmgr_gre_v6_route_lookup(&init_net, &dst_addr);
if (!rt) {
nss_connmgr_gre_warning("Unable to find route lookup for %pI6\n", dest_ip);
return GRE_ERR_NEIGH_LOOKUP;
}
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3,6,0))
neigh = rt->dst.ops->neigh_lookup(&rt->dst, &dst_addr);
#else
neigh = rt->dst.ops->neigh_lookup(&rt->dst, NULL, &dst_addr);
#endif
if (!neigh) {
ip6_rt_put(rt);
nss_connmgr_gre_warning("Err in MAC address, neighbour look up failed\n");
return GRE_ERR_NEIGH_LOOKUP;
}
if (!(neigh->nud_state & NUD_VALID) || !is_valid_ether_addr(neigh->ha)) {
ip6_rt_put(rt);
nss_connmgr_gre_warning("Err in MAC address, invalid neigh state (%x) or invalid mac(%pM)\n", neigh->nud_state, neigh->ha);
neigh_release(neigh);
return GRE_ERR_NEIGH_LOOKUP;
}
}
ether_addr_copy(dest_mac, neigh->ha);
ip6_rt_put(rt);
neigh_release(neigh);
return GRE_SUCCESS;
}
/*
* nss_connmgr_gre_tap_v6_outer_exception()
* Handle IPv6 exception for GRETAP outer device
*/
void nss_connmgr_gre_tap_v6_outer_exception(struct net_device *dev, struct sk_buff *skb)
{
struct ethhdr *eth = (struct ethhdr *)skb->data;
/*
* GRE encapsulated packet exceptioned, remove the encapsulation
* and transmit on GRE interface.
*/
if (unlikely(!pskb_may_pull(skb, (sizeof(struct ethhdr) + sizeof(struct ipv6hdr)
+ sizeof(struct gre_base_hdr))))) {
nss_connmgr_gre_warning("%px: pskb_may_pull failed for skb:%px\n", dev, skb);
dev_kfree_skb_any(skb);
return;
}
/*
* TODO: Support parsing GRE options
*/
skb_pull(skb, (sizeof(struct ethhdr) + sizeof(struct ipv6hdr)
+ sizeof(struct gre_base_hdr)));
if (unlikely(!pskb_may_pull(skb, sizeof(struct ethhdr)))) {
nss_connmgr_gre_warning("%px: pskb_may_pull failed for skb:%px\n", dev, skb);
dev_kfree_skb_any(skb);
return;
}
skb->dev = dev;
if (likely(ntohs(eth->h_proto) >= ETH_P_802_3_MIN)) {
skb->protocol = eth->h_proto;
} else {
skb->protocol = htons(ETH_P_802_2);
}
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_connmgr_gre_tun_v6_outer_exception()
* Handle IPv6 exception for GRETUN outer device
*/
void nss_connmgr_gre_tun_v6_outer_exception(struct net_device *dev, struct sk_buff *skb)
{
struct ipv6hdr *ip6h;
/*
* GRE encapsulated packet exceptioned, remove the encapsulation
* and transmit on GRE interface.
*/
if (unlikely(!pskb_may_pull(skb, sizeof(struct ipv6hdr) + sizeof(struct gre_base_hdr)))) {
nss_connmgr_gre_warning("%px: pskb_may_pull failed for skb:%px\n", dev, skb);
dev_kfree_skb_any(skb);
return;
}
/*
* TODO: Support parsing GRE options
*/
skb_pull(skb, sizeof(struct ipv6hdr) + sizeof(struct gre_base_hdr));
ip6h = (struct ipv6hdr *)skb->data;
skb->dev = dev;
switch (ip6h->version) {
case 4:
skb->protocol = htons(ETH_P_IP);
break;
case 6:
skb->protocol = htons(ETH_P_IPV6);
break;
default:
nss_connmgr_gre_info("%px: wrong IP version in GRE encapped packet. skb: %px\n", dev, skb);
dev_kfree_skb_any(skb);
return;
}
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_connmgr_gre_v6_set_config()
* Set user config to dev inerface.
*/
int nss_connmgr_gre_v6_set_config(struct net_device *dev, struct nss_connmgr_gre_cfg *cfg)
{
nss_connmgr_gre_priv_t *priv = netdev_priv(dev);
struct ip6_tnl *t = (struct ip6_tnl *)priv;
/*
* IP address validate
*/
if (ipv6_addr_any(((const struct in6_addr *)&cfg->src_ip)) ||
ipv6_addr_any(((const struct in6_addr *)&cfg->dest_ip))) {
nss_connmgr_gre_warning("Source ip/Destination IP is invalid");
return GRE_ERR_INVALID_IP;
}
/*
* MAC address validate
*/
if (cfg->use_mac_hdr) {
if (!is_valid_ether_addr((const u8 *)cfg->src_mac) ||
!is_valid_ether_addr((const u8 *)cfg->dest_mac)) {
nss_connmgr_gre_warning("User should provide valid MAC address if flag add_mac hdr is set\n");
return GRE_ERR_INVALID_MAC;
}
}
memset(t, 0, sizeof(struct ip6_tnl));
priv->pad_len = (cfg->add_padding) ? GRE_HDR_PAD_LEN : 0;
priv->gre_hlen = nss_connmgr_gre_get_hlen(cfg);
memcpy(t->parms.laddr.s6_addr, &cfg->src_ip, 16);
memcpy(t->parms.raddr.s6_addr, &cfg->dest_ip, 16);
t->parms.flowinfo = cfg->tos << 2;
if (cfg->tos_inherit) {
t->parms.flowinfo |= 0x1;
}
t->parms.hop_limit = cfg->ttl;
if (cfg->ttl_inherit) {
t->parms.hop_limit = 0;
}
if (cfg->ikey_valid) {
t->parms.i_key = cfg->ikey;
}
if (cfg->okey_valid) {
t->parms.o_key = cfg->okey;
}
nss_connmgr_gre_set_gre_flags(cfg, &t->parms.o_flags, &t->parms.i_flags);
strlcpy(t->parms.name, dev->name, IFNAMSIZ);
t->dev = dev;
return GRE_SUCCESS;
}
/*
* nss_connmgr_gre_v6_get_config()
* Fill info in config message to send to NSS.
*/
int nss_connmgr_gre_v6_get_config(struct net_device *dev, struct nss_gre_msg *req,
struct net_device **next_dev, bool hold)
{
struct ip6_tnl *t = netdev_priv(dev);
struct net_device *out_dev;
struct nss_gre_config_msg *cmsg = &req->msg.cmsg;
int ret;
struct in6_addr *src_ip = &t->parms.laddr;
struct in6_addr *dest_ip = &t->parms.raddr;
/*
* Store IPv6 addresses in host endian in the message.
*/
cmsg->src_ip[0] = ntohl(src_ip->in6_u.u6_addr32[0]);
cmsg->src_ip[1] = ntohl(src_ip->in6_u.u6_addr32[1]);
cmsg->src_ip[2] = ntohl(src_ip->in6_u.u6_addr32[2]);
cmsg->src_ip[3] = ntohl(src_ip->in6_u.u6_addr32[3]);
cmsg->dest_ip[0] = ntohl(dest_ip->in6_u.u6_addr32[0]);
cmsg->dest_ip[1] = ntohl(dest_ip->in6_u.u6_addr32[1]);
cmsg->dest_ip[2] = ntohl(dest_ip->in6_u.u6_addr32[2]);
cmsg->dest_ip[3] = ntohl(dest_ip->in6_u.u6_addr32[3]);
/*
* IPv6 outer tos field is always inherited from inner IP header.
*/
cmsg->flags |= nss_connmgr_gre_get_nss_config_flags(t->parms.o_flags,
t->parms.i_flags,
t->parms.flowinfo,
t->parms.hop_limit, 0);
cmsg->ikey = t->parms.i_key;
cmsg->okey = t->parms.o_key;
cmsg->ttl = t->parms.hop_limit;
cmsg->tos = t->parms.flowinfo >> 2;
/*
* fill in MAC addresses
*/
ret = nss_connmgr_gre_v6_get_mac_address(t->parms.laddr.s6_addr, t->parms.raddr.s6_addr,
(uint8_t *)cmsg->src_mac,
(uint8_t *)cmsg->dest_mac);
if (!ret) {
cmsg->flags |= NSS_GRE_CONFIG_SET_MAC;
}
/*
* fill in NSS interface number
*/
out_dev = nss_connmgr_gre_v6_get_tx_dev(t->parms.raddr.s6_addr);
if (out_dev) {
cmsg->next_node_if_num = nss_cmn_get_interface_number_by_dev(out_dev);
cmsg->flags |= NSS_GRE_CONFIG_NEXT_NODE_AVAILABLE;
*next_dev = out_dev;
if (!hold) {
dev_put(out_dev);
}
}
return GRE_SUCCESS;
}