blob: d2e08ac733a735656e188b0f985f8ee6b9b1d058 [file] [log] [blame]
/*
***************************************************************************
* Copyright (c) 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_match_cmd.c
*/
#include <linux/sysctl.h>
#include <linux/inetdevice.h>
#include <linux/netdevice.h>
#include <linux/ctype.h>
#include "nss_match_db.h"
#include "nss_match_cmd.h"
#include <nss_api_if.h>
#include "nss_match_priv.h"
unsigned char nss_match_data[100] __read_mostly;
/*
* nss_match_cmd_instance_config_tx_sync()
* Sends configuration message to NSS
*/
static nss_tx_status_t nss_match_cmd_instance_config_tx_sync(struct nss_ctx_instance *nss_ctx,
uint32_t if_num, struct nss_match_profile_configure_msg *config_msg)
{
struct nss_match_msg matchm;
nss_match_msg_init(&matchm, if_num, NSS_MATCH_TABLE_CONFIGURE_MSG,
sizeof(struct nss_match_profile_configure_msg), NULL, NULL);
matchm.msg.configure_msg = *config_msg;
return nss_match_msg_tx_sync(nss_ctx, &matchm);
}
static int nss_match_cmd_enable_instance(struct nss_match_profile_configure_msg *config_msg, int if_num, uint32_t table_id) {
nss_tx_status_t nss_tx_status;
struct nss_ctx_instance *nss_ctx = nss_match_get_context();
nss_tx_status = nss_match_cmd_instance_config_tx_sync(nss_ctx, if_num, config_msg);
if (nss_tx_status == NSS_TX_SUCCESS) {
if (!nss_match_db_instance_enable(table_id)) {
nss_match_warn("Failed to enable instance for table_id=%u\n", table_id);
return -1;
}
return 0;
}
nss_match_warn("%px: Cannot configure/enable the new instance\n", nss_ctx);
return -1;
}
/*
* nss_match_cmd_parse()
* Returns command type.
*/
static nss_match_cmd_t nss_match_cmd_parse(char *cmd)
{
if (cmd == NULL)
return NSS_MATCH_UNKNOWN;
if (!(strncasecmp(cmd, "createtable", strlen("createtable"))))
return NSS_MATCH_CREATE_TABLE;
if (!(strncasecmp(cmd, "addmask", strlen("addmask"))))
return NSS_MATCH_ADD_MASK;
if (!(strncasecmp(cmd, "enable", strlen("enable"))))
return NSS_MATCH_ENABLE;
if (!(strncasecmp(cmd, "addrule", strlen("addrule"))))
return NSS_MATCH_ADD_RULE;
if (!(strncasecmp(cmd, "delrule", strlen("delrule"))))
return NSS_MATCH_DELETE_RULE;
if (!(strncasecmp(cmd, "deltable", strlen("deltable"))))
return NSS_MATCH_DESTROY_TABLE;
return NSS_MATCH_UNKNOWN;
}
/*
* nss_match_cmd_get_profile_type()
* Parse message to create an instance.
*/
static enum nss_match_profile_type nss_match_cmd_get_profile_type(char *input_msg)
{
char *token, *param;
token = strsep(&input_msg, " ");
if (!token) {
return NSS_MATCH_PROFILE_TYPE_NONE;
}
param = strsep(&token, "=");
if (!param || !token) {
return NSS_MATCH_PROFILE_TYPE_NONE;
}
if (!(strncasecmp(param, "profile_type", strlen("profile_type")))) {
if (!(strncasecmp(token, "vow", strlen("vow")))) {
return NSS_MATCH_PROFILE_TYPE_VOW;
}
if (!(strncasecmp(token, "l2", strlen("l2")))) {
return NSS_MATCH_PROFILE_TYPE_L2;
}
}
return NSS_MATCH_PROFILE_TYPE_NONE;
}
/*
* nss_match_cmd_procfs_config_handler()
* Handles command input by user to create and configure match instance.
*/
static int nss_match_cmd_procfs_config_handler(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
{
char *command_str, *token, *param, *value;
char *input_msg, *input_msg_orig;
nss_match_cmd_t command;
struct nss_ctx_instance *nss_ctx = nss_match_get_context();
size_t count = *lenp;
int ret = proc_dostring(ctl, write, buffer, lenp, ppos);
if (!write) {
return ret;
}
input_msg = (char *)kzalloc(count + 1, GFP_KERNEL);
if (!input_msg) {
nss_match_warn("%px: Dynamic allocation falied while writing input message from file", ctl);
return -ENOMEM;
}
input_msg_orig = input_msg;
if (copy_from_user(input_msg, buffer, count)) {
kfree(input_msg);
nss_match_warn("%px: Cannot copy user's entry to kernel memory\n", ctl);
return -EFAULT;
}
command_str = strsep(&input_msg, " ");
command = nss_match_cmd_parse(command_str);
switch (command) {
case NSS_MATCH_CREATE_TABLE:
{
int table_id = -1, profile_type = 0;
profile_type = nss_match_cmd_get_profile_type(input_msg);
if (profile_type == NSS_MATCH_PROFILE_TYPE_NONE) {
pr_warn("%px: Please provide a valid profile type\n", ctl);
kfree(input_msg_orig);
return -EINVAL;
}
table_id = nss_match_instance_create();
if (table_id <= 0) {
pr_warn("%px: Cannot create a new match instance\n", ctl);
kfree(input_msg_orig);
return -EINVAL;
}
nss_match_db_profile_type_add(profile_type, table_id);
pr_warn("%px: New match instance created, table_id = %d\n", ctl, table_id);
kfree(input_msg_orig);
return count;
}
case NSS_MATCH_ADD_MASK:
{
uint32_t table_id = 0;
struct nss_match_msg input_mask_param = {0};
token = strsep(&input_msg, " ");
param = strsep(&token, "=");
value = token;
if (!value || !param) {
goto fail;
}
if (!strncasecmp(param, "table_id", strlen("table_id"))) {
ret = sscanf(value, "%u", &table_id);
if (!ret) {
pr_warn("%px: Cannot convert to integer. Wrong input!!", ctl);
kfree(input_msg_orig);
return -EINVAL;
}
}
if (table_id == 0 || table_id > NSS_MATCH_INSTANCE_MAX) {
pr_warn("%px: Invalid table_id %d", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
if (nss_match_db_table_validate(table_id)) {
pr_warn("%px: Table is already configured, %d", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
if (nss_match_db_parse_cmd(table_id, input_msg, &input_mask_param, NSS_MATCH_ADD_MASK)) {
kfree(input_msg_orig);
return -EINVAL;
}
nss_match_db_mask_add(&input_mask_param.msg.configure_msg, table_id);
pr_warn("%px: Mask added to instance successfully. %d", ctl, table_id);
kfree(input_msg_orig);
return count;
}
case NSS_MATCH_ENABLE:
{
uint32_t table_id = 0;
struct nss_match_profile_configure_msg config_msg = {0};
int if_num = -1;
token = strsep(&input_msg, " ");
param = strsep(&token, "=");
value = token;
if (!param || !value) {
goto fail;
}
if (!strncasecmp(param, "table_id", strlen("table_id"))) {
ret = sscanf(value, "%u", &table_id);
if (!ret) {
pr_warn("%px: Cannot convert to integer. Wrong input!!", ctl);
kfree(input_msg_orig);
return -EINVAL;
}
}
if ((table_id == 0) || (table_id > NSS_MATCH_INSTANCE_MAX)) {
pr_warn("%px: Invalid table_id %d", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
if (nss_match_db_table_validate(table_id)) {
pr_warn("%px: Table is already configured, %d", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
if (!nss_match_db_instance_config_get(&config_msg, &if_num, table_id)) {
pr_warn("%px: Unable to fetch stored configuration %d", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
if (if_num < 0) {
nss_match_warn("%px: Incorrect interface number: %d\n", ctl, if_num);
kfree(input_msg_orig);
return -EINVAL;
}
if (nss_match_cmd_enable_instance(&config_msg, if_num, table_id)) {
pr_warn("%px: Failed to enable table %d\n", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
pr_warn("%px: Table %d enabled successfully\n", ctl, table_id);
kfree(input_msg_orig);
return count;
}
case NSS_MATCH_ADD_RULE:
{
uint32_t profile_type = 0;
uint32_t table_id = 0;
int rule_id = -1;
struct nss_match_msg input_rule_param = {0};
token = strsep(&input_msg, " ");
param = strsep(&token, "=");
value = token;
if (!param || !value) {
goto fail;
}
if (!strncasecmp(param, "table_id", strlen("table_id"))) {
ret = sscanf(value, "%u", &table_id);
if (!ret) {
pr_warn("%px: Cannot convert to integer. Wrong input!!", ctl);
kfree(input_msg_orig);
return -EINVAL;
}
}
if (table_id == 0 || table_id > NSS_MATCH_INSTANCE_MAX) {
pr_warn("%px: Invalid table_id: %d", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
nss_match_db_get_profile_type(table_id, &profile_type);
if (nss_match_db_parse_cmd(table_id, input_msg, &input_rule_param, NSS_MATCH_ADD_RULE)) {
pr_warn("%px: Wrong input", ctl);
kfree(input_msg_orig);
return -EINVAL;
}
if (profile_type == NSS_MATCH_PROFILE_TYPE_VOW) {
rule_id = nss_match_vow_rule_add(nss_ctx, &input_rule_param.msg.vow_rule, table_id);
} else if (profile_type == NSS_MATCH_PROFILE_TYPE_L2) {
rule_id = nss_match_l2_rule_add(nss_ctx, &input_rule_param.msg.l2_rule, table_id);
}
if (rule_id < 0) {
pr_warn("%px: Failed to add rule into table %d.\n", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
pr_warn("%px: Rule added to table %d successfully with rule_id: %d\n", ctl, table_id, rule_id);
kfree(input_msg_orig);
return count;
}
case NSS_MATCH_DELETE_RULE:
{
uint32_t table_id = 0;
uint16_t rule_id = 0;
while (input_msg != NULL) {
token = strsep(&input_msg, " ");
param = strsep(&token, "=");
if (!param || !token) {
goto fail;
}
/*
* Parsing rule_id and table_id value from the message.
*/
if (!(strncasecmp(param, "rule_id", strlen("rule_id")))) {
if (!sscanf(token, "%hu", &rule_id)) {
pr_warn("%px: Cannot convert to integer. Wrong input\n", ctl);
kfree(input_msg_orig);
return -EINVAL;
}
continue;
}
if (!strncasecmp(param, "table_id", strlen("table_id"))) {
if (!sscanf(token, "%u", &table_id)) {
pr_warn("%px: Cannot convert to integer. Wrong input!!", ctl);
kfree(input_msg_orig);
return -EINVAL;
}
continue;
}
kfree(input_msg_orig);
return -EINVAL;
}
if (table_id == 0 || table_id > NSS_MATCH_INSTANCE_MAX) {
pr_warn("%px: Invalid table_id: %d", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
if (rule_id == 0 || rule_id > NSS_MATCH_INSTANCE_RULE_MAX) {
pr_warn("%px: Invalid rule_id: %d", ctl, rule_id);
kfree(input_msg_orig);
return -EINVAL;
}
if (nss_match_rule_delete(nss_ctx, rule_id, table_id)) {
pr_warn("%px: Failed to delete rule from table %d.\n", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
pr_warn("%px: Rule deleted from table %d successfully\n", ctl, table_id);
kfree(input_msg_orig);
return count;
}
case NSS_MATCH_DESTROY_TABLE:
{
uint32_t table_id = 0;
char *token, *param;
int ret = 0;
token = strsep(&input_msg, " ");
param = strsep(&token, "=");
if (!token || !param) {
goto fail;
}
if (!(strncasecmp(param, "table_id", strlen("table_id")))) {
ret = sscanf(token, "%u", &table_id);
if (!ret) {
pr_warn("%px: Cannot convert to integer. Wrong input!!", input_msg);
kfree(input_msg_orig);
return -EINVAL;
}
}
if (table_id == 0 || table_id > NSS_MATCH_INSTANCE_MAX) {
pr_warn("%px: Invalid table_id: %d", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
if (nss_match_instance_destroy(table_id)) {
pr_warn("%px: Failed to destroy table %d\n", ctl, table_id);
kfree(input_msg_orig);
return -EINVAL;
}
pr_warn("%px: Table %d destroyed successfully.\n", ctl, table_id);
kfree(input_msg_orig);
return count;
}
default:
{
pr_warn("%px: Input command is not as per syntax, Please enter a valid command", ctl);
kfree(input_msg_orig);
return -EINVAL;
}
}
fail:
pr_warn("%px: Wrong input, check help. (cat /proc/sys/dev/nss/match/help)", ctl);
kfree(input_msg_orig);
return ret;
}
/*
* nss_match_cmd_procfs_reset_nexthop
* Reset to default nexthop of an interface
*/
static int nss_match_cmd_procfs_reset_nexthop(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
{
struct net_device *dev;
uint32_t if_num, type = 0;
int ret;
char *dev_name;
char *cmd_buf = nss_match_data;
nss_tx_status_t nss_tx_status;
struct nss_ctx_instance *nss_ctx = nss_match_get_context();
struct nss_ctx_instance *wifi_nss_ctx = nss_wifi_get_context();
if (!nss_ctx || !wifi_nss_ctx) {
pr_warn("%px: NSS Context not found. wifi_nss_ctx: %px. Reset nexthop failed", nss_ctx, wifi_nss_ctx);
return -ENOMEM;
}
ret = proc_dostring(ctl, write, buffer, lenp, ppos);
if (!write) {
pr_warn("%px: Reset nexthop failed.\n", nss_ctx);
return ret;
}
/*
* Parse and read the devname from command.
*/
dev_name = strsep(&cmd_buf, "\0");
dev = dev_get_by_name(&init_net, dev_name);
if (!dev) {
pr_warn("%px: Cannot find the net device: %s. Reset nexthop failed.\n", nss_ctx, dev_name);
return -ENODEV;
}
if_num = nss_cmn_get_interface_number_by_dev(dev);
if (if_num < 0) {
pr_warn("%px: Invalid if_num for interface: %s. Reset nexthop failed.\n", nss_ctx, dev_name);
dev_put(dev);
return -ENODEV;
}
/*
* Reset Nexthop APIs.
* nss_phys_if_reset_nexthop: Used for physical interfaces.
* nss_if_reset_nexthop: used for VAP interfaces.
*/
type = nss_dynamic_interface_get_type(wifi_nss_ctx, if_num);
if (type == NSS_DYNAMIC_INTERFACE_TYPE_VAP) {
nss_tx_status = nss_if_reset_nexthop(wifi_nss_ctx, if_num);
} else if (if_num < NSS_MAX_PHYSICAL_INTERFACES) {
nss_tx_status = nss_phys_if_reset_nexthop(nss_ctx, if_num);
} else {
pr_warn("%px: Invalid interface to Reset nexthop. Failed to Reset nexthop on if_num %d.\n",
nss_ctx, if_num);
dev_put(dev);
return -EFAULT;
}
if (nss_tx_status != NSS_TX_SUCCESS) {
pr_warn("%px: Sending message failed, cannot reset nexthop\n", nss_ctx);
}
dev_put(dev);
pr_info("%px: Reset nexthop successful.\n", nss_ctx);
return 0;
}
/*
* nss_match_cmd_procfs_set_if_nexthop
* Set next hop of an interface to a match instance.
* Only VAP and physical interfaces are supported as of now.
*/
static int nss_match_cmd_procfs_set_if_nexthop(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
{
struct net_device *dev;
uint32_t if_num, type = 0;
uint32_t nh_if_num;
int table_id;
struct nss_ctx_instance *nss_ctx = nss_match_get_context();
struct nss_ctx_instance *wifi_nss_ctx = nss_wifi_get_context();
char *dev_name, *nexthop_msg;
char *cmd_buf = NULL;
size_t count = *lenp;
nss_tx_status_t nss_tx_status;
int ret = proc_dostring(ctl, write, buffer, lenp, ppos);
if (!write) {
return ret;
}
if (!nss_ctx || !wifi_nss_ctx) {
pr_warn("%px: NSS Context not found. wifi_nss_ctx: %px. Set nexthop failed", nss_ctx, wifi_nss_ctx);
return -ENOMEM;
}
cmd_buf = (char *)kzalloc(count + 1, GFP_KERNEL);
nexthop_msg = cmd_buf;
if (!cmd_buf) {
pr_warn("%px: Cannot allocate buffer to read input", nss_ctx);
return -ENOMEM;
}
if (copy_from_user(cmd_buf, buffer, count)) {
kfree(nexthop_msg);
pr_warn("%px: Cannot copy user's entry to kernel memory\n", nss_ctx);
return -EFAULT;
}
dev_name = strsep(&cmd_buf, " ");
dev = dev_get_by_name(&init_net, dev_name);
if (!dev) {
pr_warn("%px: Cannot find the net device\n", nss_ctx);
kfree(nexthop_msg);
return -ENODEV;
}
if_num = nss_cmn_get_interface_number_by_dev(dev);
if (if_num < 0) {
pr_warn("%px: Invalid interface number:%d\n", nss_ctx, if_num);
kfree(nexthop_msg);
dev_put(dev);
return -ENODEV;
}
if (isdigit(cmd_buf[0])) {
if (!sscanf(cmd_buf, "%u", &nh_if_num)) {
pr_warn("%px, Failed to write the nexthop if_num token to integer\n", nss_ctx);
kfree(nexthop_msg);
dev_put(dev);
return -EFAULT;
}
} else {
pr_warn("%px: Invalid nexthop interface number.\n", nss_ctx);
kfree(nexthop_msg);
dev_put(dev);
return -ENODEV;
}
if (nh_if_num < 0) {
pr_warn("%px: Invalid nexthop interface number:%d\n", nss_ctx, if_num);
kfree(nexthop_msg);
dev_put(dev);
return -ENODEV;
}
table_id = nss_match_get_table_id_by_ifnum(nh_if_num);
if (table_id <= 0 || table_id > NSS_MATCH_INSTANCE_MAX) {
pr_warn("Invalid match interface. Failed to set %d as nexthop.\n", nh_if_num);
kfree(nexthop_msg);
dev_put(dev);
return -EFAULT;
}
/*
* Set Nexthop APIs.
* nss_phys_if_set_nexthop: Used for physical interfaces.
* nss_if_set_nexthop: used for VAP interfaces.
*/
type = nss_dynamic_interface_get_type(wifi_nss_ctx, if_num);
if (type == NSS_DYNAMIC_INTERFACE_TYPE_VAP) {
nss_tx_status = nss_if_set_nexthop(wifi_nss_ctx, if_num, nh_if_num);
} else if (if_num < NSS_MAX_PHYSICAL_INTERFACES) {
nss_tx_status = nss_phys_if_set_nexthop(nss_ctx, if_num, nh_if_num);
} else {
pr_warn("Invalid interface to set nexthop. Failed to set nexthop on if_num %d.\n", if_num);
kfree(nexthop_msg);
dev_put(dev);
return -EFAULT;
}
if (nss_tx_status != NSS_TX_SUCCESS) {
pr_warn("%px: Sending message failed, cannot change nexthop\n", nss_ctx);
}
kfree(nexthop_msg);
dev_put(dev);
return ret;
}
/*
* nss_match_cmd_procfs_read_help()
* Display help for commands.
*/
static int nss_match_cmd_procfs_read_help(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
{
int ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
pr_info("\nHelp: (/proc/sys/dev/nss/match/help) \n\
1. To create match isntance:\n\
echo createtable profile_type=<vow/l2> > config \n\
2. To addmask: \n\
a. VoW profile \n\
echo addmask table_id=<1..4> mask=<1/2> ifname=<1..ffff> dscp=<1..3f> 802.1p_outer=<1..7> 802.1p_inner=<1..7> > config\n\
b. L2 profile \n\
echo addmask table_id=<1..4> mask=<1/2> ifname=<1..ffff> smac=<1..ffffffffffff>\
dmac=<1..ffffffffffff> ethertype=<1..ffff> > config\n\
3. To enable match instance \n\
echo enable table_id=1 > config \n\
5. Actions:\n\
a. action=1 priority=<pri_value>\n\
b. action=2 nexthop=<nexthop_ifnum>\n\
c. action=3 priority=<pri_value> nexthop=<nexthop_ifnum>\n\
d. action=4\n\
4. To set nexthop: \n\
echo <phy_ifname/VAP name> <match_ifnum> > set_nexthop\n\
5. To reset nexthop: \n\
echo <dev_name> > reset_nexthop\n");
*lenp = 0;
return ret;
}
static struct ctl_table nss_match_table[] = {
{
.procname = "config",
.data = &nss_match_data,
.maxlen = sizeof(nss_match_data),
.mode = 0644,
.proc_handler = &nss_match_cmd_procfs_config_handler,
},
{
.procname = "set_nexthop",
.data = &nss_match_data,
.maxlen = sizeof(nss_match_data),
.mode = 0644,
.proc_handler = &nss_match_cmd_procfs_set_if_nexthop,
},
{
.procname = "reset_nexthop",
.data = &nss_match_data,
.maxlen = IFNAMSIZ,
.mode = 0644,
.proc_handler = &nss_match_cmd_procfs_reset_nexthop,
},
{
.procname = "help",
.data = &nss_match_data,
.maxlen = sizeof(nss_match_data),
.mode = 0400,
.proc_handler = &nss_match_cmd_procfs_read_help,
},
{ }
};
static struct ctl_table nss_match_root_dir[] = {
{
.procname = "match",
.mode = 0555,
.child = nss_match_table,
},
{ }
};
static struct ctl_table nss_match_nss_root_dir[] = {
{
.procname = "nss",
.mode = 0555,
.child = nss_match_root_dir,
},
{ }
};
static struct ctl_table nss_match_root[] = {
{
.procname = "dev",
.mode = 0555,
.child = nss_match_nss_root_dir,
},
{ }
};
static struct ctl_table_header *nss_match_ctl_header;
/*
* nss_match_ctl_register
* Register command line interface for match.
*/
bool nss_match_ctl_register(void) {
nss_match_ctl_header = register_sysctl_table(nss_match_root);
if (!nss_match_ctl_header) {
nss_match_warn("Unable to register command line interface.\n");
return false;
}
return true;
}
/*
* nss_match_ctl_unregister
* Unregister command line interface for match.
*/
void nss_match_ctl_unregister(void) {
if (nss_match_ctl_header) {
unregister_sysctl_table(nss_match_ctl_header);
}
}