blob: d6c992774b8604fc78c618aef91717a28bc4ce88 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2017-2018, 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_v4.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/ip.h>
#include <net/route.h>
#include <net/ip_tunnels.h>
#include <net/ip6_tunnel.h>
#include <net/arp.h>
#include <net/gre.h>
#include <nss_api_if.h>
#include "nss_connmgr_gre_public.h"
#include "nss_connmgr_gre.h"
/*
* nss_connmgr_gre_v4_get_tx_dev()
* Find tx interface for IP address. Holds ref to next_dev.
*/
static struct net_device *nss_connmgr_gre_v4_get_tx_dev(uint32_t dest_ip)
{
struct rtable *rt;
struct net_device *dev;
uint32_t ip_addr __attribute__ ((unused)) = ntohl(dest_ip);
rt = ip_route_output(&init_net, htonl(dest_ip), 0, 0, 0);
if (IS_ERR(rt)) {
nss_connmgr_gre_warning("Unable to lookup route for %pI4\n", &ip_addr);
return NULL;
}
dev = rt->dst.dev;
if (!dev) {
ip_rt_put(rt);
nss_connmgr_gre_warning("Unable to find route dev for %pI4\n", &ip_addr);
return NULL;
}
dev_hold(dev);
ip_rt_put(rt);
return dev;
}
/*
* nss_connmgr_gre_v4_get_mac_address()
* Find source and destination MAC for source and destination IP address.
*/
static int nss_connmgr_gre_v4_get_mac_address(uint32_t src_ip, uint32_t dest_ip,
uint8_t *src_mac, uint8_t *dest_mac)
{
struct neighbour *neigh;
struct rtable *rt;
__be32 raddr = htonl(dest_ip);
__be32 laddr = htonl(src_ip);
/*
* find local MAC address
*/
struct net_device *local_dev = ip_dev_find(&init_net, laddr);
if (!local_dev) {
nss_connmgr_gre_warning("Unable to find local dev for %pI4", &laddr);
return GRE_ERR_NO_LOCAL_NETDEV;
}
ether_addr_copy(src_mac, local_dev->dev_addr);
dev_put(local_dev);
nss_connmgr_gre_info("Src MAC address for %pI4 is %pM\n", &laddr, src_mac);
rt = ip_route_output(&init_net, raddr, 0, 0, 0);
if (IS_ERR(rt)) {
nss_connmgr_gre_warning("route look up failed for %pI4\n", &raddr);
return GRE_ERR_RADDR_ROUTE_LOOKUP;
}
neigh = dst_neigh_lookup(&rt->dst, (const void *)&raddr);
if (!neigh) {
neigh = neigh_lookup(&arp_tbl, (const void *)&raddr, rt->dst.dev);
}
if (neigh) {
if (!(neigh->nud_state & NUD_VALID) || !is_valid_ether_addr(neigh->ha)) {
nss_connmgr_gre_info("neigh lookup failed for %pI4, state=%x, neigh->ha=%pM\n", &raddr, neigh->nud_state, neigh->ha);
neigh_release(neigh);
neigh = NULL;
}
}
/*
* Send arp request
*/
if (!neigh) {
neigh = neigh_create(&arp_tbl, &raddr, rt->dst.dev);
if (IS_ERR_OR_NULL(neigh)) {
nss_connmgr_gre_warning("Unable to create ARP request neigh for %pI4\n", &raddr);
ip_rt_put(rt);
return GRE_ERR_NEIGH_CREATE;
}
nss_connmgr_gre_info("Send ARP request neigh for %pI4\n", &raddr);
neigh_event_send(neigh, NULL);
msleep(2000);
}
if (!(neigh->nud_state & NUD_VALID) || !is_valid_ether_addr(neigh->ha)) {
ip_rt_put(rt);
nss_connmgr_gre_warning("invalid neigh state (%x) or invalid MAC(%pM) for %pI4\n", neigh->nud_state, neigh->ha, &raddr);
neigh_release(neigh);
return GRE_ERR_NEIGH_CREATE;
}
if (neigh->dev->type == ARPHRD_LOOPBACK) {
ip_rt_put(rt);
neigh_release(neigh);
nss_connmgr_gre_warning("Err in destination MAC address, neighbour dev is loop back for %pI4\n", &raddr);
return GRE_ERR_NEIGH_DEV_LOOPBACK;
}
if (neigh->dev->flags & IFF_NOARP) {
ip_rt_put(rt);
neigh_release(neigh);
nss_connmgr_gre_warning("Err in destination MAC address, neighbour dev is of type NO_ARP for %pI4\n", &raddr);
return GRE_ERR_NEIGH_DEV_NOARP;
}
ether_addr_copy(dest_mac, neigh->ha);
ip_rt_put(rt);
neigh_release(neigh);
nss_connmgr_gre_info("Destination MAC address for %pI4 is %pM\n", &raddr, dest_mac);
return GRE_SUCCESS;
}
/*
* nss_connmgr_gre_v4_set_config()
* Set User configuration to netdevice
*/
int nss_connmgr_gre_v4_set_config(struct net_device *dev, struct nss_connmgr_gre_cfg *cfg)
{
nss_connmgr_gre_priv_t *priv = netdev_priv(dev);
struct ip_tunnel *t = (struct ip_tunnel *)priv;
struct iphdr *iphdr;
/*
* 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;
}
}
/*
* IP address validate
*/
if ((cfg->src_ip == 0) || (cfg->dest_ip == 0)) {
nss_connmgr_gre_warning("Source ip/Destination IP is invalid");
return GRE_ERR_INVALID_IP;
}
memset(t, 0, sizeof(struct ip_tunnel));
priv->pad_len = (cfg->add_padding) ? GRE_HDR_PAD_LEN : 0;
priv->gre_hlen = nss_connmgr_gre_get_hlen(cfg);
iphdr = (struct iphdr *)&(t->parms.iph);
iphdr->protocol = IPPROTO_GRE;
memcpy(&iphdr->saddr, (uint8_t *)cfg->src_ip, 4);
memcpy(&iphdr->daddr, (uint8_t *)cfg->dest_ip, 4);
iphdr->saddr = htonl(iphdr->saddr);
iphdr->daddr = htonl(iphdr->daddr);
iphdr->tos = cfg->tos << 2;
if (cfg->tos_inherit) {
iphdr->tos |= 0x1;
}
iphdr->ttl = cfg->ttl;
if (cfg->ttl_inherit) {
iphdr->ttl = 0;
}
if (cfg->set_df) {
iphdr->frag_off = htons(IP_DF);
}
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_tap_v4_outer_exception()
* Handle IPv4 exception for GRETAP outer device
*/
void nss_connmgr_gre_tap_v4_outer_exception(struct net_device *dev, struct sk_buff *skb)
{
struct ethhdr *eth_hdr = (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 iphdr)
+ 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 iphdr)
+ 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_hdr->h_proto) >= ETH_P_802_3_MIN)) {
skb->protocol = eth_hdr->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_v4_outer_exception()
* Handle IPv4 exception for GRETUN outer device
*/
void nss_connmgr_gre_tun_v4_outer_exception(struct net_device *dev, struct sk_buff *skb)
{
struct iphdr *iph;
/*
* GRE encapsulated packet exceptioned, remove the encapsulation
* and transmit on GRE interface.
*/
if (unlikely(!pskb_may_pull(skb, sizeof(struct iphdr) + 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 iphdr) + sizeof(struct gre_base_hdr));
iph = (struct iphdr *)skb->data;
skb->dev = dev;
switch (iph->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_v4_get_config()
* Fill in config message to send to NSS.
*/
int nss_connmgr_gre_v4_get_config(struct net_device *dev, struct nss_gre_msg *req,
struct net_device **next_dev, bool hold)
{
uint32_t src_ip, dest_ip;
struct ip_tunnel *t = netdev_priv(dev);
struct iphdr *iphdr = (struct iphdr *)&(t->parms.iph);
struct net_device *out_dev;
struct nss_gre_config_msg *cmsg = &req->msg.cmsg;
int ret;
src_ip = ntohl(iphdr->saddr);
dest_ip = ntohl(iphdr->daddr);
memcpy(cmsg->src_ip, &src_ip, 4);
memcpy(cmsg->dest_ip, &dest_ip, 4);
cmsg->flags |= nss_connmgr_gre_get_nss_config_flags(t->parms.o_flags,
t->parms.i_flags,
iphdr->tos, iphdr->ttl,
iphdr->frag_off);
cmsg->ikey = t->parms.i_key;
cmsg->okey = t->parms.o_key;
cmsg->ttl = iphdr->ttl;
cmsg->tos = iphdr->tos >> 2;
/*
* fill in MAC addresses
*/
ret = nss_connmgr_gre_v4_get_mac_address(src_ip, dest_ip,
(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_v4_get_tx_dev(dest_ip);
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;
}