[qca-nss-clients] Add PPPoE clients.
Create/Destroy PPPoE session rules in NSS FW.
PPPoE client package monitors the PPP channel
connect/disconnect events and creates/destroys
the dynamic interfaces based on these events.
Change-Id: I0344f3184a60eb200bbe37094633b94cc74c1d04
Signed-off-by: Murat Sezgin <msezgin@codeaurora.org>
diff --git a/pppoe/Makefile b/pppoe/Makefile
new file mode 100644
index 0000000..5c499d4
--- /dev/null
+++ b/pppoe/Makefile
@@ -0,0 +1,5 @@
+# Makefile for pppoe client
+ccflags-y += -I$(obj)/../exports -I$(obj)/.. -I$(obj)/nss_hal/include
+ccflags-y += -DNSS_PPPOE_DEBUG_LEVEL=0
+obj-m += qca-nss-pppoe.o
+qca-nss-pppoe-objs := nss_connmgr_pppoe.o
diff --git a/pppoe/nss_connmgr_pppoe.c b/pppoe/nss_connmgr_pppoe.c
new file mode 100644
index 0000000..53fad59
--- /dev/null
+++ b/pppoe/nss_connmgr_pppoe.c
@@ -0,0 +1,439 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2017-2018, 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>
+#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("%p: channel is multilink PPP\n", dev);
+ return -1;
+ }
+
+ ppp_ch_count = ppp_hold_channels(dev, channel, 1);
+ nss_connmgr_pppoe_info("%p: PPP hold channel ret %d\n", dev, ppp_ch_count);
+ if (ppp_ch_count != 1) {
+ nss_connmgr_pppoe_warn("%p: 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("%p: session socket is not of type PX_PROTO_OE\n", dev);
+ ppp_release_channels(channel, 1);
+ return -1;
+ }
+
+ pppoe_channel_addressing_get(channel[0], addressing);
+
+ 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("%p: 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("%p: 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("%p: 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("%p: 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("%p: 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("%p: pppoe dealloc node failure for if_number=%d\n", dev, if_number);
+ } else {
+ nss_connmgr_pppoe_info("%p: 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("%p: 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("%p: 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("%p: Request interface number failed\n", dev);
+ return NOTIFY_DONE;
+ }
+
+ if (!nss_is_dynamic_interface(if_number)) {
+ nss_connmgr_pppoe_warn("%p: Invalid NSS dynamic I/F number %d\n", dev, if_number);
+ goto connect_fail1;
+ }
+
+ nss_connmgr_pppoe_info("%p: PPPoE dynamic interface allocation is sucessful with if_number %d\n", dev, if_number);
+
+ entry = nss_connmgr_add_pppoe_session(dev, &opt);
+ if (!entry) {
+ nss_connmgr_pppoe_warn("%p: 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("%p: nss_register_pppoe_session_if failed\n", dev);
+ goto connect_fail2;
+ }
+
+ nss_connmgr_pppoe_info("%p: 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 IS_ENABLED(CONFIG_BONDING)
+ bondid = bond_get_id(opt.dev);
+#endif
+ if (bondid < 0) {
+ nss_connmgr_pppoe_warn("%p: Invalid LAG group id 0x%x\n", dev, bondid);
+ goto connect_fail3;
+ }
+ npm_create->phy_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 local mac
+ * address of the session.
+ */
+ struct net_device *port = br_port_dev_get(opt.dev, info->local_mac, NULL, 0);
+ if (!port) {
+ nss_connmgr_pppoe_warn("%p: 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 physical interface.
+ */
+ npm_create->phy_if_num = nss_cmn_get_interface_number_by_dev(port);
+ nss_connmgr_pppoe_info("local_mac: %pM server_mac: %pM opt.dev: %s phy_if: %d\n",
+ info->local_mac, info->server_mac, opt.dev->name, npm_create->phy_if_num);
+ /*
+ * Release the port which was held by the br_port_dev_get() call.
+ */
+ dev_put(port);
+ } else {
+ /*
+ * PPPoE session is created on an actual physical interface like ethX.
+ */
+ npm_create->phy_if_num = nss_cmn_get_interface_number_by_dev(opt.dev);
+ }
+
+ if (npm_create->phy_if_num < 0) {
+ nss_connmgr_pppoe_warn("%p: Unable to get the nss interface number for %s\n", dev, opt.dev->name);
+ goto connect_fail3;
+ }
+
+ 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("%p: pppoe info\n", dev);
+ nss_connmgr_pppoe_info("%p: session_id %d server_mac %pM local_mac %pM phy_if %s (%d)\n",
+ dev, npm_create->session_id,
+ npm_create->server_mac, npm_create->local_mac, opt.dev->name, npm_create->phy_if_num);
+ nss_connmgr_pppoe_info("%p: 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("%p: nss pppoe session creation command error %d\n", dev, status);
+ goto connect_fail3;
+ }
+ nss_connmgr_pppoe_info("%p: 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("%p: 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("%p: 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");
diff --git a/pppoe/nss_connmgr_pppoe.h b/pppoe/nss_connmgr_pppoe.h
new file mode 100644
index 0000000..eed16f8
--- /dev/null
+++ b/pppoe/nss_connmgr_pppoe.h
@@ -0,0 +1,87 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2017-2018, 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_pppoe.h
+ * NSS PPPOE client definitions
+ */
+
+#ifndef _NSS_CONNMGR_PPPOE_H_
+#define _NSS_CONNMGR_PPPOE_H_
+/*
+ * Debug macros
+ */
+#if (NSS_PPPOE_DEBUG_LEVEL < 1)
+#define nss_connmgr_pppoe_assert(fmt, args...)
+#else
+#define nss_connmgr_pppoe_assert(c) BUG_ON(!(c))
+#endif
+
+#if defined(CONFIG_DYNAMIC_DEBUG)
+/*
+ * Compile messages for dynamic enable/disable
+ */
+#define nss_connmgr_pppoe_warn(s, ...) pr_debug("%s[%d]:" s, __func__, \
+ __LINE__, ##__VA_ARGS__)
+#define nss_connmgr_pppoe_info(s, ...) pr_debug("%s[%d]:" s, __func__, \
+ __LINE__, ##__VA_ARGS__)
+#define nss_connmgr_pppoe_trace(s, ...) pr_debug("%s[%d]:" s, __func__, \
+ __LINE__, ##__VA_ARGS__)
+#else
+
+/*
+ * Statically compile messages at different levels
+ */
+#if (NSS_PPPOE_DEBUG_LEVEL < 2)
+#define nss_connmgr_pppoe_warn(s, ...)
+#else
+#define nss_connmgr_pppoe_warn(s, ...) pr_warn("%s[%d]:" s, __func__, \
+ __LINE__, ##__VA_ARGS__)
+#endif
+
+#if (NSS_PPPOE_DEBUG_LEVEL < 3)
+#define nss_connmgr_pppoe_info(s, ...)
+#else
+#define nss_connmgr_pppoe_info(s, ...) pr_notice("%s[%d]:" s, __func__, \
+ __LINE__, ##__VA_ARGS__)
+#endif
+
+#if (NSS_PPPOE_DEBUG_LEVEL < 4)
+#define nss_connmgr_pppoe_trace(s, ...)
+#else
+#define nss_connmgr_pppoe_trace(s, ...) pr_info("%s[%d]:" s, __func__, \
+ __LINE__, ##__VA_ARGS__)
+#endif
+#endif
+
+/*
+ * Structure for PPPOE client driver session info
+ */
+struct nss_connmgr_pppoe_session_info {
+ uint32_t session_id;
+ uint8_t server_mac[ETH_ALEN];
+ uint8_t local_mac[ETH_ALEN];
+};
+
+/*
+ * Structure for PPPOE session entry into HASH table
+ */
+struct nss_connmgr_pppoe_session_entry {
+ struct nss_connmgr_pppoe_session_info info;
+ struct net_device *dev;
+ struct hlist_node hash_list;
+};
+#endif