blob: 68246f02e1d8ed1cf2f6a607518d9ccda280bf17 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2015-2017, 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.
**************************************************************************
*/
/*
* nss_connnmgr_l2tpv2.c
*
* This file is the NSS l2tpv2 tunnel module
*/
#include <linux/types.h>
#include <linux/ip.h>
#include <linux/rcupdate.h>
#include <linux/module.h>
#include <linux/of.h>
#include <net/ipv6.h>
#include <linux/rwlock_types.h>
#include <linux/if_pppox.h>
#include <linux/hashtable.h>
#include <linux/inetdevice.h>
#include <linux/l2tp.h>
#include <linux/if_arp.h>
#include <linux/version.h>
#include <linux/sysctl.h>
#include <net/route.h>
#include <linux/refcount.h>
#include <linux/../../net/l2tp/l2tp_core.h>
#include <nss_api_if.h>
#include <nss_dynamic_interface.h>
#include "nss_connmgr_l2tpv2.h"
#include "nss_l2tpv2_stats.h"
#include "nss_l2tpmgr.h"
#define L2TP_HDRFLAG_T 0x8000
#define L2TP_HDRFLAG_L 0x4000
#define L2TP_HDRFLAG_S 0x0800
#define L2TP_HDRFLAG_O 0x0200
#define L2TP_HDRFLAG_P 0x0100
#define L2TP_HDR_MIN_LEN 6
#define PPP_HDR_LEN 4
#define BYTES_PER_SHORT 2
/*
* NSS l2tpv2 debug macros
*/
#if (NSS_L2TP_DEBUG_LEVEL < 1)
#define nss_connmgr_l2tpv2_assert(fmt, args...)
#else
#define nss_connmgr_l2tpv2_assert(c) BUG_ON(!(c));
#endif
#if defined(CONFIG_DYNAMIC_DEBUG)
/*
* Compile messages for dynamic enable/disable
*/
#define nss_connmgr_l2tpv2_warning(s, ...) pr_debug("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#define nss_connmgr_l2tpv2_info(s, ...) pr_debug("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#define nss_connmgr_l2tpv2_trace(s, ...) pr_debug("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#else
/*
* Statically compile messages at different levels
*/
#if (NSS_L2TP_DEBUG_LEVEL < 2)
#define nss_connmgr_l2tpv2_warning(s, ...)
#else
#define nss_connmgr_l2tpv2_warning(s, ...) pr_warn("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#endif
#if (NSS_L2TP_DEBUG_LEVEL < 3)
#define nss_connmgr_l2tpv2_info(s, ...)
#else
#define nss_connmgr_l2tpv2_info(s, ...) pr_notice("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#endif
#if (NSS_L2TP_DEBUG_LEVEL < 4)
#define nss_connmgr_l2tpv2_trace(s, ...)
#else
#define nss_connmgr_l2tpv2_trace(s, ...) pr_info("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#endif
#endif
#define NUM_L2TP_CHANNELS_IN_PPP_NETDEVICE 1
#define HASH_BUCKET_SIZE 2 /* ( 2^ HASH_BUCKET_SIZE ) == 4 */
#define L2TP_SYSCTL_STR_LEN_MAX 2*IFNAMSIZ + 1 /* Size is determined to accomodate two netdevice names and a space in between */
static DEFINE_HASHTABLE(l2tpv2_session_data_hash_table, HASH_BUCKET_SIZE);
static int ip_ttl_max = 255;
#if defined(NSS_L2TP_IPSEC_BIND_BY_NETDEV)
static char l2tpoipsec_config[L2TP_SYSCTL_STR_LEN_MAX];
static struct ctl_table_header *ctl_tbl_hdr; /* l2tpv2 sysctl */
#endif
static struct l2tpmgr_ipsecmgr_cb __rcu ipsecmgr_cb;
/*
* nss_connmgr_l2tpv2_msg_cb()
* L2TP message callback.
*/
static void nss_connmgr_l2tpv2_msg_cb(void *app_data, struct nss_l2tpv2_msg *msg) {
struct nss_cmn_msg *ncm = &msg->cm;
if (ncm->response != NSS_CMN_RESPONSE_ACK) {
nss_connmgr_l2tpv2_warning("Recevied NACK for L2TP message type = %d from NSS\n", ncm->type);
}
nss_connmgr_l2tpv2_trace("Recevied ACK for L2TP message type = %d from NSS\n", ncm->type);
}
/*
* nss_connmgr_l2tpv2_get_session()
* retrieve ppp over l2tpv2 session associated with this netdevice if any
*
*/
static struct l2tp_session *nss_connmgr_l2tpv2_get_session(struct net_device *dev)
{
struct ppp_channel *ppp_chan[1] = {NULL};
int px_proto;
struct sock *sk;
struct l2tp_session *session = NULL;
/*
* check whether the interface is of type PPP
*/
if (dev->type != ARPHRD_PPP || !(dev->flags & IFF_POINTOPOINT)) {
nss_connmgr_l2tpv2_info("netdevice is not a PPP tunnel type\n");
return NULL;
}
if (ppp_is_multilink(dev)) {
nss_connmgr_l2tpv2_info("channel is multilink PPP\n");
return NULL;
}
if (ppp_hold_channels(dev, ppp_chan, 1) != 1) {
nss_connmgr_l2tpv2_info("hold channel for netdevice failed\n");
return NULL;
}
if (!ppp_chan[0]) {
nss_connmgr_l2tpv2_info("channel don't have a ppp_channel\n");
goto err_nss_connmgr_l2tpv2_get_session_1;
}
px_proto = ppp_channel_get_protocol(ppp_chan[0]);
if (px_proto != PX_PROTO_OL2TP) {
nss_connmgr_l2tpv2_info("session socket is not of type PX_PROTO_OL2TP\n");
goto err_nss_connmgr_l2tpv2_get_session_1;
}
sk = (struct sock *)ppp_chan[0]->private;
if (!sk) {
nss_connmgr_l2tpv2_info("session socket is NULL\n");
goto err_nss_connmgr_l2tpv2_get_session_1;
}
sock_hold(sk);
/*
* This is very unlikely, But check the socket is connected state
*/
if (unlikely(sock_flag(sk, SOCK_DEAD) || !(sk->sk_state & PPPOX_CONNECTED))) {
nss_connmgr_l2tpv2_info("l2tpv2 session sock is either dead or not in connected state\n");
goto err_nss_connmgr_l2tpv2_get_session_2;
}
session = (struct l2tp_session *)(sk->sk_user_data);
if (!session) {
nss_connmgr_l2tpv2_info("l2tpv2 session is null\n");
goto err_nss_connmgr_l2tpv2_get_session_2;
}
session_hold(session);
err_nss_connmgr_l2tpv2_get_session_2:
sock_put(sk);
err_nss_connmgr_l2tpv2_get_session_1:
ppp_release_channels(ppp_chan, 1);
return session;
}
/*
* nss_connmgr_l2tpv2_get_all_proto_data()
* get all information on ppp, l2tpv2, udp and ip protocol
*/
static struct nss_connmgr_l2tpv2_session_data
*nss_connmgr_l2tpv2_get_all_proto_data(struct net_device *dev,
struct l2tp_session *session)
{
struct nss_connmgr_l2tpv2_session_data *l2tpv2_session_data = NULL;
struct l2tp_tunnel *tunnel = NULL;
struct inet_sock *inet;
struct nss_connmgr_l2tpv2_data *data;
struct udp_sock *udp;
l2tpv2_session_data = kmalloc(sizeof(struct nss_connmgr_l2tpv2_session_data),
GFP_KERNEL);
if (!l2tpv2_session_data) {
nss_connmgr_l2tpv2_info("failed to allocate l2tpv2_session_data\n");
session_put(session);
return NULL;
}
tunnel = session->tunnel;
if (unlikely(!tunnel)) {
nss_connmgr_l2tpv2_info("tunnel is null for session %px\n", session);
goto err_nss_connmgr_l2tpv2_get_data_1;
}
tunnel_hold(tunnel);
data = &l2tpv2_session_data->data;
if (tunnel->version != L2TP_V_2) {
nss_connmgr_l2tpv2_info("l2tp version is not 2\n");
goto err_nss_connmgr_l2tpv2_get_data_2;
}
if (!tunnel->sock) {
nss_connmgr_l2tpv2_info("tunnel sock is NULL\n");
goto err_nss_connmgr_l2tpv2_get_data_2;
}
sock_hold(tunnel->sock);
/*
* Get Tunnel Info
*/
data->l2tpv2.tunnel.tunnel_id = tunnel->tunnel_id;
data->l2tpv2.tunnel.peer_tunnel_id = tunnel->peer_tunnel_id;
nss_connmgr_l2tpv2_info("vers=%u tun id=%u peer_id=%u ncap=%u\n",
tunnel->version, tunnel->tunnel_id,
tunnel->peer_tunnel_id, tunnel->encap);
/*
* Get session info
*/
data->l2tpv2.session.session_id = session->session_id;
data->l2tpv2.session.peer_session_id = session->peer_session_id;
data->l2tpv2.session.hdr_len = session->hdr_len;
data->l2tpv2.session.reorder_timeout = session->reorder_timeout;
data->l2tpv2.session.recv_seq = session->recv_seq;
data->l2tpv2.session.send_seq = session->send_seq;
nss_connmgr_l2tpv2_info("sess %u, peer=%u nr=%u ns=%u hdr_len=%u timeout=%x"
" recv_seq=%x send_seq=%x\n",
session->session_id, session->peer_session_id, session->nr,
session->ns, session->hdr_len,
session->reorder_timeout, session->recv_seq,
session->send_seq);
/*
* tunnel->sock->sk_no_check/sk_no_check_tx is set to true
* if UDP checksum is not needed for the L2TP socket.
*/
data->l2tpv2.tunnel.udp_csum = !tunnel->sock->sk_no_check_tx;
inet = inet_sk(tunnel->sock);
/*
* UDP header exist ?
*/
if (tunnel->encap != L2TP_ENCAPTYPE_UDP) {
nss_connmgr_l2tpv2_info("encap type is not UDP\n");
goto err_nss_connmgr_l2tpv2_get_data_3;
}
if (tunnel->sock->sk_protocol != IPPROTO_UDP) {
nss_connmgr_l2tpv2_info("Wrong protocol %u\n",
tunnel->sock->sk_protocol);
goto err_nss_connmgr_l2tpv2_get_data_3;
}
if (tunnel->sock->sk_state != TCP_ESTABLISHED) {
nss_connmgr_l2tpv2_info("Udp socket is not in established" \
" state %u\n", tunnel->sock->sk_state);
goto err_nss_connmgr_l2tpv2_get_data_3;
}
udp = udp_sk(tunnel->sock);
if (!udp) {
nss_connmgr_l2tpv2_info("There is no udp socket !!! ?\n");
goto err_nss_connmgr_l2tpv2_get_data_3;
}
data->udp.dport = inet->inet_dport;
data->udp.sport = inet->inet_sport;
nss_connmgr_l2tpv2_info("udp sport=0x%x dport=0x%x\n", ntohs(inet->inet_sport), ntohs(inet->inet_dport));
/*
* ip data
*/
if (inet->sk.sk_family == AF_INET) {
data->ip.v4.saddr.s_addr = inet->inet_saddr;
data->ip.v4.daddr.s_addr = inet->inet_daddr;
} else {
nss_connmgr_l2tpv2_info("Only Ipv4 protocol supports l2tpv2 packet's outer IP\n");
goto err_nss_connmgr_l2tpv2_get_data_3;
}
nss_connmgr_l2tpv2_info("saddr 0x%x daddr = 0x%x\n", ntohl(inet->inet_saddr), ntohl(inet->inet_daddr));
/*
* fill in extra data. This can be used to get reference to
* netdevice
*/
dev_hold(dev);
l2tpv2_session_data->dev = dev;
strlcpy(session->ifname, dev->name, IFNAMSIZ);
/*
* There is no need for protecting simultaneous addition &
* deletion of l2tpv2_session_data as all netdev notifier chain
* call back is called with rtln mutex lock.
*/
hash_add_rcu(l2tpv2_session_data_hash_table,
&l2tpv2_session_data->hash_list,
dev->ifindex);
sock_put(tunnel->sock);
tunnel_put(tunnel);
session_put(session);
return l2tpv2_session_data;
err_nss_connmgr_l2tpv2_get_data_3:
sock_put(tunnel->sock);
err_nss_connmgr_l2tpv2_get_data_2:
tunnel_put(tunnel);
err_nss_connmgr_l2tpv2_get_data_1:
kfree(l2tpv2_session_data);
session_put(session);
return NULL;
}
/*
* nss_connmgr_l2tpv2_exception()
* Exception handler registered to NSS for handling l2tpv2 pkts
*
*/
static void nss_connmgr_l2tpv2_exception(struct net_device *dev,
struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
const struct iphdr *iph_outer, *iph_inner;
struct nss_connmgr_l2tpv2_session_data *ptr;
uint16_t *l2tp_hdr;
uint16_t l2tp_flags;
int l2tp_hdr_len = L2TP_HDR_MIN_LEN;
__be32 tunnel_local_ip;
struct rtable *rt;
struct net_device *in_dev;
/* discard L2 header */
skb_pull(skb, sizeof(struct ethhdr));
skb_reset_mac_header(skb);
iph_outer = (const struct iphdr *)skb->data;
rcu_read_lock();
hash_for_each_possible_rcu(l2tpv2_session_data_hash_table, ptr,
hash_list, dev->ifindex) {
if (ptr->dev == dev) {
tunnel_local_ip = ptr->data.ip.v4.saddr.s_addr;
rcu_read_unlock();
if (iph_outer->version == 4 && iph_outer->saddr == tunnel_local_ip) { /*pkt is encapsulated */
skb_pull(skb, sizeof(struct iphdr));
skb_pull(skb, sizeof(struct udphdr));
l2tp_hdr = (uint16_t *)skb->data;
l2tp_flags = ntohs(*l2tp_hdr);
if (unlikely(l2tp_flags & L2TP_HDRFLAG_L)) {
l2tp_hdr_len += 2;
}
if (unlikely(l2tp_flags & L2TP_HDRFLAG_S)) {
l2tp_hdr_len += 4;
}
if (unlikely(l2tp_flags & L2TP_HDRFLAG_O)) {
uint16_t offset_pad = ntohs(*(l2tp_hdr + l2tp_hdr_len/BYTES_PER_SHORT));
l2tp_hdr_len += 2 + offset_pad;
}
skb_pull(skb, l2tp_hdr_len); /* pull l2tp header */
skb_pull(skb, PPP_HDR_LEN); /* pull ppp header */
iph_inner = (const struct iphdr *)skb->data;
if (iph_inner->version == 4) {
skb->protocol = htons(ETH_P_IP);
} else if (iph_inner->version == 6) {
skb->protocol = htons(ETH_P_IPV6);
} else {
nss_connmgr_l2tpv2_warning("not able to handle this pkt, so freeing\n");
dev_kfree_skb_any(skb);
return;
}
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
skb->ip_summed = CHECKSUM_NONE;
skb->pkt_type = PACKET_HOST;
skb->dev = dev;
/*
* set skb_iif
*/
rt = ip_route_output(&init_net, iph_inner->saddr, 0, 0, 0);
if (unlikely(IS_ERR(rt))) {
nss_connmgr_l2tpv2_warning("Martian packets !!!");
} else {
in_dev = rt->dst.dev;
ip_rt_put(rt);
if (likely(in_dev)) {
skb->skb_iif = in_dev->ifindex;
} else {
nss_connmgr_l2tpv2_warning("could not find incoming interface\n");
}
}
dev_queue_xmit(skb);
return;
} else { /* pkt is decapsulated */
if (iph_outer->version == 4) {
skb->protocol = htons(ETH_P_IP);
} else if (iph_outer->version == 6) {
skb->protocol = htons(ETH_P_IPV6);
} else {
nss_connmgr_l2tpv2_info("pkt may be a control packet\n");
}
skb_reset_network_header(skb);
skb->pkt_type = PACKET_HOST;
skb->skb_iif = dev->ifindex;
skb->ip_summed = CHECKSUM_NONE;
skb->dev = dev;
netif_receive_skb(skb);
return;
}
}
}
rcu_read_unlock();
nss_connmgr_l2tpv2_warning("not able to handle this pkt, so freeing\n");
dev_kfree_skb_any(skb);
}
/*
* nss_connmgr_l2tpv2_event_receive()
* Event Callback to receive events from NSS
*/
static void nss_connmgr_l2tpv2_event_receive(void *if_ctx, struct nss_l2tpv2_msg *tnlmsg)
{
struct net_device *netdev = if_ctx;
switch (tnlmsg->cm.type) {
case NSS_L2TPV2_MSG_SYNC_STATS:
nss_l2tpv2_update_dev_stats(netdev, (struct nss_l2tpv2_sync_session_stats_msg *)&tnlmsg->msg.stats);
break;
default:
nss_connmgr_l2tpv2_info("Unknown Event from NSS\n");
break;
}
}
/*
* nss_connmgr_l2tpv2_bind_ipsec_by_ipaddr()
* Bind L2TP tunnel with IPsec(xfrm) based on IP Address
*/
static void nss_connmgr_l2tpv2_bind_ipsec_by_ipaddr(struct nss_ctx_instance *nss_ctx, struct nss_connmgr_l2tpv2_data *l2tpv2_data, uint32_t l2tp_ifnum)
{
struct nss_l2tpv2_msg l2tpv2msg;
nss_tx_status_t status;
struct nss_l2tpv2_bind_ipsec_if_msg *l2tpv2_bind_ipsec_msg;
int32_t ipsec_ifnum;
get_ipsec_ifnum_by_ipv4_addr_callback_t ipsec_cb;
/*
* Check if the L2TP interface is applied over an IPsec (XFRM) interface by querying the IPsec
* client by using the L2TP tunnel IPv4 source/destination addresses.
*/
rcu_read_lock();
ipsec_cb = rcu_dereference(ipsecmgr_cb.get_ifnum_by_ipv4_addr);
if (!ipsec_cb) {
rcu_read_unlock();
nss_connmgr_l2tpv2_info("%px: IPsec get_ifnum_by_ipv4_addr callback is not registered\n", nss_ctx);
return;
}
ipsec_ifnum = ipsec_cb(&l2tpv2_data->ip.v4.saddr.s_addr, &l2tpv2_data->ip.v4.daddr.s_addr);
rcu_read_unlock();
if (ipsec_ifnum < 0) {
nss_connmgr_l2tpv2_info("%px: Invalid IPsec interface no.(0x%x) based on local & remote IP-address\n", nss_ctx, ipsec_ifnum);
return;
}
/*
* For, l2tpoipsec, send the command to bind the l2tp session with the IPsec interface.
*/
memset(&l2tpv2msg, 0, sizeof(struct nss_l2tpv2_msg));
nss_l2tpv2_msg_init(&l2tpv2msg, l2tp_ifnum, NSS_L2TPV2_MSG_BIND_IPSEC_IF,
sizeof(struct nss_l2tpv2_bind_ipsec_if_msg), (void *)nss_connmgr_l2tpv2_msg_cb, NULL);
l2tpv2_bind_ipsec_msg = &l2tpv2msg.msg.bind_ipsec_if_msg;
l2tpv2_bind_ipsec_msg->ipsec_ifnum = ipsec_ifnum;
status = nss_l2tpv2_tx(nss_ctx, &l2tpv2msg);
if (status != NSS_TX_SUCCESS) {
/*
* TODO: Add retry logic. Currently it sends a warning message to user.
* In case of bind fails, then we don't bring down L2TP tunnel, instead we give a warning log
* to user, as this introduces a potential risk of not having a per packet check for source
* interface number in firmware.
*/
nss_connmgr_l2tpv2_warning("%px: L2TPv2 interface binding with IPSec interface(0x%x) failed.\n", nss_ctx, ipsec_ifnum);
return;
}
nss_connmgr_l2tpv2_info("%px: L2TPv2 interface is bound to IPsec interface with if_num(0x%x)\n", nss_ctx, ipsec_ifnum);
}
/*
* nss_connmgr_l2tpv2_dev_up()
* pppol2tpv2 interface's up event handler
*/
static int nss_connmgr_l2tpv2_dev_up(struct net_device *dev)
{
uint32_t if_number;
struct l2tp_session *session = NULL;
struct nss_connmgr_l2tpv2_session_data *l2tpv2_session_data = NULL;
struct nss_connmgr_l2tpv2_data *data;
nss_tx_status_t status;
struct nss_ctx_instance *nss_ctx;
uint32_t features = 0; /* features denote the skb types supported by this interface */
struct nss_l2tpv2_msg l2tpv2msg;
struct nss_l2tpv2_session_create_msg *l2tpv2cfg;
session = nss_connmgr_l2tpv2_get_session(dev);
if (!session) {
return NOTIFY_DONE;
}
l2tpv2_session_data = nss_connmgr_l2tpv2_get_all_proto_data(dev, session);
if (l2tpv2_session_data == NULL) {
return NOTIFY_DONE;
}
/*
* Create nss dynamic interface and register
*/
if_number = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_L2TPV2);
if (if_number == -1) {
nss_connmgr_l2tpv2_info("Request interface number failed\n");
return NOTIFY_DONE;
}
nss_connmgr_l2tpv2_info("nss_dynamic_interface_alloc_node() sucessful. if_number = %d\n", if_number);
if (!nss_is_dynamic_interface(if_number)) {
nss_connmgr_l2tpv2_warning("Invalid NSS dynamic I/F number %d\n", if_number);
return NOTIFY_BAD;
}
/*
* Register l2tpv2 tunnel with NSS
*/
nss_ctx = nss_register_l2tpv2_if(if_number,
nss_connmgr_l2tpv2_exception,
nss_connmgr_l2tpv2_event_receive,
dev,
features);
if (!nss_ctx) {
status = nss_dynamic_interface_dealloc_node(if_number, NSS_DYNAMIC_INTERFACE_TYPE_L2TPV2);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_l2tpv2_info("Unable to dealloc the node[%d] in the NSS fw!\n", if_number);
}
nss_connmgr_l2tpv2_info("nss_register_l2tpv2_if failed\n");
return NOTIFY_BAD;
}
nss_connmgr_l2tpv2_info("%px: nss_register_l2tpv2_if() successful\n", nss_ctx);
data = &l2tpv2_session_data->data;
memset(&l2tpv2msg, 0, sizeof(struct nss_l2tpv2_msg));
l2tpv2cfg = &l2tpv2msg.msg.session_create_msg;
l2tpv2cfg->local_tunnel_id = data->l2tpv2.tunnel.tunnel_id;
l2tpv2cfg->peer_tunnel_id = data->l2tpv2.tunnel.peer_tunnel_id;
l2tpv2cfg->local_session_id = data->l2tpv2.session.session_id;
l2tpv2cfg->peer_session_id = data->l2tpv2.session.peer_session_id;
l2tpv2cfg->sip = ntohl(data->ip.v4.saddr.s_addr);
l2tpv2cfg->dip = ntohl(data->ip.v4.daddr.s_addr);
l2tpv2cfg->reorder_timeout = jiffies_to_msecs(data->l2tpv2.session.reorder_timeout);
l2tpv2cfg->sport = ntohs(data->udp.sport);
l2tpv2cfg->dport = ntohs(data->udp.dport);
/*
* all other fields
*/
l2tpv2cfg->recv_seq = data->l2tpv2.session.recv_seq;
l2tpv2cfg->oip_ttl = ip_ttl_max;
l2tpv2cfg->udp_csum = data->l2tpv2.tunnel.udp_csum;
nss_connmgr_l2tpv2_info("%px: l2tpv2 info\n", nss_ctx);
nss_connmgr_l2tpv2_info("%px: tunnel_id %d peer_tunnel_id %d session_id %d peer_session_id %d\n", nss_ctx,
l2tpv2cfg->local_tunnel_id,
l2tpv2cfg->peer_tunnel_id,
l2tpv2cfg->local_session_id,
l2tpv2cfg->peer_session_id);
nss_connmgr_l2tpv2_info("%px: saddr 0x%x daddr 0x%x sport 0x%x dport 0x%x\n", nss_ctx,
l2tpv2cfg->sip, l2tpv2cfg->dip, l2tpv2cfg->sport, l2tpv2cfg->dport);
nss_connmgr_l2tpv2_info("Sending l2tpv2 i/f up command to NSS %px\n", nss_ctx);
nss_l2tpv2_msg_init(&l2tpv2msg, if_number, NSS_L2TPV2_MSG_SESSION_CREATE, sizeof(struct nss_l2tpv2_session_create_msg), NULL, NULL);
status = nss_l2tpv2_tx(nss_ctx, &l2tpv2msg);
if (status != NSS_TX_SUCCESS) {
nss_unregister_l2tpv2_if(if_number);
status = nss_dynamic_interface_dealloc_node(if_number, NSS_DYNAMIC_INTERFACE_TYPE_L2TPV2);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_l2tpv2_warning("%px: Unable to dealloc the node[%d] in the NSS fw!\n", nss_ctx, if_number);
}
nss_connmgr_l2tpv2_warning("%px: nss l2tp session creation command error %d\n", nss_ctx, status);
return NOTIFY_BAD;
}
nss_connmgr_l2tpv2_info("%px: nss_l2tpv2_tx() CREATE successful\n", nss_ctx);
/*
* Check if we need to bind the L2TP to an IPsec interface. This is required as per RFC3193
*/
nss_connmgr_l2tpv2_bind_ipsec_by_ipaddr(nss_ctx, data, if_number);
return NOTIFY_DONE;
}
/*
* nss_connmgr_l2tpv2_dev_down()
* pppol2tpv2 interface's down event handler
*/
static int nss_connmgr_l2tpv2_dev_down(struct net_device *dev)
{
struct nss_connmgr_l2tpv2_session_data *ptr;
struct hlist_node *tmp;
struct nss_l2tpv2_msg l2tpv2msg;
struct nss_l2tpv2_session_destroy_msg *l2tpv2cfg;
int if_number;
nss_tx_status_t status;
/*
* check whether the interface is of type PPP
*/
if (dev->type != ARPHRD_PPP || !(dev->flags & IFF_POINTOPOINT)) {
nss_connmgr_l2tpv2_info("netdevice is not a l2tpv2/l2tpv3 tunnel type\n");
return NOTIFY_DONE;
}
/*
* Check if l2tpv2 is registered ?
*/
if_number = nss_cmn_get_interface_number_by_dev(dev);
if (if_number < 0) {
nss_connmgr_l2tpv2_info("Net device:%px is not registered with nss\n", dev);
return NOTIFY_DONE;
}
hash_for_each_possible_safe(l2tpv2_session_data_hash_table, ptr,
tmp, hash_list, dev->ifindex) {
if (ptr->dev == dev) {
dev_put(dev);
hash_del_rcu(&ptr->hash_list);
synchronize_rcu();
memset(&l2tpv2msg, 0, sizeof(struct nss_l2tpv2_msg));
l2tpv2cfg = &l2tpv2msg.msg.session_destroy_msg;
l2tpv2cfg->local_tunnel_id = ptr->data.l2tpv2.tunnel.tunnel_id;
l2tpv2cfg->local_session_id = ptr->data.l2tpv2.session.session_id;
kfree(ptr);
nss_l2tpv2_msg_init(&l2tpv2msg, if_number, NSS_L2TPV2_MSG_SESSION_DESTROY, sizeof(struct nss_l2tpv2_session_destroy_msg), NULL, NULL);
status = nss_l2tpv2_tx(nss_l2tpv2_get_context(), &l2tpv2msg);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_l2tpv2_info("l2tpv2 session destroy command failed, if_number = %d\n", if_number);
return NOTIFY_BAD;
}
nss_unregister_l2tpv2_if(if_number);
status = nss_dynamic_interface_dealloc_node(if_number, NSS_DYNAMIC_INTERFACE_TYPE_L2TPV2);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_l2tpv2_info("l2tpv2 dealloc node failure for if_number=%d\n", if_number);
return NOTIFY_BAD;
}
nss_connmgr_l2tpv2_info("deleting l2tpv2session, if_number %d, tunnel_id %d, session_id %d\n",
if_number, l2tpv2cfg->local_tunnel_id, l2tpv2cfg->local_session_id);
break;
}
}
return NOTIFY_DONE;
}
/*
* nss_connmgr_l2tpv2_dev_event()
* Net device notifier for nss l2tpv2 module
*/
static int nss_connmgr_l2tpv2_dev_event(struct notifier_block *nb,
unsigned long event, void *dev)
{
struct net_device *netdev;
netdev = netdev_notifier_info_to_dev(dev);
switch (event) {
case NETDEV_UP:
return nss_connmgr_l2tpv2_dev_up(netdev);
case NETDEV_DOWN:
return nss_connmgr_l2tpv2_dev_down(netdev);
default:
break;
}
return NOTIFY_DONE;
}
/*
* nss_connmgr_l2tpv2_get_data()
* return 0 on success.
*/
int nss_connmgr_l2tpv2_get_data(struct net_device *dev, struct nss_connmgr_l2tpv2_data *data)
{
struct nss_connmgr_l2tpv2_session_data *ptr;
if (!data) {
nss_connmgr_l2tpv2_info("nss_connmgr_l2tpv2_data ptr is null\n");
return -EINVAL;
}
rcu_read_lock();
hash_for_each_possible_rcu(l2tpv2_session_data_hash_table, ptr,
hash_list, dev->ifindex) {
if (ptr->dev == dev) {
memcpy(data, &ptr->data, sizeof(struct nss_connmgr_l2tpv2_data));
rcu_read_unlock();
return 0;
}
}
rcu_read_unlock();
return -ENODEV;
}
EXPORT_SYMBOL(nss_connmgr_l2tpv2_get_data);
/*
* nss_connmgr_l2tpv2_does_connmgr_track()
* return 0 on success.
*/
int nss_connmgr_l2tpv2_does_connmgr_track(const struct net_device *dev)
{
struct nss_connmgr_l2tpv2_session_data *ptr;
rcu_read_lock();
hash_for_each_possible_rcu(l2tpv2_session_data_hash_table, ptr,
hash_list, dev->ifindex) {
if (ptr->dev == dev) {
rcu_read_unlock();
return 0;
}
}
rcu_read_unlock();
return -ENODEV;
}
EXPORT_SYMBOL(nss_connmgr_l2tpv2_does_connmgr_track);
/*
* l2tpmgr_register_ipsecmgr_callback_by_ipaddr()
* Register IPSecmgr callback.
*/
void l2tpmgr_register_ipsecmgr_callback_by_ipaddr(struct l2tpmgr_ipsecmgr_cb *cb)
{
get_ipsec_ifnum_by_ipv4_addr_callback_t ipsec_get_ifnum_by_ipv4_addr;
rcu_read_lock();
ipsec_get_ifnum_by_ipv4_addr = rcu_dereference(ipsecmgr_cb.get_ifnum_by_ipv4_addr);
if (ipsec_get_ifnum_by_ipv4_addr) {
rcu_read_unlock();
nss_connmgr_l2tpv2_info("%px: IPSecmgr Callback get_ifnum_by_ipv4_addr is already registered\n", cb);
return;
}
rcu_read_unlock();
if (cb->get_ifnum_by_ipv4_addr == NULL) {
nss_connmgr_l2tpv2_warning("%px: IPSecmgr Callback get_ifnum_by_ipv4_addr is NULL\n", cb);
return;
}
rcu_assign_pointer(ipsecmgr_cb.get_ifnum_by_ipv4_addr, cb->get_ifnum_by_ipv4_addr);
synchronize_rcu();
}
EXPORT_SYMBOL(l2tpmgr_register_ipsecmgr_callback_by_ipaddr);
/*
* l2tpmgr_unregister_ipsecmgr_callback_by_ipaddr
* Unregister callback.
*/
void l2tpmgr_unregister_ipsecmgr_callback_by_ipaddr(void)
{
rcu_assign_pointer(ipsecmgr_cb.get_ifnum_by_ipv4_addr, NULL);
synchronize_rcu();
}
EXPORT_SYMBOL(l2tpmgr_unregister_ipsecmgr_callback_by_ipaddr);
#if defined(NSS_L2TP_IPSEC_BIND_BY_NETDEV)
/*
* l2tpmgr_register_ipsecmgr_callback_by_netdev()
* Register IPSecmgr callback.
*/
void l2tpmgr_register_ipsecmgr_callback_by_netdev(struct l2tpmgr_ipsecmgr_cb *cb)
{
get_ipsec_ifnum_by_dev_callback_t ipsec_get_ifnum_by_dev;
rcu_read_lock();
ipsec_get_ifnum_by_dev = rcu_dereference(ipsecmgr_cb.get_ifnum_by_dev);
if (ipsec_get_ifnum_by_dev) {
rcu_read_unlock();
nss_connmgr_l2tpv2_info("%px: IPSecmgr Callback get_ifnum_by_dev is already registered\n", cb);
return;
}
rcu_read_unlock();
if (cb->get_ifnum_by_dev == NULL) {
nss_connmgr_l2tpv2_warning("%px: IPSecmgr Callback get_ifnum_by_dev is NULL\n", cb);
return;
}
rcu_assign_pointer(ipsecmgr_cb.get_ifnum_by_dev, cb->get_ifnum_by_dev);
synchronize_rcu();
}
EXPORT_SYMBOL(l2tpmgr_register_ipsecmgr_callback_by_netdev);
/*
* l2tpmgr_unregister_ipsecmgr_callback_by_netdev
* Unregister callback.
*/
void l2tpmgr_unregister_ipsecmgr_callback_by_netdev(void)
{
rcu_assign_pointer(ipsecmgr_cb.get_ifnum_by_dev, NULL);
synchronize_rcu();
}
EXPORT_SYMBOL(l2tpmgr_unregister_ipsecmgr_callback_by_netdev);
/*
* nss_connmgr_l2tpv2_proc_handler()
* Read and write handler for sysctl.
*/
static int nss_connmgr_l2tpv2_proc_handler(struct ctl_table *ctl,
int write, void __user *buffer,
size_t *lenp, loff_t *ppos)
{
char *l2tp_device_name, *ipsec_device_name;
char *input_str = l2tpoipsec_config;
int32_t l2tp_ifnum, ipsec_ifnum;
struct net_device *l2tpdev, *ipsecdev;
nss_tx_status_t status;
struct nss_l2tpv2_msg l2tpv2msg;
get_ipsec_ifnum_by_dev_callback_t ipsec_cb;
struct nss_l2tpv2_bind_ipsec_if_msg *l2tpv2_bind_ipsec_msg;
struct nss_ctx_instance *nss_ctx = nss_l2tpv2_get_context();
int ret = proc_dostring(ctl, write, buffer, lenp, ppos);
struct nss_connmgr_l2tpv2_session_data *ptr;
if (!write) {
nss_connmgr_l2tpv2_info("command to write is echo <l2tp-device-name> <ipsec-device-name> > <filename>\n");
return ret;
}
l2tp_device_name = strsep(&input_str, " ");
ipsec_device_name = strsep(&input_str, "\0");
if (!l2tp_device_name) {
nss_connmgr_l2tpv2_info("L2TP device name not found\n");
nss_connmgr_l2tpv2_info("command is echo <l2tp-device-name> <ipsec-device-name> > <filename>\n");
return -EINVAL;
}
if (!ipsec_device_name) {
nss_connmgr_l2tpv2_info("IPsec device name not found\n");
nss_connmgr_l2tpv2_info("command is echo <l2tp-device-name> <ipsec-device-name> > <filename>\n");
return -EINVAL;
}
l2tpdev = dev_get_by_name(&init_net, l2tp_device_name);
if (!l2tpdev) {
nss_connmgr_l2tpv2_info("Cannot find the netdevice associated with %s\n", l2tp_device_name);
return -EINVAL;
}
rcu_read_lock();
hash_for_each_possible_rcu(l2tpv2_session_data_hash_table, ptr,
hash_list, l2tpdev->ifindex) {
if (ptr->dev != l2tpdev) {
continue;
}
if (ptr->data.l2tpv2.tunnel.udp_csum) {
nss_connmgr_l2tpv2_info("Enabling UDP checksum in L2TP packet is not supported for l2tpoipsec flow\n");
}
}
rcu_read_unlock();
ipsecdev = dev_get_by_name(&init_net, ipsec_device_name);
if (!ipsecdev) {
nss_connmgr_l2tpv2_info("Cannot find the netdevice associated with %s\n", ipsec_device_name);
dev_put(l2tpdev);
return -EINVAL;
}
/*
* Get NSS ifnum for L2TP interface.
*/
l2tp_ifnum = nss_cmn_get_interface_number_by_dev(l2tpdev);
if (l2tp_ifnum == -1) {
nss_connmgr_l2tpv2_info("Cannot find the NSS interface associated with %s\n", l2tp_device_name);
ret = -ENODEV;
goto exit;
}
rcu_read_lock();
ipsec_cb = rcu_dereference(ipsecmgr_cb.get_ifnum_by_dev);
if (!ipsec_cb) {
rcu_read_unlock();
nss_connmgr_l2tpv2_info("Callback to get IPsec tun device not registered");
ret = -EPERM;
goto exit;
}
/*
* Get NSS ifnum for IPsec interface.
*/
ipsec_ifnum = ipsec_cb(ipsecdev);
rcu_read_unlock();
if (ipsec_ifnum == -1) {
nss_connmgr_l2tpv2_info("Cannot find the NSS interface associated with %s\n", ipsec_device_name);
ret = -ENODEV;
goto exit;
}
/*
* Send the command to bind the l2tp session with the IPsec interface.
*/
memset(&l2tpv2msg, 0, sizeof(struct nss_l2tpv2_msg));
nss_l2tpv2_msg_init(&l2tpv2msg, l2tp_ifnum, NSS_L2TPV2_MSG_BIND_IPSEC_IF, sizeof(struct nss_l2tpv2_bind_ipsec_if_msg), (void *)nss_connmgr_l2tpv2_msg_cb, NULL);
l2tpv2_bind_ipsec_msg = &l2tpv2msg.msg.bind_ipsec_if_msg;
l2tpv2_bind_ipsec_msg->ipsec_ifnum = ipsec_ifnum;
status = nss_l2tpv2_tx(nss_ctx, &l2tpv2msg);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_l2tpv2_info("%px IPSec interface bind failed\n", nss_ctx);
ret = -EAGAIN;
}
exit:
dev_put(l2tpdev);
dev_put(ipsecdev);
return ret;
}
/*
* nss_connmgr_l2tpv2_table
*/
static struct ctl_table nss_connmgr_l2tpv2_table[] = {
{
.procname = "l2tpoipsec",
.data = &l2tpoipsec_config,
.maxlen = L2TP_SYSCTL_STR_LEN_MAX,
.mode = 0644,
.proc_handler = &nss_connmgr_l2tpv2_proc_handler,
},
{ }
};
/*
* nss_connmgr_l2tpv2_dir
*/
static struct ctl_table nss_connmgr_l2tpv2_dir[] = {
{
.procname = "l2tpv2",
.mode = 0555,
.child = nss_connmgr_l2tpv2_table,
},
{ }
};
/*
* nss_connmgr_l2tpv2_sysroot sysctl root dir
*/
static struct ctl_table nss_connmgr_l2tpv2_sysroot[] = {
{
.procname = "nss",
.mode = 0555,
.child = nss_connmgr_l2tpv2_dir,
},
{ }
};
#endif
/*
* Linux Net device Notifier
*/
struct notifier_block nss_connmgr_l2tpv2_notifier = {
.notifier_call = nss_connmgr_l2tpv2_dev_event,
};
/*
* nss_connmgr_l2tpv2_init_module()
* Tunnel l2tpv2 module init function
*/
int __init nss_connmgr_l2tpv2_init_module(void)
{
#ifdef CONFIG_OF
/*
* If the node is not compatible, don't do anything.
*/
if (!of_find_node_by_name(NULL, "nss-common")) {
return 0;
}
#endif
#if defined(NSS_L2TP_IPSEC_BIND_BY_NETDEV)
ctl_tbl_hdr = register_sysctl_table(nss_connmgr_l2tpv2_sysroot);
if (!ctl_tbl_hdr) {
nss_connmgr_l2tpv2_info("Unable to register sysctl table for L2TP conn mgr\n");
return -EFAULT;
}
#endif
/*
* Initialize ipsecmgr callback.
*/
rcu_assign_pointer(ipsecmgr_cb.get_ifnum_by_dev, NULL);
rcu_assign_pointer(ipsecmgr_cb.get_ifnum_by_ipv4_addr, NULL);
synchronize_rcu();
register_netdevice_notifier(&nss_connmgr_l2tpv2_notifier);
return 0;
}
/*
* nss_connmgr_l2tpv2_exit_module
* Tunnel l2tpv2 module exit function
*/
void __exit nss_connmgr_l2tpv2_exit_module(void)
{
#ifdef CONFIG_OF
/*
* If the node is not compatible, don't do anything.
*/
if (!of_find_node_by_name(NULL, "nss-common")) {
return;
}
#endif
unregister_netdevice_notifier(&nss_connmgr_l2tpv2_notifier);
}
module_init(nss_connmgr_l2tpv2_init_module);
module_exit(nss_connmgr_l2tpv2_exit_module);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("NSS l2tpv2 over ppp offload manager");