blob: 00bc79435de3133a9e3a2ffe41a1a97ae5a101fc [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.
**************************************************************************
*/
#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/etherdevice.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>
#include <linux/if_bridge.h>
#include <net/bonding.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#endif
#include <nss_api_if.h>
#include <nss_dynamic_interface.h>
#include "nss_connmgr_pppoe.h"
#define HASH_BUCKET_SIZE 2 /* ( 2^ HASH_BUCKET_SIZE ) == 4 */
static DEFINE_HASHTABLE(pppoe_session_table, HASH_BUCKET_SIZE);
/*
* nss_connmgr_pppoe_get_session()
* Retrieve pppoe session associated with this netdevice if any
*/
static int nss_connmgr_pppoe_get_session(struct net_device *dev, struct pppoe_opt *addressing)
{
struct ppp_channel *channel[1] = {NULL};
int px_proto;
int ppp_ch_count;
if (ppp_is_multilink(dev)) {
nss_connmgr_pppoe_warn("%px: channel is multilink PPP\n", dev);
return -1;
}
ppp_ch_count = ppp_hold_channels(dev, channel, 1);
nss_connmgr_pppoe_info("%px: PPP hold channel ret %d\n", dev, ppp_ch_count);
if (ppp_ch_count != 1) {
nss_connmgr_pppoe_warn("%px: hold channel for netdevice failed\n", dev);
return -1;
}
px_proto = ppp_channel_get_protocol(channel[0]);
if (px_proto != PX_PROTO_OE) {
nss_connmgr_pppoe_warn("%px: session socket is not of type PX_PROTO_OE\n", dev);
ppp_release_channels(channel, 1);
return -1;
}
if (pppoe_channel_addressing_get(channel[0], addressing)) {
nss_connmgr_pppoe_warn("%px: failed to get addressing information\n", dev);
ppp_release_channels(channel, 1);
return -1;
}
dev_put(addressing->dev);
ppp_release_channels(channel, 1);
return 0;
}
/*
* nss_connmgr_add_pppoe_session()
* Add PPPoE session entry into Hash table
*/
static struct nss_connmgr_pppoe_session_entry *nss_connmgr_add_pppoe_session(struct net_device *dev, struct pppoe_opt *opt)
{
struct nss_connmgr_pppoe_session_entry *entry = NULL;
struct nss_connmgr_pppoe_session_info *info;
entry = kmalloc(sizeof(struct nss_connmgr_pppoe_session_entry),
GFP_KERNEL);
if (!entry) {
nss_connmgr_pppoe_warn("%px: failed to allocate pppoe session entry\n", dev);
return NULL;
}
info = &entry->info;
/*
* Get session info
*/
info->session_id = (uint16_t)ntohs((uint16_t)opt->pa.sid);
ether_addr_copy(info->server_mac, opt->pa.remote);
ether_addr_copy(info->local_mac, opt->dev->dev_addr);
nss_connmgr_pppoe_info("%px: Added PPPoE session with session_id=%u server_mac=%pM local_mac %pM\n",
dev, info->session_id, info->server_mac, info->local_mac);
entry->dev = dev;
/*
* There is no need for protecting simultaneous addition &
* deletion of pppoe sesion entry as the PPP notifier chain
* call back is called with mutex lock.
*/
hash_add_rcu(pppoe_session_table,
&entry->hash_list,
dev->ifindex);
return entry;
}
/*
* nss_connmgr_pppoe_disconnect()
* pppoe interface's disconnect event handler
*/
static int nss_connmgr_pppoe_disconnect(struct net_device *dev)
{
struct nss_connmgr_pppoe_session_entry *entry;
struct nss_connmgr_pppoe_session_entry *found = NULL;
struct hlist_node *temp;
struct nss_pppoe_msg npm;
struct nss_pppoe_destroy_msg *npm_destroy;
nss_tx_status_t status;
int if_number;
/*
* check whether the interface is of type PPP
*/
if (dev->type != ARPHRD_PPP || !(dev->flags & IFF_POINTOPOINT)) {
return NOTIFY_DONE;
}
/*
* Check if pppoe is registered ?
*/
if_number = nss_cmn_get_interface_number_by_dev(dev);
if (if_number < 0) {
nss_connmgr_pppoe_warn("%px: Net device is not registered with nss\n", dev);
return NOTIFY_DONE;
}
hash_for_each_possible_safe(pppoe_session_table, entry,
temp, hash_list, dev->ifindex) {
if (entry->dev != dev) {
continue;
}
/*
* In the hash list, there must be only one entry match with this net device.
*/
found = entry;
break;
}
if (!found) {
nss_connmgr_pppoe_warn("%px: PPPoE session is not found for device: %s\n", dev, dev->name);
return NOTIFY_DONE;
}
hash_del_rcu(&entry->hash_list);
synchronize_rcu();
memset(&npm, 0, sizeof(struct nss_pppoe_msg));
npm_destroy = &npm.msg.destroy;
npm_destroy->session_id = entry->info.session_id;
ether_addr_copy(npm_destroy->server_mac, entry->info.server_mac);
ether_addr_copy(npm_destroy->local_mac, entry->info.local_mac);
nss_pppoe_msg_init(&npm, if_number, NSS_PPPOE_MSG_SESSION_DESTROY, sizeof(struct nss_pppoe_destroy_msg), NULL, NULL);
status = nss_pppoe_tx_msg_sync(nss_pppoe_get_context(), &npm);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pppoe_warn("%px: pppoe session destroy command failed, if_number = %d\n", dev, if_number);
goto done;
}
nss_unregister_pppoe_session_if(if_number);
status = nss_dynamic_interface_dealloc_node(if_number, NSS_DYNAMIC_INTERFACE_TYPE_PPPOE);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pppoe_warn("%px: pppoe dealloc node failure for if_number=%d\n", dev, if_number);
} else {
nss_connmgr_pppoe_info("%px: PPPoE session is destroyed with if_number %d, session_id %d, server_mac %pM, local_mac %pM\n",
dev, if_number, entry->info.session_id,
entry->info.server_mac, entry->info.local_mac);
}
done:
kfree(entry);
return NOTIFY_DONE;
}
/*
* nss_connmgr_pppoe_connect()
* pppoe interface's connect event handler
*/
static int nss_connmgr_pppoe_connect(struct net_device *dev)
{
struct pppoe_opt opt;
struct nss_connmgr_pppoe_session_entry *entry = NULL;
struct nss_connmgr_pppoe_session_info *info;
nss_tx_status_t status;
struct nss_ctx_instance *nss_ctx;
uint32_t features = 0;
int32_t if_number;
struct nss_pppoe_msg npm;
struct nss_pppoe_create_msg *npm_create;
int ret;
/*
* check whether the interface is of type PPP
*/
if (dev->type != ARPHRD_PPP || !(dev->flags & IFF_POINTOPOINT)) {
return NOTIFY_DONE;
}
/*
* Check if pppoe is already registered.
*/
if_number = nss_cmn_get_interface_number_by_dev(dev);
if (if_number >= 0) {
nss_connmgr_pppoe_warn("%px: Net device is already registered with nss\n", dev);
return NOTIFY_DONE;
}
ret = nss_connmgr_pppoe_get_session(dev, &opt);
if (ret < 0) {
nss_connmgr_pppoe_warn("%px: Unable to get pppoe session from the netdev\n", dev);
return NOTIFY_DONE;
}
/*
* Create nss dynamic interface and register
*/
if_number = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_PPPOE);
if (if_number == -1) {
nss_connmgr_pppoe_warn("%px: Request interface number failed\n", dev);
return NOTIFY_DONE;
}
if (!nss_is_dynamic_interface(if_number)) {
nss_connmgr_pppoe_warn("%px: Invalid NSS dynamic I/F number %d\n", dev, if_number);
goto connect_fail1;
}
nss_connmgr_pppoe_info("%px: PPPoE dynamic interface allocation is successful with if_number %d\n", dev, if_number);
entry = nss_connmgr_add_pppoe_session(dev, &opt);
if (!entry) {
nss_connmgr_pppoe_warn("%px: PPPoE session add failed %d\n", dev, if_number);
goto connect_fail1;
}
/*
* Register pppoe tunnel with NSS
*/
nss_ctx = nss_register_pppoe_session_if(if_number,
NULL,
dev,
features,
entry);
if (!nss_ctx) {
nss_connmgr_pppoe_warn("%px: nss_register_pppoe_session_if failed\n", dev);
goto connect_fail2;
}
nss_connmgr_pppoe_info("%px: PPPoE session interface registration is successful\n", nss_ctx);
memset(&npm, 0, sizeof(struct nss_pppoe_msg));
npm_create = &npm.msg.create;
info = &entry->info;
npm_create->session_id = info->session_id;
/*
* PPPoE connection could be on a bridge device like br-wan or on bond device like bond0.
* So, check if the opt device is bridge or bond.
*/
if (netif_is_bond_master(opt.dev)) {
int32_t bondid = -1;
#if defined(BONDING_SUPPORT)
bondid = bond_get_id(opt.dev);
#endif
if (bondid < 0) {
nss_connmgr_pppoe_warn("%px: Invalid LAG group id 0x%x\n", dev, bondid);
goto connect_fail3;
}
npm_create->base_if_num = bondid + NSS_LAG0_INTERFACE_NUM;
} else if (opt.dev->priv_flags & IFF_EBRIDGE) {
/*
* Device is bridge. We need to get the actual physical port.
* Searching this physical port in the fdb database with the server mac
* address of the session.
*/
struct net_device *port = br_port_dev_get(opt.dev, info->server_mac, NULL, 0);
if (!port) {
nss_connmgr_pppoe_warn("%px: Unable to get the bridge port device from the bridge interface: %s\n",
dev, opt.dev->name);
goto connect_fail3;
}
/*
* Get the interface number of the base interface.
*/
npm_create->base_if_num = nss_cmn_get_interface_number_by_dev(port);
nss_connmgr_pppoe_info("local_mac: %pM server_mac: %pM opt.dev: %s base_if: %d\n",
info->local_mac, info->server_mac, opt.dev->name, npm_create->base_if_num);
/*
* Release the port which was held by the br_port_dev_get() call.
*/
dev_put(port);
} else {
/*
* PPPoE sessions can be created over either a physical or virtual interface. They may not be
* created over a LAG or bridge interface. This interface is the base interface which will be used
* for HW next_hop. On SoCs, which do not have HW acceleration, this interface number is not used
* and -1 is returned.
*/
npm_create->base_if_num = nss_cmn_get_interface_number_by_dev(opt.dev);
}
ether_addr_copy(npm_create->server_mac, info->server_mac);
ether_addr_copy(npm_create->local_mac, info->local_mac);
npm_create->mtu = dev->mtu;
nss_connmgr_pppoe_info("%px: pppoe info\n", dev);
nss_connmgr_pppoe_info("%px: session_id %d server_mac %pM local_mac %pM base_if %s (%d)\n",
dev, npm_create->session_id,
npm_create->server_mac, npm_create->local_mac, opt.dev->name, npm_create->base_if_num);
nss_connmgr_pppoe_info("%px: Sending pppoe session create command to NSS\n", dev);
nss_pppoe_msg_init(&npm, if_number, NSS_PPPOE_MSG_SESSION_CREATE, sizeof(struct nss_pppoe_create_msg), NULL, NULL);
status = nss_pppoe_tx_msg_sync(nss_ctx, &npm);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pppoe_warn("%px: nss pppoe session creation command error %d\n", dev, status);
goto connect_fail3;
}
nss_connmgr_pppoe_info("%px: PPPoE session creation is successful\n", dev);
return NOTIFY_DONE;
connect_fail3:
nss_unregister_pppoe_session_if(if_number);
connect_fail2:
hash_del_rcu(&entry->hash_list);
synchronize_rcu();
kfree(entry);
connect_fail1:
status = nss_dynamic_interface_dealloc_node(if_number, NSS_DYNAMIC_INTERFACE_TYPE_PPPOE);
if (status != NSS_TX_SUCCESS) {
nss_connmgr_pppoe_warn("%px: Unable to dealloc the node[%d] in the NSS fw!\n", dev, if_number);
}
return NOTIFY_DONE;
}
/*
* nss_connmgr_pppoe_channel_notifier_handler()
* PPPoE channel notifier handler.
*/
static int nss_connmgr_pppoe_channel_notifier_handler(struct notifier_block *nb,
unsigned long event,
void *arg)
{
struct net_device *dev = (struct net_device *)arg;
switch (event) {
case PPP_CHANNEL_CONNECT:
return nss_connmgr_pppoe_connect(dev);
case PPP_CHANNEL_DISCONNECT:
return nss_connmgr_pppoe_disconnect(dev);
default:
nss_connmgr_pppoe_info("%px: Unhandled channel event: %lu\n", dev, event);
break;
}
return NOTIFY_DONE;
}
struct notifier_block nss_connmgr_pppoe_channel_notifier_nb = {
.notifier_call = nss_connmgr_pppoe_channel_notifier_handler,
};
/*
* nss_connmgr_pppoe_exit_module
* pppoe module exit function
*/
void __exit nss_connmgr_pppoe_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 the module from the PPP channel events.
*/
ppp_channel_connection_unregister_notify(&nss_connmgr_pppoe_channel_notifier_nb);
}
/*
* nss_connmgr_pppoe_init_module()
* pppoe module init function
*/
int __init nss_connmgr_pppoe_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 the module to the PPP channel events.
*/
ppp_channel_connection_register_notify(&nss_connmgr_pppoe_channel_notifier_nb);
return 0;
}
module_init(nss_connmgr_pppoe_init_module);
module_exit(nss_connmgr_pppoe_exit_module);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("NSS pppoe offload manager");