blob: 3632d62b32ac6e5925ae1e766769e6564aba3342 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2020-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.
**************************************************************************
*/
#include <linux/version.h>
#include <linux/types.h>
#include <linux/ip.h>
#include <linux/of.h>
#include <linux/tcp.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <net/ipv6.h>
#include <net/ip_tunnels.h>
#include <net/ip6_tunnel.h>
#include <linux/if_arp.h>
#include <nss_api_if.h>
#include <linux/sysctl.h>
#include <linux/printk.h>
#include <linux/inet.h>
#include "nss_connmgr_tunipip6.h"
#define MAX_PROC_SIZE 1024
#define MAX_DATA_LEN 500
#define MAX_IPV4_PREFIX_LEN 32
#define MAX_IPV6_PREFIX_LEN 128
#define MAX_PSID_OFFSET_LEN 15
#define NETDEV_STR_LEN 30
#define PREFIX_STR_LEN 100
extern bool frag_id_update;
unsigned char nss_tunipip6_data[MAX_DATA_LEN] __read_mostly;
enum nss_tunipip6_sysctl_mode {
NSS_TUNIPIP6_SYSCTL_ADD_MAPRULE,
NSS_TUNIPIP6_SYSCTL_DEL_MAPRULE,
NSS_TUNIPIP6_SYSCTL_FLUSH_FMR_RULE,
NSS_TUNIPIP6_SYSCTL_FRAG_ID,
};
static int nss_tunipip6_data_parser(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos, enum nss_tunipip6_sysctl_mode mode)
{
char dev_name[NETDEV_STR_LEN] = {0}, ipv6_prefix_str[PREFIX_STR_LEN] = {0}, ipv6_suffix_str[PREFIX_STR_LEN] = {0}, ipv4_prefix_str[PREFIX_STR_LEN] = {0};
uint32_t ipv6_prefix[4], ipv6_prefix_len, ipv6_suffix[4], ipv6_suffix_len, ipv4_prefix, ipv4_prefix_len, ea_len, psid_offset;
int rule_type = 0, frag_id = 0;
bool ipv6_prefix_valid = false, ipv6_prefix_len_valid = false, ipv6_suffix_valid = false;
bool ipv4_prefix_valid = false, ipv4_prefix_len_valid = false, ipv6_suffix_len_valid = false;
bool rule_type_valid = false, ea_len_valid = false, psid_offset_valid = false, netdev_valid = false;
struct nss_connmgr_tunipip6_maprule_cfg mrcfg = {0};
char *buf;
enum nss_connmgr_tunipip6_err_codes status;
struct net_device *dev = NULL;
char *pfree;
char *token;
int ret;
int count;
if (!write) {
return -EINVAL;
}
buf = kzalloc(MAX_PROC_SIZE, GFP_KERNEL);
if (!buf) {
return -ENOMEM;
}
pfree = buf;
count = *lenp;
if (count > MAX_PROC_SIZE) {
count = MAX_PROC_SIZE;
}
if (copy_from_user(buf, buffer, count)) {
kfree(pfree);
return -EFAULT;
}
while (buf) {
char *param, *value;
token = strsep(&buf, " \n");
if (token[0] == 0) {
continue;
}
param = strsep(&token, "=");
value = token;
if (!value || !param) {
kfree(pfree);
goto fail;
}
/*
* Parse netdev and FMR parameters.
*/
if (!strcmp(param, "netdev")) {
strlcpy(dev_name, value, 30);
dev = dev_get_by_name(&init_net, dev_name);
if (!dev) {
kfree(pfree);
goto fail;
}
netdev_valid = true;
continue;
}
if (!strcmp(param, "rule_type")) {
if (!sscanf(value, "%u", &rule_type)) {
kfree(pfree);
goto fail;
}
if ((rule_type !=NSS_CONNMGR_TUNIPIP6_RULE_BMR) &&
(rule_type != NSS_CONNMGR_TUNIPIP6_RULE_FMR)) {
kfree(pfree);
goto fail;
}
rule_type_valid = true;
continue;
}
if (!strcmp(param, "frag_id_update")) {
if (!sscanf(value, "%u", &frag_id)) {
kfree(pfree);
goto fail;
}
if (frag_id != 0 && frag_id != 1) {
kfree(pfree);
goto fail;
}
continue;
}
if (!strcmp(param, "ipv4_prefix")) {
strlcpy(ipv4_prefix_str, value, 30);
ret = in4_pton(ipv4_prefix_str, -1, (uint8_t *)&ipv4_prefix, -1, NULL);
if (ret != 1) {
kfree(pfree);
goto fail;
}
ipv4_prefix_valid = true;
continue;
}
if (!strcmp(param, "ipv4_prefix_len")) {
if (!sscanf(value, "%u", &ipv4_prefix_len)) {
kfree(pfree);
goto fail;
}
if (ipv4_prefix_len > MAX_IPV4_PREFIX_LEN) {
kfree(pfree);
goto fail;
}
ipv4_prefix_len_valid = true;
continue;
}
if (!strcmp(param, "ipv6_prefix")) {
strlcpy(ipv6_prefix_str, value, 100);
ret = in6_pton(ipv6_prefix_str, -1, (uint8_t *)&ipv6_prefix, -1, NULL);
if (ret != 1) {
kfree(pfree);
goto fail;
}
ipv6_prefix_valid = true;
continue;
}
if (!strcmp(param, "ipv6_prefix_len")) {
if (!sscanf(value, "%u", &ipv6_prefix_len)) {
kfree(pfree);
goto fail;
}
if (ipv6_prefix_len > MAX_IPV6_PREFIX_LEN) {
kfree(pfree);
goto fail;
}
ipv6_prefix_len_valid = true;
continue;
}
if (!strcmp(param, "ipv6_suffix")) {
strlcpy(ipv6_suffix_str, value, 100);
ret = in6_pton(ipv6_suffix_str, -1, (uint8_t *)&ipv6_suffix, -1, NULL);
if (ret != 1) {
kfree(pfree);
goto fail;
}
ipv6_suffix_valid = true;
continue;
}
if (!strcmp(param, "ipv6_suffix_len")) {
if (!sscanf(value, "%u", &ipv6_suffix_len)) {
kfree(pfree);
goto fail;
}
if (ipv6_suffix_len > MAX_IPV6_PREFIX_LEN) {
kfree(pfree);
goto fail;
}
ipv6_suffix_len_valid = true;
continue;
}
if (!strcmp(param, "ea_len")) {
if (!sscanf(value, "%u", &ea_len)) {
kfree(pfree);
goto fail;
}
ea_len_valid = true;
continue;
}
if (!strcmp(param, "psid_offset")) {
if (!sscanf(value, "%u", &psid_offset)) {
kfree(pfree);
goto fail;
}
if (psid_offset> MAX_PSID_OFFSET_LEN) {
kfree(pfree);
goto fail;
}
psid_offset_valid = true;
continue;
}
}
kfree(pfree);
/*
* Netdev param is not needed only for frag_id command
*/
if (!netdev_valid && mode != NSS_TUNIPIP6_SYSCTL_FRAG_ID) {
goto fail;
}
switch(mode) {
case NSS_TUNIPIP6_SYSCTL_DEL_MAPRULE:
if (!rule_type_valid ) {
goto fail;
}
/*
* BMR delete will follow this and FMR delete will fall through.
*/
if (rule_type == NSS_CONNMGR_TUNIPIP6_RULE_BMR) {
if ((ipv6_prefix_valid || ipv6_prefix_len_valid || ipv6_suffix_valid ||
ipv4_prefix_valid || ipv4_prefix_len_valid || ea_len_valid ||
psid_offset_valid)) {
goto fail;
}
mrcfg.rule_type = rule_type;
status = nss_connmgr_tunipip6_del_maprule(dev, &mrcfg);
if (status == NSS_CONNMGR_TUNIPIP6_SUCCESS) {
pr_info("Map Rule delete success for netdev: %s\n", dev->name);
} else {
pr_info("Map Rule delete failure for netdev: %s\n", dev->name);
}
break;
}
#if __has_attribute(__fallthrough__)
__attribute__((__fallthrough__));
#endif
case NSS_TUNIPIP6_SYSCTL_ADD_MAPRULE:
if (!(rule_type_valid && ipv6_prefix_valid && ipv6_prefix_len_valid && ipv6_suffix_valid &&
ipv4_prefix_valid && ipv4_prefix_len_valid && ea_len_valid &&
psid_offset_valid)) {
goto fail;
}
mrcfg.rule_type = rule_type;
mrcfg.ipv6_prefix[0] = ntohl(ipv6_prefix[0]);
mrcfg.ipv6_prefix[1] = ntohl(ipv6_prefix[1]);
mrcfg.ipv6_prefix[2] = ntohl(ipv6_prefix[2]);
mrcfg.ipv6_prefix[3] = ntohl(ipv6_prefix[3]);
mrcfg.ipv6_prefix_len = ipv6_prefix_len;
mrcfg.ipv4_prefix = ntohl(ipv4_prefix);
mrcfg.ipv4_prefix_len = ipv4_prefix_len;
mrcfg.ipv6_suffix[0] = ntohl(ipv6_suffix[0]);
mrcfg.ipv6_suffix[1] = ntohl(ipv6_suffix[1]);
mrcfg.ipv6_suffix[2] = ntohl(ipv6_suffix[2]);
mrcfg.ipv6_suffix[3] = ntohl(ipv6_suffix[3]);
mrcfg.ipv6_suffix_len = ipv6_suffix_len;
mrcfg.ea_len = ea_len;
mrcfg.psid_offset = psid_offset;
if (mode == NSS_TUNIPIP6_SYSCTL_ADD_MAPRULE) {
status = nss_connmgr_tunipip6_add_maprule(dev, &mrcfg);
if (status == NSS_CONNMGR_TUNIPIP6_SUCCESS) {
pr_info("Map Rule create success for netdev: %s\n", dev->name);
} else {
pr_info("Map Rule create failure for netdev: %s\n", dev->name);
}
} else {
status = nss_connmgr_tunipip6_del_maprule(dev, &mrcfg);
if (status == NSS_CONNMGR_TUNIPIP6_SUCCESS) {
pr_info("Map Rule delete success for netdev: %s\n", dev->name);
} else {
pr_info("Map Rule delete failure for netdev: %s\n", dev->name);
}
}
break;
case NSS_TUNIPIP6_SYSCTL_FLUSH_FMR_RULE:
status = nss_connmgr_tunipip6_flush_fmr_rule(dev);
if (status == NSS_CONNMGR_TUNIPIP6_SUCCESS) {
pr_info("Map Rule flush success for netdev: %s\n", dev->name);
} else {
pr_info("Map Rule flush failed for netdev: %s\n", dev->name);
}
break;
case NSS_TUNIPIP6_SYSCTL_FRAG_ID:
if ((netdev_valid || rule_type_valid || ipv6_prefix_valid || ipv6_prefix_len_valid || ipv6_suffix_valid ||
ipv4_prefix_valid || ipv4_prefix_len_valid || ea_len_valid ||
psid_offset_valid)) {
goto fail;
}
if (frag_id) {
frag_id_update = true;
pr_info("Frag Id enabled for all tunnels.\n");
} else {
frag_id_update = false;
pr_info("Frag Id disabled for all tunnels.\n");
}
return 0;
}
dev_put(dev);
return 0;
fail:
if (dev) {
dev_put(dev);
}
pr_info("Wrong input, check help. (cat /proc/sys/dev/nss/ipip6/help)\n");
return 0;
}
static int nss_tunipip6_cmd_procfs_add_maprule(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
{
return nss_tunipip6_data_parser(ctl, write, buffer, lenp, ppos, NSS_TUNIPIP6_SYSCTL_ADD_MAPRULE);
}
static int nss_tunipip6_cmd_procfs_del_maprule(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
{
return nss_tunipip6_data_parser(ctl, write, buffer, lenp, ppos, NSS_TUNIPIP6_SYSCTL_DEL_MAPRULE);
}
static int nss_tunipip6_cmd_procfs_flush_fmr_rule(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
{
return nss_tunipip6_data_parser(ctl, write, buffer, lenp, ppos, NSS_TUNIPIP6_SYSCTL_FLUSH_FMR_RULE);
}
static int nss_tunipip6_cmd_procfs_enable_frag_id(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
{
return nss_tunipip6_data_parser(ctl, write, buffer, lenp, ppos, NSS_TUNIPIP6_SYSCTL_FRAG_ID);
}
static int nss_tunipip6_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/ipip6/help) \n\
1. To add maprule(rule_type=1(BMR)/2(FMR)):\n\
echo dev=<map-mape/MAP-E netdevice> rule_type=<1/2> ipv6_prefix=<XXXX::XXXX> ipv6_prefix_len=<XX> ipv4_prefix=<X.X.X.X> ipv4_prefix_len=<XX> \n\
ipv6_suffix=<XXXX::XXXX> ipv6_sufffix_len=<XX> ea_len=<XX> psid_offset=<XX> > add_map_rule \n\
2. a. To delete maprule(FMR):\n\
echo dev=<map-mape/MAP-E netdevice> rule_type=<2> ipv6_prefix=<XXXX::XXXX> ipv6_prefix_len=<XX> ipv4_prefix=<X.X.X.X> ipv4_prefix_len=<XX> \n\
ipv6_suffix=<XXXX::XXXX> ipv6_sufffix_len=<XX> ea_len=<XX> psid_offset=<XX> > remove_map_rule \n\
b. To delete maprule(BMR):\n\
echo dev=<map-mape/MAP-E netdevice> rule_type=<1> > remove_map_rule\n\
3. To flush FMR entries:\n\
echo dev=<map-mape/MAP-E netdevice> > flush_fmr_rule\n");
pr_info("\t\t\t4. To enable/disable frag id: \n\
echo frag_id_update=<0/1> > frag_id \n\
=====end of help=====\n");
*lenp = 0;
return ret;
}
static struct ctl_table nss_tunipip6_table[] = {
{
.procname = "add_map_rule",
.data = &nss_tunipip6_data,
.maxlen = sizeof(nss_tunipip6_data),
.mode = 0644,
.proc_handler = &nss_tunipip6_cmd_procfs_add_maprule,
},
{
.procname = "remove_map_rule",
.data = &nss_tunipip6_data,
.maxlen = sizeof(nss_tunipip6_data),
.mode = 0644,
.proc_handler = &nss_tunipip6_cmd_procfs_del_maprule,
},
{
.procname = "flush_fmr_rule",
.data = &nss_tunipip6_data,
.maxlen = sizeof(nss_tunipip6_data),
.mode = 0644,
.proc_handler = &nss_tunipip6_cmd_procfs_flush_fmr_rule,
},
{
.procname = "frag_id",
.data = &nss_tunipip6_data,
.maxlen = sizeof(nss_tunipip6_data),
.mode = 0644,
.proc_handler = &nss_tunipip6_cmd_procfs_enable_frag_id,
},
{
.procname = "help",
.data = &nss_tunipip6_data,
.maxlen = sizeof(nss_tunipip6_data),
.mode = 0400,
.proc_handler = &nss_tunipip6_cmd_procfs_read_help,
},
{ }
};
static struct ctl_table nss_tunipip6_root_dir[] = {
{
.procname = "ipip6",
.mode = 0555,
.child = nss_tunipip6_table,
},
{ }
};
static struct ctl_table nss_tunipip6_nss_root_dir[] = {
{
.procname = "nss",
.mode = 0555,
.child = nss_tunipip6_root_dir,
},
{ }
};
static struct ctl_table nss_tunipip6_root[] = {
{
.procname = "dev",
.mode = 0555,
.child = nss_tunipip6_nss_root_dir,
},
{ }
};
static struct ctl_table_header *nss_tunipip6_ctl_header;
/*
* nss_tunipip6_sysctl_register()
* Register command line interface for tunipip6.
*/
bool nss_tunipip6_sysctl_register(void) {
nss_tunipip6_ctl_header = register_sysctl_table(nss_tunipip6_root);
if (!nss_tunipip6_ctl_header) {
return false;
}
return true;
}
/*
* nss_tunipip6_sysctl_unregister()
* Unregister command line interface for tunipip6.
*/
void nss_tunipip6_sysctl_unregister(void) {
if (nss_tunipip6_ctl_header) {
unregister_sysctl_table(nss_tunipip6_ctl_header);
}
}