blob: 80805c4ca9bfdff4c5ced80deefc557621f588c9 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2015-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.
**************************************************************************
*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/rwlock_types.h>
#include <linux/hashtable.h>
#include <linux/inetdevice.h>
#include <linux/ip.h>
#include <net/ipv6.h>
#include <linux/if_arp.h>
#include <net/route.h>
#include <linux/if_pppox.h>
#include <net/ip.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#endif
#include <nss_api_if.h>
#include <nss_dynamic_interface.h>
#include "nss_connmgr_pptp.h"
#define PPP_HDR_LEN 4
/*
* NSS pptp debug macros
*/
#if (NSS_PPTP_DEBUG_LEVEL < 1)
#define nss_connmgr_pptp_assert(fmt, args...)
#else
#define nss_connmgr_pptp_assert(c) if (!(c)) { BUG_ON(!(c)); }
#endif
#if defined(CONFIG_DYNAMIC_DEBUG)
/*
* Compile messages for dynamic enable/disable
*/
#define nss_connmgr_pptp_warning(s, ...) pr_debug("%s[%d]:" s, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define nss_connmgr_pptp_info(s, ...) pr_debug("%s[%d]:" s, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define nss_connmgr_pptp_trace(s, ...) pr_debug("%s[%d]:" s, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
/*
* Statically compile messages at different levels
*/
#if (NSS_PPTP_DEBUG_LEVEL < 2)
#define nss_connmgr_pptp_warning(s, ...)
#else
#define nss_connmgr_pptp_warning(s, ...) pr_warn("%s[%d]:" s, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#endif
#if (NSS_PPTP_DEBUG_LEVEL < 3)
#define nss_connmgr_pptp_info(s, ...)
#else
#define nss_connmgr_pptp_info(s, ...) pr_notice("%s[%d]:" s, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#endif
#if (NSS_PPTP_DEBUG_LEVEL < 4)
#define nss_connmgr_pptp_trace(s, ...)
#else
#define nss_connmgr_pptp_trace(s, ...) pr_info("%s[%d]:" s, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#endif
#endif
#define NUM_PPTP_CHANNELS_IN_PPP_NETDEVICE 1
#define HASH_BUCKET_SIZE 2 /* ( 2^ HASH_BUCKET_SIZE ) == 4 */
static DEFINE_HASHTABLE(pptp_session_table, HASH_BUCKET_SIZE);
/*
* nss_connmgr_pptp_client_xmit()
* PPTP GRE seq/ack offload callback handler. Sends SKB to NSS firmware.
* Note: RCU lock is already held by caller.
*/
static int nss_connmgr_pptp_client_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct nss_connmgr_pptp_session_entry *session_info;
struct nss_ctx_instance *nss_pptp_ctx;
nss_tx_status_t status;
int host_inner_if;;
/*
* Check if pptp host inner I/F is registered ?
*/
host_inner_if = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_PPTP_HOST_INNER);
if (host_inner_if < 0) {
nss_connmgr_pptp_warning("%px: Net device is not registered\n", dev);
return -1;
}
nss_pptp_ctx = nss_pptp_get_context();
if (!nss_pptp_ctx) {
nss_connmgr_pptp_warning("%px: NSS PPTP context not found for if_number %d\n", dev, host_inner_if);
return -1;
}
hash_for_each_possible_rcu(pptp_session_table, session_info,
hash_list, dev->ifindex) {
if (session_info->dev != dev) {
continue;
}
status = nss_pptp_tx_buf(nss_pptp_ctx, host_inner_if, skb);
if (status == NSS_TX_SUCCESS) {
/*
* Found a match for a session and successfully posted
* packet to firmware. Retrun success.
*/
nss_connmgr_pptp_info("%px: NSS FW tx success if_number %d\n", dev, host_inner_if);
return 0;
}
nss_connmgr_pptp_info("%px: NSS FW tx failed if_number %d\n", dev, host_inner_if);
return -1;
}
/* Return error, Could not find a match for session */
return -1;
}
/*
* nss_connmgr_pptp_get_session()
* Retrieve pptp session associated with this netdevice if any
*/
static int nss_connmgr_pptp_get_session(struct net_device *dev, struct pptp_opt *opt)
{
struct ppp_channel *channel[1] = {NULL};
int px_proto;
int ppp_ch_count;
/*
* check whether the interface is of type PPP
*/
if (dev->type != ARPHRD_PPP || !(dev->priv_flags_ext & IFF_EXT_PPP_PPTP)) {
nss_connmgr_pptp_info("%px: netdevice is not a PPP tunnel type\n", dev);
return -1;
}
if (ppp_is_multilink(dev)) {
nss_connmgr_pptp_warning("%px: channel is multilink PPP\n", dev);
return -1;
}
if (ppp_is_cp_enabled(dev)) {
nss_connmgr_pptp_warning("%px: rx or tx compression is enabled for PPP\n", dev);
return -1;
}
ppp_ch_count = ppp_hold_channels(dev, channel, 1);
nss_connmgr_pptp_info("%px: PPP hold channel ret %d\n", dev, ppp_ch_count);
if (ppp_ch_count != 1) {
nss_connmgr_pptp_warning("%px: hold channel for netdevice failed\n", dev);
return -1;
}
if (!channel[0]) {
nss_connmgr_pptp_warning("%px: channel don't have a ppp_channel\n", dev);
return -1;
}
px_proto = ppp_channel_get_protocol(channel[0]);
if (px_proto != PX_PROTO_PPTP) {
nss_connmgr_pptp_warning("%px: session socket is not of type PX_PROTO_PPTP\n", dev);
ppp_release_channels(channel, 1);
return -1;
}
pptp_channel_addressing_get(opt, channel[0]);
ppp_release_channels(channel, 1);
return 0;
}
/*
* nss_connmgr_add_pptp_session()
* Add PPTP session entry into Hash table
*/
static struct nss_connmgr_pptp_session_entry *nss_connmgr_add_pptp_session(struct net_device *dev, struct pptp_opt *session)
{
struct nss_connmgr_pptp_session_entry *pptp_session_data = NULL;
struct nss_connmgr_pptp_session_info *data;
struct net_device *physical_dev;
pptp_session_data = kmalloc(sizeof(struct nss_connmgr_pptp_session_entry),
GFP_KERNEL);
if (!pptp_session_data) {
nss_connmgr_pptp_warning("%px: failed to allocate pptp_session_data\n", dev);
return NULL;
}
data = &pptp_session_data->data;
/*
* Get session info
*/
data->src_call = session->src_addr.call_id;
data->dst_call = session->dst_addr.call_id;
data->src_ip = session->src_addr.sin_addr.s_addr;
data->dst_ip = session->dst_addr.sin_addr.s_addr;
nss_connmgr_pptp_info("%px: src_call_id=%u peer_call_id=%u\n", dev, data->src_call, data->dst_call);
/*
* This netdev hold will be released when netdev
* down event arrives and session goes down.
*/
dev_hold(dev);
pptp_session_data->dev = dev;
/*
* Note: ip_dev_find does a hold on the physical device,
* which is released when PPTP session goes down
*/
physical_dev = ip_dev_find(&init_net, data->src_ip);
if (!physical_dev) {
nss_connmgr_pptp_warning("%px: couldn't find a phycal dev %s\n", dev, dev->name);
dev_put(dev);
kfree(pptp_session_data);
return NULL;
}
pptp_session_data->phy_dev = physical_dev;
/*
* There is no need for protecting simultaneous addition &
* deletion of pptp_session_data as all netdev notifier chain
* call back is called with rtln mutex lock.
*/
hash_add_rcu(pptp_session_table,
&pptp_session_data->hash_list,
dev->ifindex);
return pptp_session_data;
}
/*
* nss_connmgr_pptp_event_receive()
* Event Callback to receive events from NSS
*/
static void nss_connmgr_pptp_event_receive(void *if_ctx, struct nss_pptp_msg *tnlmsg)
{
struct nss_connmgr_pptp_session_entry *session_info = (struct nss_connmgr_pptp_session_entry *)if_ctx;
struct net_device *netdev = session_info->dev;
struct nss_pptp_sync_session_stats_msg *sync_stats;
uint32_t if_type;
switch (tnlmsg->cm.type) {
case NSS_PPTP_MSG_SYNC_STATS:
if (!netdev) {
return;
}
nss_connmgr_pptp_info("%px: Update PPP stats for PPTP netdev %px\n", session_info, netdev);
sync_stats = (struct nss_pptp_sync_session_stats_msg *)&tnlmsg->msg.stats;
dev_hold(netdev);
if_type = nss_dynamic_interface_get_type(nss_pptp_get_context(), tnlmsg->cm.interface);
if (if_type == NSS_DYNAMIC_INTERFACE_TYPE_PPTP_OUTER) {
ppp_update_stats(netdev,
(unsigned long)sync_stats->node_stats.rx_packets,
(unsigned long)sync_stats->node_stats.rx_bytes,
0, 0, 0, 0, 0, 0);
} else {
ppp_update_stats(netdev, 0, 0,
(unsigned long)sync_stats->node_stats.tx_packets,
(unsigned long)sync_stats->node_stats.tx_bytes,
0, 0, 0, 0);
}
dev_put(netdev);
break;
default:
nss_connmgr_pptp_warning("%px: Unknown Event from NSS\n", session_info);
break;
}
}
/*
* nss_connmgr_pptp_decap_exception()
* Exception handler registered to NSS for handling pptp pkts
*/
static void nss_connmgr_pptp_decap_exception(struct net_device *dev,
struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct iphdr *iph_outer;
struct nss_connmgr_pptp_session_entry *session_info;
struct flowi4 fl4;
struct nss_pptp_gre_hdr *gre_hdr;
__be32 tunnel_local_ip;
__be32 tunnel_peer_ip;
struct rtable *rt;
/* discard L2 header */
skb_pull(skb, sizeof(struct ethhdr));
skb_reset_mac_header(skb);
iph_outer = (struct iphdr *)skb->data;
rcu_read_lock();
hash_for_each_possible_rcu(pptp_session_table, session_info,
hash_list, dev->ifindex) {
if (session_info->dev != dev) {
continue;
}
tunnel_local_ip = session_info->data.src_ip;
tunnel_peer_ip = session_info->data.dst_ip;
rcu_read_unlock();
if ((iph_outer->version == 4) && (iph_outer->protocol == IPPROTO_GRE) &&
(iph_outer->saddr == tunnel_local_ip) && (iph_outer->daddr == tunnel_peer_ip)) { /*pkt is encapsulated */
/*
* Pull the outer IP header and confirm the packet is a PPTP GRE Packet
*/
skb_pull(skb, sizeof(struct iphdr));
gre_hdr = (struct nss_pptp_gre_hdr *)skb->data;
if ((ntohs(gre_hdr->protocol) != NSS_PPTP_GRE_PROTO) &&
(gre_hdr->flags_ver == NSS_PPTP_GRE_VER)) {
nss_connmgr_pptp_warning("%px, Not PPTP_GRE_PROTO, so freeing\n", dev);
dev_kfree_skb_any(skb);
return;
}
skb_push(skb, sizeof(struct iphdr));
/*
* This is a PPTP encapsulated packet that has been exceptioned to host from NSS.
* We can send it directly to the physical device
*/
rt = ip_route_output_ports(&init_net, &fl4, NULL, tunnel_peer_ip,
tunnel_local_ip, 0, 0, IPPROTO_GRE, RT_TOS(0), 0);
if (unlikely(IS_ERR(rt))) {
nss_connmgr_pptp_warning("%px: Martian packets, drop\n", dev);
nss_connmgr_pptp_warning("%px: No route or out dev, drop packet...\n", dev);
dev_kfree_skb_any(skb);
return;
}
if (likely(rt->dst.dev)) {
nss_connmgr_pptp_info("%px: dst route dev is %s\n", session_info, rt->dst.dev->name);
} else {
nss_connmgr_pptp_warning("%px: No out dev, drop packet...\n", dev);
dev_kfree_skb_any(skb);
}
/*
* Sets the 'dst' entry for SKB, reset the IP and Transport
* Header and sends the packet out directly to the physical
* device associated with the PPTP tunnel interface.
*/
skb->dev = dev;
skb_dst_drop(skb);
skb_dst_set(skb, &rt->dst);
skb->ip_summed = CHECKSUM_COMPLETE;
skb_reset_network_header(skb);
skb_set_transport_header(skb, iph_outer->ihl*4);
skb->skb_iif = dev->ifindex;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0))
ip_local_out(skb);
#else
ip_local_out(&init_net, skb->sk, skb);
#endif
return;
}
}
rcu_read_unlock();
nss_connmgr_pptp_warning("%px: unable to find session for PPTP exception packet from %s, so freeing\n", dev, dev->name);
dev_kfree_skb_any(skb);
}
/*
* nss_connmgr_pptp_encap_exception()
* Exception handler registered to NSS for handling pptp pkts
*/
static void nss_connmgr_pptp_encap_exception(struct net_device *dev,
struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct iphdr *iph_outer;
struct nss_connmgr_pptp_session_entry *session_info;
__be32 tunnel_local_ip;
__be32 tunnel_peer_ip;
/* discard L2 header */
skb_pull(skb, sizeof(struct ethhdr));
skb_reset_mac_header(skb);
iph_outer = (struct iphdr *)skb->data;
rcu_read_lock();
hash_for_each_possible_rcu(pptp_session_table, session_info,
hash_list, dev->ifindex) {
if (session_info->dev != dev) {
continue;
}
tunnel_local_ip = session_info->data.src_ip;
tunnel_peer_ip = session_info->data.dst_ip;
rcu_read_unlock();
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_pptp_info("%px: pkt may be a control packet\n", dev);
}
skb_reset_network_header(skb);
skb->pkt_type = PACKET_HOST;
skb->skb_iif = dev->ifindex;
skb->ip_summed = CHECKSUM_NONE;
skb->dev = dev;
nss_connmgr_pptp_info("%px: send decapsulated packet through network stack", dev);
netif_receive_skb(skb);
return;
}
rcu_read_unlock();
nss_connmgr_pptp_warning("%px: unable to find session for PPTP exception packet from %s, so freeing\n", dev, dev->name);
dev_kfree_skb_any(skb);
}
/*
* nss_connmgr_pptp_dev_up()
* pppopptp interface's up event handler
*/
static int nss_connmgr_pptp_dev_up(struct net_device *dev)
{
struct pptp_opt opt;
struct nss_connmgr_pptp_session_entry *session_info = NULL;
struct nss_connmgr_pptp_session_info *data;
nss_tx_status_t status;
struct nss_ctx_instance *nss_ctx;
uint32_t features = 0;
int32_t inner_if, outer_if, host_inner_if;
struct nss_pptp_msg pptpmsg;
struct nss_pptp_session_configure_msg *pptpcfg;
int ret;
ret = nss_connmgr_pptp_get_session(dev, &opt);
if (ret < 0) {
return NOTIFY_DONE;
}
/*
* Create nss dynamic interface and register
*/
inner_if = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_PPTP_INNER);
if (inner_if < 0) {
nss_connmgr_pptp_warning("%px: Request inner interface number failed\n", dev);
return NOTIFY_DONE;
}
if (!nss_is_dynamic_interface(inner_if)) {
nss_connmgr_pptp_warning("%px: Invalid NSS dynamic I/F number %d\n", dev, inner_if);
goto inner_fail;
}
outer_if = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_PPTP_OUTER);
if (outer_if < 0) {
nss_connmgr_pptp_warning("%px: Request outer interface number failed\n", dev);
goto inner_fail;
}
if (!nss_is_dynamic_interface(outer_if)) {
nss_connmgr_pptp_warning("%px: Invalid NSS dynamic I/F number %d\n", dev, outer_if);
goto outer_fail;
}
host_inner_if = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_PPTP_HOST_INNER);
if (host_inner_if < 0) {
nss_connmgr_pptp_warning("%px: Request host inner interface number failed\n", dev);
goto outer_fail;
}
if (!nss_is_dynamic_interface(host_inner_if)) {
nss_connmgr_pptp_warning("%px: Invalid NSS dynamic I/F number %d\n", dev, host_inner_if);
goto host_inner_fail;
}
session_info = nss_connmgr_add_pptp_session(dev, &opt);
if (!session_info) {
nss_connmgr_pptp_warning("%px: PPTP session add failed\n", dev);
goto host_inner_fail;
}
/*
* Register pptp tunnel inner interface with NSS
*/
nss_ctx = nss_register_pptp_if(inner_if,
NSS_DYNAMIC_INTERFACE_TYPE_PPTP_INNER,
nss_connmgr_pptp_encap_exception,
nss_connmgr_pptp_event_receive,
dev,
features,
session_info);
if (!nss_ctx) {
nss_connmgr_pptp_warning("%px: nss_register_pptp_if failed for inner\n", dev);
goto register_inner_if_fail;
}
nss_connmgr_pptp_info("%px: inner interface registration successful\n", nss_ctx);
/*
* Register pptp tunnel outer interface with NSS
*/
nss_ctx = nss_register_pptp_if(outer_if,
NSS_DYNAMIC_INTERFACE_TYPE_PPTP_OUTER,
nss_connmgr_pptp_decap_exception,
nss_connmgr_pptp_event_receive,
dev,
features,
session_info);
if (!nss_ctx) {
nss_connmgr_pptp_warning("%px: nss_register_pptp_if failed for outer\n", dev);
goto register_outer_if_fail;
}
nss_connmgr_pptp_info("%px: outer interface registration successful\n", nss_ctx);
/*
* Register pptp tunnel inner interface with NSS
*/
nss_ctx = nss_register_pptp_if(host_inner_if,
NSS_DYNAMIC_INTERFACE_TYPE_PPTP_HOST_INNER,
nss_connmgr_pptp_encap_exception,
nss_connmgr_pptp_event_receive,
dev,
features,
session_info);
if (!nss_ctx) {
nss_connmgr_pptp_warning("%px: nss_register_pptp_if failed for host inner\n", dev);
goto register_host_inner_if_fail;
}
nss_connmgr_pptp_info("%px: host inner interface registration successful\n", nss_ctx);
/*
* Initialize and configure inner I/F.
*/
data = &session_info->data;
memset(&pptpmsg, 0, sizeof(struct nss_pptp_msg));
pptpcfg = &pptpmsg.msg.session_configure_msg;
/*
* The call id is already in host byte order,
* therefore no need to do ntohs() for call id.
*/
pptpcfg->src_call_id = data->src_call;
pptpcfg->dst_call_id = data->dst_call;
/*
* Convert IP addresses from nework byte order
* to host byte order before posting to firmware.
*/
pptpcfg->sip = ntohl(data->src_ip);
pptpcfg->dip = ntohl(data->dst_ip);
/*
* Populate the sibling interfaces.
*/
pptpcfg->sibling_ifnum_pri = outer_if;
pptpcfg->sibling_ifnum_aux = host_inner_if;
nss_connmgr_pptp_info("%px: pptp info\n", dev);
nss_connmgr_pptp_info("%px: local_call_id %d peer_call_id %d\n", dev,
pptpcfg->src_call_id,
pptpcfg->dst_call_id);
nss_connmgr_pptp_info("%px: saddr 0x%x daddr 0x%x \n", dev, pptpcfg->sip, pptpcfg->dip);
nss_pptp_msg_init(&pptpmsg, inner_if, NSS_PPTP_MSG_SESSION_CONFIGURE, sizeof(struct nss_pptp_session_configure_msg), NULL, NULL);
status = nss_pptp_tx_msg_sync(nss_ctx, &pptpmsg);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: nss pptp session creation command error %d\n", dev, status);
goto tx_msg_fail;
}
nss_connmgr_pptp_info("%px: nss_pptp_tx() successful for inner\n", dev);
/*
* Initialize and configure outer I/F.
*/
pptpcfg->sibling_ifnum_pri = inner_if;
pptpcfg->sibling_ifnum_aux = host_inner_if;
nss_pptp_msg_init(&pptpmsg, outer_if, NSS_PPTP_MSG_SESSION_CONFIGURE, sizeof(struct nss_pptp_session_configure_msg), NULL, NULL);
status = nss_pptp_tx_msg_sync(nss_ctx, &pptpmsg);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: nss pptp session creation command error %d\n", dev, status);
goto tx_msg_fail;
}
nss_connmgr_pptp_info("%px: nss_pptp_tx() successful for outer\n", dev);
/*
* Initialize and configure host inner I/F.
*/
pptpcfg->sibling_ifnum_pri = outer_if;
pptpcfg->sibling_ifnum_aux = inner_if;
nss_pptp_msg_init(&pptpmsg, host_inner_if, NSS_PPTP_MSG_SESSION_CONFIGURE, sizeof(struct nss_pptp_session_configure_msg), NULL, NULL);
status = nss_pptp_tx_msg_sync(nss_ctx, &pptpmsg);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: nss pptp session creation command error %d\n", dev, status);
goto tx_msg_fail;
}
nss_connmgr_pptp_info("%px: nss_pptp_tx() successful for host inner\n", dev);
/*
* Enable the offload mode for Linux PPTP kernel driver. After this
* all PPTP GRE packets will go through the NSS FW.
*/
pptp_session_enable_offload_mode(data->dst_call, data->dst_ip);
return NOTIFY_DONE;
tx_msg_fail:
nss_unregister_pptp_if(host_inner_if);
register_host_inner_if_fail:
nss_unregister_pptp_if(outer_if);
register_outer_if_fail:
nss_unregister_pptp_if(inner_if);
register_inner_if_fail:
dev_put(dev); /* We are accessing dev later */
dev_put(session_info->phy_dev);
hash_del_rcu(&session_info->hash_list);
synchronize_rcu();
kfree(session_info);
host_inner_fail:
status = nss_dynamic_interface_dealloc_node(host_inner_if, NSS_DYNAMIC_INTERFACE_TYPE_PPTP_HOST_INNER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: Unable to dealloc the node[%d] in the NSS fw!\n", dev, host_inner_if);
}
outer_fail:
status = nss_dynamic_interface_dealloc_node(inner_if, NSS_DYNAMIC_INTERFACE_TYPE_PPTP_OUTER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: Unable to dealloc the node[%d] in the NSS fw!\n", dev, outer_if);
}
inner_fail:
status = nss_dynamic_interface_dealloc_node(inner_if, NSS_DYNAMIC_INTERFACE_TYPE_PPTP_INNER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: Unable to dealloc the node[%d] in the NSS fw!\n", dev, inner_if);
}
return NOTIFY_DONE;
}
/*
* nss_connmgr_pptp_dev_down()
* pptp interface's down event handler
*/
static int nss_connmgr_pptp_dev_down(struct net_device *dev)
{
struct nss_connmgr_pptp_session_entry *session_info;
struct nss_connmgr_pptp_session_entry *session_found = NULL;
struct hlist_node *tmp;
struct nss_pptp_msg pptpmsg;
struct nss_pptp_session_deconfigure_msg *pptpcfg;
nss_tx_status_t status;
int32_t inner_if, outer_if, host_inner_if;
/*
* check whether the interface is of type PPP
*/
if (dev->type != ARPHRD_PPP || !(dev->priv_flags_ext & IFF_EXT_PPP_PPTP)) {
nss_connmgr_pptp_info("%px: netdevice is not a pptp tunnel type\n", dev);
return NOTIFY_DONE;
}
/*
* Check if pptp inner I/F is registered ?
*/
inner_if = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_PPTP_INNER);
if (inner_if < 0) {
nss_connmgr_pptp_warning("%px: outer I/F is not registered\n", dev);
return NOTIFY_DONE;
}
/*
* Check if pptp outer I/F is registered ?
*/
outer_if = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_PPTP_OUTER);
if (outer_if < 0) {
nss_connmgr_pptp_warning("%px: inner I/F is not registered\n", dev);
return NOTIFY_DONE;
}
/*
* Check if pptp host inner I/F is registered ?
*/
host_inner_if = nss_cmn_get_interface_number_by_dev_and_type(dev, NSS_DYNAMIC_INTERFACE_TYPE_PPTP_HOST_INNER);
if (host_inner_if < 0) {
nss_connmgr_pptp_warning("%px: Net device is not registered\n", dev);
return NOTIFY_DONE;
}
hash_for_each_possible_safe(pptp_session_table, session_info,
tmp, hash_list, dev->ifindex) {
if (session_info->dev != dev) {
continue;
}
session_found = session_info;
break;
}
if (!session_found) {
nss_connmgr_pptp_warning("%px: pptp session is not found for this device", dev);
return NOTIFY_DONE;
}
/*
* Disable the pptp offload mode. This will allow all PPTP GRE packets
* to go through linux PPTP kernel module.
*/
pptp_session_disable_offload_mode(session_info->data.dst_call, session_info->data.dst_ip);
dev_put(dev);
dev_put(session_info->phy_dev);
hash_del_rcu(&session_info->hash_list);
synchronize_rcu();
memset(&pptpmsg, 0, sizeof(struct nss_pptp_msg));
pptpcfg = &pptpmsg.msg.session_deconfigure_msg;
pptpcfg->src_call_id = session_info->data.src_call;
/*
* Deconfigure all I/Fs.
*/
nss_pptp_msg_init(&pptpmsg, inner_if, NSS_PPTP_MSG_SESSION_DECONFIGURE, sizeof(struct nss_pptp_session_deconfigure_msg), NULL, NULL);
status = nss_pptp_tx_msg_sync(nss_pptp_get_context(), &pptpmsg);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: pptp session destroy command failed, if_number = %d\n", dev, inner_if);
goto fail;
}
nss_pptp_msg_init(&pptpmsg, outer_if, NSS_PPTP_MSG_SESSION_DECONFIGURE, sizeof(struct nss_pptp_session_deconfigure_msg), NULL, NULL);
status = nss_pptp_tx_msg_sync(nss_pptp_get_context(), &pptpmsg);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: pptp session destroy command failed, if_number = %d\n", dev, outer_if);
goto fail;
}
nss_pptp_msg_init(&pptpmsg, host_inner_if, NSS_PPTP_MSG_SESSION_DECONFIGURE, sizeof(struct nss_pptp_session_deconfigure_msg), NULL, NULL);
status = nss_pptp_tx_msg_sync(nss_pptp_get_context(), &pptpmsg);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: pptp session destroy command failed, if_number = %d\n", dev, host_inner_if);
goto fail;
}
/*
* Unregister all the I/Fs.
*/
nss_unregister_pptp_if(inner_if);
nss_unregister_pptp_if(outer_if);
nss_unregister_pptp_if(host_inner_if);
/*
* Dealloc all the I/Fs.
*/
status = nss_dynamic_interface_dealloc_node(inner_if, NSS_DYNAMIC_INTERFACE_TYPE_PPTP_INNER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: pptp dealloc node failure for inner if_number=%d\n", dev, inner_if);
goto fail;
}
status = nss_dynamic_interface_dealloc_node(outer_if, NSS_DYNAMIC_INTERFACE_TYPE_PPTP_OUTER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: pptp dealloc node failure for outer if_number=%d\n", dev, outer_if);
goto fail;
}
status = nss_dynamic_interface_dealloc_node(host_inner_if, NSS_DYNAMIC_INTERFACE_TYPE_PPTP_HOST_INNER);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pptp_warning("%px: pptp dealloc node failure for host inner if_number=%d\n", dev, host_inner_if);
goto fail;
}
nss_connmgr_pptp_info("%px: deleting pptpsession, if_number %d, local_call_id %d, peer_call_id %d\n", dev,
dev->ifindex, session_info->data.src_call, session_info->data.dst_call);
fail:
kfree(session_info);
return NOTIFY_DONE;
}
/*
* nss_connmgr_pptp_dev_event()
* Net device notifier for nss pptp module
*/
static int nss_connmgr_pptp_dev_event(struct notifier_block *nb,
unsigned long event, void *dev)
{
struct net_device *netdev = netdev_notifier_info_to_dev(dev);
switch (event) {
case NETDEV_UP:
nss_connmgr_pptp_info("%px: netdevice '%s' UP event\n", netdev, netdev->name);
return nss_connmgr_pptp_dev_up(netdev);
case NETDEV_DOWN:
nss_connmgr_pptp_info("%px: netdevice '%s' Down event\n", netdev, netdev->name);
return nss_connmgr_pptp_dev_down(netdev);
default:
break;
}
return NOTIFY_DONE;
}
/*
* Linux Net device Notifier
*/
struct notifier_block nss_connmgr_pptp_notifier = {
.notifier_call = nss_connmgr_pptp_dev_event,
};
/*
* nss_connmgr_pptp_init_module()
* Tunnel pptp module init function
*/
int __init nss_connmgr_pptp_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
register_netdevice_notifier(&nss_connmgr_pptp_notifier);
pptp_register_gre_seq_offload_callback(nss_connmgr_pptp_client_xmit);
return 0;
}
/*
* nss_connmgr_pptp_exit_module
* Tunnel pptp module exit function
*/
void __exit nss_connmgr_pptp_exit_module(void)
{
pptp_unregister_gre_seq_offload_callback();
#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_pptp_notifier);
}
module_init(nss_connmgr_pptp_init_module);
module_exit(nss_connmgr_pptp_exit_module);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("NSS pptp offload manager");