blob: dd6465323e91ee4b33ad291fc629b7414b8dafcb [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2022, Qualcomm Innovation Center, Inc. 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/module.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/debugfs.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/etherdevice.h>
#include <linux/inet.h>
#include "exports/ecm_sfe_common_public.h"
/*
* Global WAN interface name parameter.
*/
char wan_name[IFNAMSIZ];
int wan_name_len;
/*
* DebugFS entry object.
*/
static struct dentry *ecm_sfe_l2_dentry;
/*
* Policy rule directions.
*/
enum ecm_sfe_l2_policy_rule_dir {
ECM_SFE_L2_POLICY_RULE_EGRESS = 1,
ECM_SFE_L2_POLICY_RULE_INGRESS,
ECM_SFE_L2_POLICY_RULE_EGRESS_INGRESS,
};
/*
* Policy rule commands.
*/
enum ecm_sfe_l2_policy_rule_cmd {
ECM_SFE_L2_POLICY_RULE_ADD = 1,
ECM_SFE_L2_POLICY_RULE_DEL,
ECM_SFE_L2_POLICY_RULE_FLUSH_ALL
};
/*
* ECM tuple directions.
*/
enum ecm_sfe_l2_tuple_dir {
ECM_SFE_L2_TUPLE_DIR_ORIGINAL,
ECM_SFE_L2_TUPLE_DIR_REPLY,
};
/*
* Defunct by 5-tuple command option types.
*/
enum ecm_sfe_l2_defunct_by_5tuple_options {
ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_IP_VERSION,
ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_SIP,
ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_SPORT,
ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_DIP,
ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_DPORT,
ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_PROTOCOL,
ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_MAX,
};
/*
* Policy rule structure
*/
struct ecm_sfe_l2_policy_rule {
struct list_head list;
int protocol;
int src_port;
int dest_port;
uint32_t src_addr[4];
uint32_t dest_addr[4];
int ip_ver;
enum ecm_sfe_l2_policy_rule_dir direction;
};
LIST_HEAD(ecm_sfe_l2_policy_rules);
DEFINE_SPINLOCK(ecm_sfe_l2_policy_rules_lock);
/*
* ecm_sfe_l2_policy_rule_find()
* Finds a policy rule with the given parameters.
*/
static struct ecm_sfe_l2_policy_rule *ecm_sfe_l2_policy_rule_find(int ip_ver, uint32_t *sip_addr, int sport,
uint32_t *dip_addr, int dport,
int protocol)
{
struct ecm_sfe_l2_policy_rule *rule = NULL;
list_for_each_entry(rule , &ecm_sfe_l2_policy_rules, list) {
if (rule->ip_ver != ip_ver)
continue;
if (rule->protocol && (rule->protocol != protocol))
continue;
if (rule->dest_port && (rule->dest_port != dport))
continue;
if (rule->src_port && (rule->src_port != sport))
continue;
if (ip_ver == 4) {
if (rule->dest_addr[0] && (rule->dest_addr[0] != dip_addr[0]))
continue;
} else {
if (rule->dest_addr[0] && memcmp(rule->dest_addr, dip_addr, sizeof(uint32_t) * 4))
continue;
}
if (ip_ver == 4) {
if (rule->src_addr[0] && (rule->src_addr[0] != sip_addr[0]))
continue;
} else {
if (rule->src_addr[0] && memcmp(rule->src_addr, sip_addr, sizeof(uint32_t) * 4))
continue;
}
return rule;
}
return NULL;
}
/*
* ecm_sfe_l2_connection_check_with_policy_rules()
* Checks the ECM tuple with the policy rules in our rules list and
* set the L2 acceleration accordingly, if there is a match.
*/
static uint32_t ecm_sfe_l2_connection_check_with_policy_rules(struct ecm_sfe_common_tuple *tuple, enum ecm_sfe_l2_tuple_dir tuple_dir)
{
struct ecm_sfe_l2_policy_rule *rule = NULL;
enum ecm_sfe_l2_policy_rule_dir direction;
uint32_t l2_accel_bits = (ECM_SFE_COMMON_FLOW_L2_ACCEL_ALLOWED | ECM_SFE_COMMON_RETURN_L2_ACCEL_ALLOWED);
if (tuple_dir == ECM_SFE_L2_TUPLE_DIR_ORIGINAL) {
spin_lock_bh(&ecm_sfe_l2_policy_rules_lock);
rule = ecm_sfe_l2_policy_rule_find(tuple->ip_ver, tuple->src_addr, tuple->src_port,
tuple->dest_addr, tuple->dest_port, tuple->protocol);
if (!rule) {
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
pr_warn("No rule with this tuple\n");
goto done;
}
direction = rule->direction;
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
if (direction == ECM_SFE_L2_POLICY_RULE_EGRESS) {
pr_debug("flow side should be L3 interface\n");
l2_accel_bits &= ~ECM_SFE_COMMON_FLOW_L2_ACCEL_ALLOWED;
} else if (direction == ECM_SFE_L2_POLICY_RULE_INGRESS) {
pr_debug("return side should be L3 interface\n");
l2_accel_bits &= ~ECM_SFE_COMMON_RETURN_L2_ACCEL_ALLOWED;
}
} else if (tuple_dir == ECM_SFE_L2_TUPLE_DIR_REPLY) {
spin_lock_bh(&ecm_sfe_l2_policy_rules_lock);
rule = ecm_sfe_l2_policy_rule_find(tuple->ip_ver, tuple->dest_addr, tuple->dest_port,
tuple->src_addr, tuple->src_port, tuple->protocol);
if (!rule) {
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
pr_warn("No rule with this tuple\n");
goto done;
}
direction = rule->direction;
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
if (direction == ECM_SFE_L2_POLICY_RULE_EGRESS) {
pr_debug("return side should be L3 interface\n");
l2_accel_bits &= ~ECM_SFE_COMMON_RETURN_L2_ACCEL_ALLOWED;
} else if (direction == ECM_SFE_L2_POLICY_RULE_INGRESS) {
pr_debug("flow side should be L3 interface\n");
l2_accel_bits &= ~ECM_SFE_COMMON_FLOW_L2_ACCEL_ALLOWED;
}
} else {
pr_err("unknow tuple_dir: %d\n", tuple_dir);
goto done;
}
if (direction == ECM_SFE_L2_POLICY_RULE_EGRESS_INGRESS) {
pr_debug("both sides should be L3 interface\n");
l2_accel_bits &= ~ECM_SFE_COMMON_FLOW_L2_ACCEL_ALLOWED;
l2_accel_bits &= ~ECM_SFE_COMMON_RETURN_L2_ACCEL_ALLOWED;
}
done:
return l2_accel_bits;
}
/*
* ecm_sfe_l2_accel_check_callback()
* L2 acceleration check function callback.
*/
uint32_t ecm_sfe_l2_accel_check_callback(struct ecm_sfe_common_tuple *tuple)
{
struct net_device *flow_dev;
struct net_device *return_dev;
struct net_device *wan_dev;
uint32_t l2_accel_bits = (ECM_SFE_COMMON_FLOW_L2_ACCEL_ALLOWED | ECM_SFE_COMMON_RETURN_L2_ACCEL_ALLOWED);
if (strlen(wan_name) == 0) {
pr_debug("WAN interface is not set in the debugfs\n");
goto done;
}
wan_dev = dev_get_by_name(&init_net, wan_name);
if (!wan_dev) {
pr_debug("WAN interface: %s couldn't be found\n", wan_name);
goto done;
}
flow_dev = dev_get_by_index(&init_net, tuple->src_ifindex);
if (!flow_dev) {
pr_debug("flow netdevice couldn't be found with index: %d\n", tuple->src_ifindex);
dev_put(wan_dev);
goto done;
}
return_dev = dev_get_by_index(&init_net, tuple->dest_ifindex);
if (!return_dev) {
pr_debug("return netdevice couldn't be found with index: %d\n", tuple->dest_ifindex);
dev_put(wan_dev);
dev_put(flow_dev);
goto done;
}
if (wan_dev == return_dev) {
/*
* Check the tuple with the policy rules in the ORIGINAL direction of the tuple.
*/
l2_accel_bits = ecm_sfe_l2_connection_check_with_policy_rules(tuple, ECM_SFE_L2_TUPLE_DIR_ORIGINAL);
} else if (wan_dev == flow_dev) {
/*
* Check the tuple with the policy rules in the REPLY direction of the tuple.
*/
l2_accel_bits = ecm_sfe_l2_connection_check_with_policy_rules(tuple, ECM_SFE_L2_TUPLE_DIR_REPLY);
}
dev_put(wan_dev);
dev_put(flow_dev);
dev_put(return_dev);
done:
return l2_accel_bits;
}
/*
* ecm_sfe_l2_flush_policy_rules()
* Flushes all the policy rules.
*/
static void ecm_sfe_l2_flush_policy_rules(void)
{
struct ecm_sfe_l2_policy_rule *rule;
struct ecm_sfe_l2_policy_rule *tmp;
spin_lock_bh(&ecm_sfe_l2_policy_rules_lock);
list_for_each_entry_safe(rule , tmp, &ecm_sfe_l2_policy_rules, list) {
list_del(&rule->list);
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
kfree(rule);
spin_lock_bh(&ecm_sfe_l2_policy_rules_lock);
}
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
}
/*
* ecm_sfe_l2_delete_policy_rule()
* Deletes a policy rule with the given parameters.
*/
static bool ecm_sfe_l2_delete_policy_rule(int ip_ver, uint32_t *sip_addr, int sport, uint32_t *dip_addr, int dport, int protocol)
{
struct ecm_sfe_l2_policy_rule *rule;
spin_lock_bh(&ecm_sfe_l2_policy_rules_lock);
rule = ecm_sfe_l2_policy_rule_find(ip_ver, sip_addr, sport, dip_addr, dport, protocol);
if (!rule) {
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
pr_warn("rule cannot be found in the list\n");
return false;
}
list_del(&rule->list);
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
kfree(rule);
pr_info("rule deleted\n");
return true;
}
/*
* ecm_sfe_l2_add_policy_rule()
* Adds a policy rule with the given parameters.
*/
static bool ecm_sfe_l2_add_policy_rule(int ip_ver, uint32_t *sip_addr, int sport, uint32_t *dip_addr, int dport, int protocol, enum ecm_sfe_l2_policy_rule_dir direction)
{
struct ecm_sfe_l2_policy_rule *rule;
spin_lock_bh(&ecm_sfe_l2_policy_rules_lock);
rule = ecm_sfe_l2_policy_rule_find(ip_ver, sip_addr, sport, dip_addr, dport, protocol);
if (rule) {
if (rule->direction != direction) {
pr_info("Update direction of the rule from %d to %d\n", rule->direction, direction);
rule->direction = direction;
}
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
pr_warn("rule is already present\n");
return true;
}
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
rule = kzalloc(sizeof(struct ecm_sfe_l2_policy_rule), GFP_ATOMIC);
if (!rule) {
pr_warn("alloc failed for new rule\n");
return false;
}
rule->ip_ver = ip_ver;
rule->protocol = protocol;
rule->src_port = sport;
rule->dest_port = dport;
memcpy(rule->src_addr, sip_addr, sizeof(uint32_t) * 4);
memcpy(rule->dest_addr, dip_addr, sizeof(uint32_t) * 4);
rule->direction = direction;
INIT_LIST_HEAD(&rule->list);
spin_lock_bh(&ecm_sfe_l2_policy_rules_lock);
list_add(&rule->list, &ecm_sfe_l2_policy_rules);
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
pr_info("rule added\n");
return true;
}
/*
* ecm_sfe_l2_policy_rule_write()
* Adds a policy rule to the rule table.
*
* Policy rule must include cmd, ip_ver and direction. It can also include src/dest IP and ports, protocol.
* cmd and ip_ver MUST be the first 2 options in the command.
*/
static ssize_t ecm_sfe_l2_policy_rule_write(struct file *file,
const char __user *user_buf, size_t count, loff_t *offset)
{
char *cmd_buf;
char *fields;
char *token;
char *option, *value;
int cmd = 0; /* must be present in the rule */
int ip_ver = 0; /* must be present in the rule */
uint32_t sip_addr[4] = {0};
uint32_t dip_addr[4] = {0};
int sport = 0;
int dport = 0;
int protocol = 0;
int direction = 0; /* must be present in the rule */
/*
* Command is formed as:
* echo "cmd=1 ip_ver=4 dport=443 protocol=6 direction=1" > /sys/kernel/debug/ecm_sfe_l2/policy_rules
*
* cmd: 1 is to add, 2 is to delete a rule.
* direction: 1 is egress, 2 is ingress, 3 is both
*/
cmd_buf = kzalloc(count + 1, GFP_ATOMIC);
if (!cmd_buf) {
pr_warn("unable to allocate memory for cmd buffer\n");
return -ENOMEM;
}
count = simple_write_to_buffer(cmd_buf, count, offset, user_buf, count);
/*
* Split the buffer into tokens
*/
fields = cmd_buf;
while ((token = strsep(&fields, " "))) {
pr_info("\ntoken: %s\n", token);
option = strsep(&token, "=");
value = token;
pr_info("\t\toption: %s\n", option);
pr_info("\t\tvalue: %s\n", value);
if (!strcmp(option, "cmd")) {
if (sscanf(value, "%d", &cmd)) {
if (cmd != ECM_SFE_L2_POLICY_RULE_ADD && cmd != ECM_SFE_L2_POLICY_RULE_DEL &&
cmd != ECM_SFE_L2_POLICY_RULE_FLUSH_ALL) {
pr_err("invalid cmd value: %d\n", cmd);
goto fail;
}
continue;
}
pr_warn("cannot read value\n");
goto fail;
}
if (!strcmp(option, "ip_ver")) {
if (sscanf(value, "%d", &ip_ver)) {
if (ip_ver != 4 && ip_ver != 6) {
pr_err("invalid ip_ver: %d\n", ip_ver);
goto fail;
}
continue;
}
pr_warn("cannot read value\n");
goto fail;
}
if (!strcmp(option, "protocol")) {
if (sscanf(value, "%d", &protocol)) {
continue;
}
pr_warn("cannot read value\n");
goto fail;
}
if (!strcmp(option, "sport")) {
if (sscanf(value, "%d", &sport)) {
continue;
}
pr_warn("cannot read value\n");
goto fail;
}
if (!strcmp(option, "dport")) {
if (sscanf(value, "%d", &dport)) {
continue;
}
pr_warn("cannot read value\n");
goto fail;
}
if (!strcmp(option, "direction")) {
if (cmd == ECM_SFE_L2_POLICY_RULE_DEL) {
pr_err("direction is not allowed in delete command\n");
goto fail;
}
if (sscanf(value, "%d", &direction)) {
if (direction != ECM_SFE_L2_POLICY_RULE_EGRESS
&& direction != ECM_SFE_L2_POLICY_RULE_INGRESS
&& direction != ECM_SFE_L2_POLICY_RULE_EGRESS_INGRESS) {
pr_err("invalid direction: %d\n", direction);
goto fail;
}
continue;
}
pr_warn("cannot read value\n");
goto fail;
}
if (!strcmp(option, "sip")) {
if (ip_ver == 4) {
if (!in4_pton(value, -1, (uint8_t *)&sip_addr[0], -1, NULL)) {
pr_err("invalid source IP V4 value: %s\n", value);
goto fail;
}
} else if (ip_ver ==6) {
if (!in6_pton(value, -1, (uint8_t *)sip_addr, -1, NULL)) {
pr_err("invalid source IP V6 value: %s\n", value);
goto fail;
}
} else {
pr_err("ip_ver hasn't been set yet\n");
goto fail;
}
continue;
}
if (!strcmp(option, "dip")) {
if (ip_ver == 4) {
if (!in4_pton(value, -1, (uint8_t *)&dip_addr[0], -1, NULL)) {
pr_err("invalid destination IP V4 value: %s\n", value);
goto fail;
}
} else if (ip_ver == 6) {
if (!in6_pton(value, -1, (uint8_t *)dip_addr, -1, NULL)) {
pr_err("invalid destination IP V6 value: %s\n", value);
goto fail;
}
} else {
pr_err("ip_ver hasn't been set yet\n");
goto fail;
}
continue;
}
pr_warn("unrecognized option: %s\n", option);
goto fail;
}
kfree(cmd_buf);
if (cmd == ECM_SFE_L2_POLICY_RULE_ADD) {
if (!ecm_sfe_l2_add_policy_rule(ip_ver, sip_addr, sport, dip_addr, dport, protocol, direction)) {
pr_err("Add policy rule failed\n");
return -ENOMEM;
}
} else if (cmd == ECM_SFE_L2_POLICY_RULE_DEL) {
if (!ecm_sfe_l2_delete_policy_rule(ip_ver, sip_addr, sport, dip_addr, dport, protocol)) {
pr_err("Delete policy rule failed\n");
return -ENOMEM;
}
} else if (cmd == ECM_SFE_L2_POLICY_RULE_FLUSH_ALL) {
ecm_sfe_l2_flush_policy_rules();
}
return count;
fail:
kfree(cmd_buf);
return -EINVAL;
}
/*
* ecm_sfe_l2_policy_rule_seq_show()
*/
static int ecm_sfe_l2_policy_rule_seq_show(struct seq_file *m, void *v)
{
struct ecm_sfe_l2_policy_rule *rule;
rule = list_entry(v, struct ecm_sfe_l2_policy_rule, list);
if (rule->ip_ver == 4) {
seq_printf(m, "ip_ver: %d"
"\tprotocol: %d"
"\tsip_addr: %pI4"
"\tdip_addr: %pI4"
"\tsport: %d"
"\tdport: %d"
"\tdirection: %d\n",
rule->ip_ver,
rule->protocol,
&rule->src_addr[0],
&rule->dest_addr[0],
rule->src_port,
rule->dest_port,
rule->direction);
} else {
struct in6_addr saddr;
struct in6_addr daddr;
memcpy(&saddr.s6_addr32, rule->src_addr, sizeof(uint32_t) * 4);
memcpy(&daddr.s6_addr32, rule->dest_addr, sizeof(uint32_t) * 4);
seq_printf(m, "ip_ver: %d"
"\tprotocol: %d"
"\tsip_addr: %pI6"
"\tdip_addr: %pI6"
"\tsport: %d"
"\tdport: %d"
"\tdirection: %d\n",
rule->ip_ver,
rule->protocol,
&saddr,
&daddr,
rule->src_port,
rule->dest_port,
rule->direction);
}
return 0;
}
/*
* ecm_sfe_l2_policy_rule_seq_stop()
*/
static void ecm_sfe_l2_policy_rule_seq_stop(struct seq_file *p, void *v)
{
spin_unlock_bh(&ecm_sfe_l2_policy_rules_lock);
}
/*
* ecm_sfe_l2_policy_rule_seq_next()
*/
static void *ecm_sfe_l2_policy_rule_seq_next(struct seq_file *p, void *v,
loff_t *pos)
{
return seq_list_next(v, &ecm_sfe_l2_policy_rules, pos);
}
/*
* ecm_sfe_l2_policy_rule_seq_start()
*/
static void *ecm_sfe_l2_policy_rule_seq_start(struct seq_file *m, loff_t *_pos)
{
spin_lock_bh(&ecm_sfe_l2_policy_rules_lock);
return seq_list_start(&ecm_sfe_l2_policy_rules, *_pos);
}
static const struct seq_operations ecm_sfe_l2_policy_rule_seq_ops = {
.start = ecm_sfe_l2_policy_rule_seq_start,
.next = ecm_sfe_l2_policy_rule_seq_next,
.stop = ecm_sfe_l2_policy_rule_seq_stop,
.show = ecm_sfe_l2_policy_rule_seq_show,
};
/*
* ecm_sfe_l2_policy_rule_open()
*/
static int ecm_sfe_l2_policy_rule_open(struct inode *inode, struct file *file)
{
return seq_open(file, &ecm_sfe_l2_policy_rule_seq_ops);
}
/*
* File operations for policy rules add/delete/list operations.
*/
static const struct file_operations ecm_sfe_l2_policy_rule_fops = {
.owner = THIS_MODULE,
.open = ecm_sfe_l2_policy_rule_open,
.write = ecm_sfe_l2_policy_rule_write,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
/*
* ecm_sfe_l2_defunct_by_5tuple_write()
* Writes the defunct by 5-tuple command to the debugfs node.
*/
static ssize_t ecm_sfe_l2_defunct_by_5tuple_write(struct file *f, const char *user_buf,
size_t count, loff_t *offset)
{
int ret = -EINVAL;
char *cmd_buf;
int field_count;
char *fields_ptr;
char *fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_MAX];
char *option, *value;
int ip_ver;
uint32_t sip_addr_v4;
uint32_t dip_addr_v4;
struct in6_addr sip_addr_v6;
struct in6_addr dip_addr_v6;
int sport, dport;
int protocol;
bool defunct_result;
/*
* Command is formed as for IPv4 and IPv6 5-tuples as below respectively.
*
* echo "ip_ver=4 sip=192.168.1.100 sport=443 dip=192.168.2.100 dport=1000 protocol=6" > /sys/kernel/debug/ecm_sfe_l2/defunct_by_5tuple
* echo “ip_ver=6 sip=2aaa::100 sport=443 dip=3bbb::200 dport=1000 protocol=6” > /sys/kernel/debug/ecm_sfe_l2/defunct_by_5tuple
*
* The order of the options MUST be as above and it MUST contain all the 5-tuple fields and the ip_ver.
*/
cmd_buf = kzalloc(count + 1, GFP_ATOMIC);
if (!cmd_buf) {
pr_warn("unable to allocate memory for cmd buffer\n");
return -ENOMEM;
}
count = simple_write_to_buffer(cmd_buf, count, offset, user_buf, count);
/*
* Split the buffer into its fields
*/
field_count = 0;
fields_ptr = cmd_buf;
fields[field_count] = strsep(&fields_ptr, " ");
while (fields[field_count] != NULL) {
pr_info("Field %d: %s\n", field_count, fields[field_count]);
field_count++;
if (field_count == ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_MAX)
break;
fields[field_count] = strsep(&fields_ptr, " ");
}
if (field_count != ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_MAX) {
kfree(cmd_buf);
pr_err("Invalid field count %d\n", field_count);
return -EINVAL;
}
/*
* IP version (ip_ver) field validation.
*/
option = strsep(&fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_IP_VERSION], "=");
if (!option || strcmp(option, "ip_ver")) {
pr_err("invalid IP version option name: %s\n", option);
goto fail;
}
value = fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_IP_VERSION];
if (!sscanf(value, "%d", &ip_ver)) {
pr_err("Unable to read IP version value %s\n", value);
goto fail;
}
if (ip_ver != 4 && ip_ver != 6) {
pr_err("invalid IP version: %d\n", ip_ver);
goto fail;
}
/*
* Source IP (sip) field validation.
*/
option = strsep(&fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_SIP], "=");
if (!option || strcmp(option, "sip")) {
pr_err("invalid source IP option name: %s\n", option);
goto fail;
}
value = fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_SIP];
if (ip_ver == 4) {
if (!in4_pton(value, -1, (uint8_t *)&sip_addr_v4, -1, NULL)) {
pr_err("invalid source IP V4 value: %s\n", value);
goto fail;
}
} else {
if (!in6_pton(value, -1, (uint8_t *)sip_addr_v6.s6_addr, -1, NULL)) {
pr_err("invalid source IP V6 value: %s\n", value);
goto fail;
}
}
/*
* Source port (sport) field validadtion.
*/
option = strsep(&fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_SPORT], "=");
if (!option || strcmp(option, "sport")) {
pr_err("invalid source port option name: %s\n", option);
goto fail;
}
value = fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_SPORT];
if (!sscanf(value, "%d", &sport)) {
pr_err("Unable to read source port value %s\n", value);
goto fail;
}
/*
* Destination IP (dip) field validation.
*/
option = strsep(&fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_DIP], "=");
if (!option || strcmp(option, "dip")) {
pr_err("invalid destination IP option name: %s\n", option);
goto fail;
}
value = fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_DIP];
if (ip_ver == 4) {
if (!in4_pton(value, -1, (uint8_t *)&dip_addr_v4, -1, NULL)) {
pr_err("invalid destination IP V4 value: %s\n", value);
goto fail;
}
} else {
if (!in6_pton(value, -1, (uint8_t *)dip_addr_v6.s6_addr, -1, NULL)) {
pr_err("invalid destination IP V6 value: %s\n", value);
goto fail;
}
}
/*
* Destination port (dport) field validadtion.
*/
option = strsep(&fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_DPORT], "=");
if (!option || strcmp(option, "dport")) {
pr_err("invalid destination port option name: %s\n", option);
goto fail;
}
value = fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_DPORT];
if (!sscanf(value, "%d", &dport)) {
pr_err("Unable to read destination port value %s\n", value);
goto fail;
}
/*
* Protocol field validadtion.
*/
option = strsep(&fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_PROTOCOL], "=");
if (!option || strcmp(option, "protocol")) {
pr_err("invalid protocol option name: %s\n", option);
goto fail;
}
value = fields[ECM_SFE_L2_DEFUNCT_BY_5TUPLE_OPTION_PROTOCOL];
if (!sscanf(value, "%d", &protocol)) {
pr_err("Unable to read protocol value %s\n", value);
goto fail;
}
/*
* Call 5-tuple defunct functions.
*/
if (ip_ver == 4) {
pr_debug("sip: %pI4 sport: %d dip: %pI4 dport: %d protocol: %d\n", &sip_addr_v4, sport, &dip_addr_v4, dport, protocol);
defunct_result = ecm_sfe_common_defunct_ipv4_connection(sip_addr_v4, htons(sport), dip_addr_v4, htons(dport), protocol);
} else {
pr_debug("sip: %pI6 sport: %d dip: %pI6 dport: %d protocol: %d\n", &sip_addr_v6, sport, &dip_addr_v6, dport, protocol);
defunct_result = ecm_sfe_common_defunct_ipv6_connection(&sip_addr_v6, htons(sport), &dip_addr_v6, htons(dport), protocol);
}
if (!defunct_result) {
pr_warn("No connection found with this 5-tuple\n");
}
ret = count;
fail:
kfree(cmd_buf);
return ret;
}
/*
* File operations for defunct by 5-tuple operations.
*/
static struct file_operations ecm_sfe_l2_defunct_by_5tuple_fops = {
.owner = THIS_MODULE,
.write = ecm_sfe_l2_defunct_by_5tuple_write,
};
/*
* ecm_sfe_l2_defunct_by_port_write()
* Writes the defunct by port command to the debugfs node.
*/
static ssize_t ecm_sfe_l2_defunct_by_port_write(struct file *f, const char *user_buf,
size_t count, loff_t *offset)
{
char *cmd_buf;
char *fields;
char *option, *value;
int port;
int direction;
/*
* Command is formed as:
*
* echo “sport=443” > /sys/kernel/debug/ecm_sfe_l2/defunct_by_port
* echo “dport=443” > /sys/kernel/debug/ecm_sfe_l2/defunct_by_port
*/
cmd_buf = kzalloc(count + 1, GFP_ATOMIC);
if (!cmd_buf) {
pr_warn("unable to allocate memory for cmd buffer\n");
return -ENOMEM;
}
count = simple_write_to_buffer(cmd_buf, count, offset, user_buf, count);
/*
* Split the buffer into its fields
*/
fields = cmd_buf;
option = strsep(&fields, "=");
if (!strcmp(option, "sport")) {
direction = 0;
} else if (!strcmp(option, "dport")) {
direction = 1;
} else {
pr_err("invalid option name: %s\n", option);
kfree(cmd_buf);
return -EINVAL;
}
value = fields;
if (!sscanf(value, "%d", &port)) {
pr_err("Unable to read port value %s\n", value);
kfree(cmd_buf);
return -EINVAL;
}
pr_debug("option: %s value: %d\n", option, port);
kfree(cmd_buf);
/*
* Call port based defunct function.
*/
ecm_sfe_common_defunct_by_port(port, direction, wan_name);
return count;
}
/*
* File operations for defunct by port operations.
*/
static struct file_operations ecm_sfe_l2_defunct_by_port_fops = {
.owner = THIS_MODULE,
.write = ecm_sfe_l2_defunct_by_port_write,
};
/*
* ecm_sfe_l2_defunct_by_protocol_write()
* Writes the defunct by protocol command to the debugfs node.
*/
static ssize_t ecm_sfe_l2_defunct_by_protocol_write(struct file *f, const char *user_buf,
size_t count, loff_t *offset)
{
char *cmd_buf;
char *fields;
char *option, *value;
int protocol;
/*
* Command is formed as:
*
* echo “protocol=6” > /sys/kernel/debug/ecm_sfe_l2/defunct_by_protocol
*/
cmd_buf = kzalloc(count + 1, GFP_ATOMIC);
if (!cmd_buf) {
pr_warn("unable to allocate memory for cmd buffer\n");
return -ENOMEM;
}
count = simple_write_to_buffer(cmd_buf, count, offset, user_buf, count);
/*
* Split the buffer into its fields
*/
fields = cmd_buf;
option = strsep(&fields, "=");
if (strcmp(option, "protocol")) {
pr_err("invalid option name: %s\n", option);
kfree(cmd_buf);
return -EINVAL;
}
value = fields;
if (!sscanf(value, "%d", &protocol)) {
pr_err("Unable to read protocol value %s\n", value);
kfree(cmd_buf);
return -EINVAL;
}
pr_debug("option: %s value: %d\n", option, protocol);
kfree(cmd_buf);
/*
* Defunct the connections which has this protocol number.
*/
ecm_sfe_common_defunct_by_protocol(protocol);
return count;
}
/*
* File operations for defunct by protocol operations.
*/
static struct file_operations ecm_sfe_l2_defunct_by_protocol_fops = {
.owner = THIS_MODULE,
.write = ecm_sfe_l2_defunct_by_protocol_write,
};
/*
* ecm_sfe_l2_wan_name_read()
* Reads the WAN interface name from the debugfs node wan_name
*/
static ssize_t ecm_sfe_l2_wan_name_read(struct file *f, char *buffer,
size_t len, loff_t *offset)
{
return simple_read_from_buffer(buffer, len, offset, wan_name, wan_name_len);
}
/*
* ecm_sfe_l2_wan_name_write()
* Writes the WAN interface name to the debugfs node wan_name
*/
static ssize_t ecm_sfe_l2_wan_name_write(struct file *f, const char *buffer,
size_t len, loff_t *offset)
{
ssize_t ret;
if (len > IFNAMSIZ) {
pr_err("WAN interface name is too long\n");
return -EINVAL;
}
ret = simple_write_to_buffer(wan_name, IFNAMSIZ, offset, buffer, len);
if (ret < 0) {
pr_err("WAN interface name cannot be written\n");
return ret;
}
wan_name[ret - 1] = '\0';
wan_name_len = ret;
return ret;
}
/*
* File operations for wan interface name.
*/
static struct file_operations ecm_sfe_l2_wan_name_fops = {
.owner = THIS_MODULE,
.write = ecm_sfe_l2_wan_name_write,
.read = ecm_sfe_l2_wan_name_read,
};
struct ecm_sfe_common_callbacks sfe_cbs = {
.l2_accel_check = ecm_sfe_l2_accel_check_callback, /**< Callback to decide if L2 acceleration is wanted for the flow. */
};
/*
* ecm_sfe_l2_init()
*/
static int __init ecm_sfe_l2_init(void)
{
pr_debug("ECM SFE L2 module INIT\n");
/*
* Create entries in DebugFS for control functions
*/
ecm_sfe_l2_dentry = debugfs_create_dir("ecm_sfe_l2", NULL);
if (!ecm_sfe_l2_dentry) {
pr_info("Failed to create SFE L2 directory entry\n");
return -1;
}
if (!debugfs_create_file("wan_name", S_IWUSR, ecm_sfe_l2_dentry,
NULL, &ecm_sfe_l2_wan_name_fops)) {
pr_debug("Failed to create ecm wan interface file in debugfs\n");
debugfs_remove_recursive(ecm_sfe_l2_dentry);
return -1;
}
if (!debugfs_create_file("policy_rules", S_IWUSR, ecm_sfe_l2_dentry,
NULL, &ecm_sfe_l2_policy_rule_fops)) {
pr_debug("Failed to create ecm SFE L2 policy rules file in debugfs\n");
debugfs_remove_recursive(ecm_sfe_l2_dentry);
return -1;
}
if (!debugfs_create_file("defunct_by_protocol", S_IWUSR, ecm_sfe_l2_dentry,
NULL, &ecm_sfe_l2_defunct_by_protocol_fops)) {
pr_debug("Failed to create ecm defunct by protocol file in debugfs\n");
debugfs_remove_recursive(ecm_sfe_l2_dentry);
return -1;
}
if (!debugfs_create_file("defunct_by_5tuple", S_IWUSR, ecm_sfe_l2_dentry,
NULL, &ecm_sfe_l2_defunct_by_5tuple_fops)) {
pr_debug("Failed to create ecm defunct by 5tuple file in debugfs\n");
debugfs_remove_recursive(ecm_sfe_l2_dentry);
return -1;
}
if (!debugfs_create_file("defunct_by_port", S_IWUSR, ecm_sfe_l2_dentry,
NULL, &ecm_sfe_l2_defunct_by_port_fops)) {
pr_debug("Failed to create ecm defunct by port file in debugfs\n");
debugfs_remove_recursive(ecm_sfe_l2_dentry);
return -1;
}
if (ecm_sfe_common_callbacks_register(&sfe_cbs)) {
pr_debug("Failed to register callbacks\n");
debugfs_remove_recursive(ecm_sfe_l2_dentry);
return -1;
}
return 0;
}
/*
* ecm_sfe_l2_exit()
*/
static void __exit ecm_sfe_l2_exit(void)
{
pr_debug("ECM SFE L2 check module EXIT\n");
ecm_sfe_common_callbacks_unregister();
/*
* Remove the debugfs files recursively.
*/
debugfs_remove_recursive(ecm_sfe_l2_dentry);
}
module_init(ecm_sfe_l2_init)
module_exit(ecm_sfe_l2_exit)
MODULE_DESCRIPTION("ECM SFE L2 Module");
#ifdef MODULE_LICENSE
MODULE_LICENSE("Dual BSD/GPL");
#endif