blob: b4c7d5ba01c44319f4aef4bdf622f10f349ff27f [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2016-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_nlipv6.c
* NSS Netlink IPv6 Handler
*/
#include <linux/completion.h>
#include <linux/etherdevice.h>
#include <linux/if.h>
#include <linux/if_addr.h>
#include <linux/if_vlan.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/rcupdate.h>
#include <linux/semaphore.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/version.h>
#include <linux/vmalloc.h>
#include <net/addrconf.h>
#include <net/arp.h>
#include <net/genetlink.h>
#include <net/ip6_route.h>
#include <net/neighbour.h>
#include <net/net_namespace.h>
#include <net/route.h>
#include <net/sock.h>
#include <nss_api_if.h>
#include <nss_cmn.h>
#include <nss_ipsec.h>
#include <nss_ipsec_cmn.h>
#include <nss_nl_if.h>
#include "nss_ipsecmgr.h"
#include "nss_nl.h"
#include "nss_nlcmn_if.h"
#include "nss_nlgre_redir_cmd.h"
#include "nss_nlipsec.h"
#include "nss_nlipsec_if.h"
#include "nss_nlipv6.h"
#include "nss_nlipv6_if.h"
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0))
#define DST_NEIGH_LOOKUP(dst, ip_addr) dst_neigh_lookup(dst, ip_addr)
#else
#define DST_NEIGH_LOOKUP(dst, ip_addr) dst_get_neighbour_noref(dst)
#endif
/*
* NSS NETLINK IPv6 context
*/
struct nss_nlipv6_ctx {
void *nss;
};
/*
* prototypes
*/
static int nss_nlipv6_ops_create_rule(struct sk_buff *skb, struct genl_info *info);
static int nss_nlipv6_ops_destroy_rule(struct sk_buff *skb, struct genl_info *info);
static int nss_nlipv6_process_notify(struct notifier_block *nb, unsigned long val, void *data);
/*
* multicast group for sending message status & events
*/
static struct genl_multicast_group nss_nlipv6_mcgrp[] = {
{.name = NSS_NLIPV6_MCAST_GRP},
};
/*
* operation table called by the generic netlink layer based on the command
*/
static struct genl_ops nss_nlipv6_ops[] = {
{.cmd = NSS_IPV6_TX_CREATE_RULE_MSG, .doit = nss_nlipv6_ops_create_rule,}, /* rule create */
{.cmd = NSS_IPV6_TX_DESTROY_RULE_MSG, .doit = nss_nlipv6_ops_destroy_rule,}, /* rule destroy */
};
/*
* IPV6 family definition
*/
static struct genl_family nss_nlipv6_family = {
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 0))
.id = GENL_ID_GENERATE, /* Auto generate ID */
#endif
.name = NSS_NLIPV6_FAMILY, /* family name string */
.hdrsize = sizeof(struct nss_nlipv6_rule), /* NSS NETLINK IPV6 rule */
.version = NSS_NL_VER, /* Set it to NSS_NLIPV6 version */
.maxattr = NSS_IPV6_MAX_MSG_TYPES, /* maximum commands supported */
.netnsok = true,
.pre_doit = NULL,
.post_doit = NULL,
.ops = nss_nlipv6_ops,
.n_ops = ARRAY_SIZE(nss_nlipv6_ops),
.mcgrps = nss_nlipv6_mcgrp,
.n_mcgrps = ARRAY_SIZE(nss_nlipv6_mcgrp)
};
/*
* statistics call back handler for ipv6 from NSS
*/
static struct notifier_block nss_ipv6_stats_notifier_nb = {
.notifier_call = nss_nlipv6_process_notify,
};
#define NSS_NLIPV6_OPS_SZ ARRAY_SIZE(nss_nlipv6_ops)
static struct nss_nlipv6_ctx gbl_ctx;
/*
* nss_nlipv6_get_neigh()
* Returns neighbour reference for a given IP address
*/
static struct neighbour *nss_nlipv6_get_neigh(uint32_t dst_addr[4])
{
struct neighbour *neigh;
struct dst_entry *dst;
struct rt6_info *rt;
struct in6_addr daddr;
IPV6_ADDR_TO_IN6_ADDR(daddr, dst_addr);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0))
rt = rt6_lookup(&init_net, &daddr, NULL, 0, 0);
#else
rt = rt6_lookup(&init_net, &daddr, NULL, 0, NULL, 0);
#endif
if (!rt) {
return NULL;
}
dst = (struct dst_entry *)rt;
/*
* neighbour lookup using IP address in the route table
*/
neigh = DST_NEIGH_LOOKUP(dst, &daddr);
if (likely(neigh)) {
neigh_hold(neigh);
dst_release(dst);
return neigh;
}
dst_release(dst);
return NULL;
}
/*
* nss_nlipv6_get_addr_hton()
* Convert the ipv6 address from host order to network order.
*/
static inline void nss_nlipv6_get_addr_hton(uint32_t src[4], uint32_t dst[4])
{
nss_nlipv6_swap_addr(src, dst);
dst[0] = htonl(dst[0]);
dst[1] = htonl(dst[1]);
dst[2] = htonl(dst[2]);
dst[3] = htonl(dst[3]);
}
/*
* nss_nlipv6_mac_addr_get()
* Return the hardware (MAC) address of the given ipv6 address, if any.
*
* Returns 0 on success or a negative result on failure.
* We look up the rtable entry for the address and,
* from its neighbour structure,obtain the hardware address.
* This means we will also work if the neighbours are routers too.
*/
static int nss_nlipv6_get_macaddr(uint32_t ip_addr[4], uint8_t mac_addr[])
{
struct neighbour *neigh;
struct in6_addr addr;
nss_nlipv6_get_addr_hton(ip_addr, addr.s6_addr32);
if (ipv6_addr_is_multicast(&addr)) {
/*
* fixed
*/
mac_addr[0] = 0x33;
mac_addr[1] = 0x33;
/*
* Last word of the IPv6 address.
*/
memcpy(&mac_addr[2], &addr.s6_addr32[3], sizeof(uint32_t));
return 0;
}
rcu_read_lock();
/*
* retrieve the neighbour
*/
neigh = nss_nlipv6_get_neigh(addr.s6_addr32);
if (!neigh) {
rcu_read_unlock();
nss_nl_info("neighbour lookup failed for %pI6c\n", addr.s6_addr32);
return -ENODEV;
}
rcu_read_unlock();
if ((neigh->nud_state & NUD_VALID) == 0) {
nss_nl_info("neighbour state is invalid for %pI6c\n", addr.s6_addr32);
goto fail;
}
if (!neigh->dev) {
nss_nl_info("neighbour device not found for %pI6c\n", addr.s6_addr32);
goto fail;
}
if (is_multicast_ether_addr(neigh->ha)) {
nss_nl_info("neighbour MAC address is multicast or broadcast\n");
goto fail;
}
ether_addr_copy(mac_addr, neigh->ha);
neigh_release(neigh);
return 0;
fail:
neigh_release(neigh);
return -ENODEV;
}
/*
* nss_nlipv6_verify_5tuple()
* verify and override 5-tuple entries
*/
static int nss_nlipv6_verify_5tuple(struct nss_ipv6_5tuple *tuple)
{
/*
* protocol must be provided
*/
if (!tuple->protocol) {
nss_nl_info("Empty protocol for 5-tuple\n");
return -EINVAL;
}
if (bitmap_empty((unsigned long *)tuple->flow_ip, NSS_NLIPV6_ADDR_BITS)) {
nss_nl_info("Empty flow IP\n");
return -EINVAL;
}
if (bitmap_empty((unsigned long *)tuple->return_ip, NSS_NLIPV6_ADDR_BITS)) {
nss_nl_info("Empty return IP\n");
return -EINVAL;
}
/*
* Validate the port number
*/
switch (tuple->protocol) {
case IPPROTO_UDP:
case IPPROTO_TCP:
case IPPROTO_SCTP:
if (!tuple->flow_ident || !tuple->return_ident) {
nss_nl_error("Empty flow ident or return ident. flow ident:%d return ident:%d protocol:%d\n",
tuple->flow_ident, tuple->return_ident, tuple->protocol);
return -EINVAL;
}
break;
default:
if (tuple->flow_ident || tuple->return_ident) {
nss_nl_error("Flow ident and return ident must be empty. flow ident:%u return ident:%u protocol:%u\n",
tuple->flow_ident, tuple->return_ident, tuple->protocol);
return -EINVAL;
}
}
return 0;
}
/*
* nss_nlipv6_verify_conn_rule()
* verify and override connection rule entries
*/
static int nss_nlipv6_verify_conn_rule(struct nss_ipv6_rule_create_msg *msg, struct net_device *flow_dev,
struct net_device *return_dev, enum nss_nl_iftype flow_iftype,
enum nss_nl_iftype return_iftype)
{
struct nss_ipv6_connection_rule *conn = &msg->conn_rule;
struct nss_ipv6_nexthop *nexthop = &msg->nexthop_rule;
struct nss_ipv6_5tuple *tuple = &msg->tuple;
const size_t rule_sz = sizeof(struct nss_ipv6_connection_rule);
bool valid;
/*
* Connection rule is not valid ignore rest of the checks
*/
valid = msg->valid_flags & NSS_IPV6_RULE_CREATE_CONN_VALID;
if (!valid) {
memset(conn, 0, rule_sz);
return -EINVAL;
}
if ((flow_iftype >= NSS_NL_IFTYPE_MAX) || (return_iftype >= NSS_NL_IFTYPE_MAX)) {
nss_nl_error("%px: Invalid interface type (flow:%d, return:%d)\n", msg, flow_iftype, return_iftype);
return -EINVAL;
}
/*
* Update the flow & return MAC address
*/
if (nss_nlipv6_get_macaddr(tuple->flow_ip, (uint8_t *)conn->flow_mac)) {
nss_nl_info("Error in Updating the Flow MAC Address \n");
return -EINVAL;
}
if (nss_nlipv6_get_macaddr(tuple->return_ip, (uint8_t *)conn->return_mac)) {
nss_nl_info("Error in Updating the Return MAC Address \n");
return -EINVAL;
}
/*
* Update flow interface number and flow mtu
*/
switch (flow_iftype) {
case NSS_NL_IFTYPE_TUNNEL_IPSEC:
conn->flow_interface_num = nss_nlipsec_get_ifnum(flow_dev, tuple->protocol,
tuple->return_ident, tuple->flow_ident);
if (conn->flow_interface_num < 0 ) {
nss_nl_error("%px: Failed to get flow interface number (dev:%s, type:%d)\n",
flow_dev, flow_dev->name, flow_iftype);
return -EINVAL;
}
conn->flow_mtu = nss_nlipsec_get_mtu(flow_dev, 6, tuple->protocol,
tuple->return_ident, tuple->flow_ident);
break;
case NSS_NL_IFTYPE_TUNNEL_GRE:
conn->flow_interface_num = nss_nlgre_redir_cmd_get_ifnum(flow_dev, tuple->protocol);
if (conn->flow_interface_num < 0 ) {
nss_nl_error("%px: Failed to get flow interface number (dev:%s, type:%d)\n",
flow_dev, flow_dev->name, flow_iftype);
return -EINVAL;
}
conn->flow_mtu = nss_nlgre_redir_cmd_get_mtu(flow_dev, NSS_GRE_REDIR_IP_HDR_TYPE_IPV6, conn->flow_interface_num);
break;
case NSS_NL_IFTYPE_VLAN:
conn->flow_interface_num = nss_cmn_get_interface_number_by_dev(vlan_dev_real_dev(flow_dev));
if (conn->flow_interface_num < 0 ) {
nss_nl_error("%px: Failed to get flow interface number (dev:%s, type:%d)\n",
flow_dev, flow_dev->name, flow_iftype);
return -EINVAL;
}
conn->flow_mtu = flow_dev->mtu;
break;
case NSS_NL_IFTYPE_PHYSICAL:
conn->flow_interface_num = nss_cmn_get_interface_number_by_dev(flow_dev);
if (conn->flow_interface_num < 0 ) {
nss_nl_error("%px: Failed to get flow interface number (dev:%s, type:%d)\n",
flow_dev, flow_dev->name, flow_iftype);
return -EINVAL;
}
conn->flow_mtu = flow_dev->mtu;
break;
default:
nss_nl_error("%px: Unsupported flow interface type (%d)\n", msg, flow_iftype);
return -EINVAL;
}
nss_nl_info("%px: dev=%s flow_ifnum:0x%x flow_mtu=%d\n", msg, flow_dev->name,
conn->flow_interface_num, conn->flow_mtu);
/*
* Update return interface number and return mtu
*/
switch (return_iftype) {
case NSS_NL_IFTYPE_TUNNEL_IPSEC:
conn->return_interface_num = nss_nlipsec_get_ifnum(return_dev, tuple->protocol,
tuple->return_ident, tuple->flow_ident);
if (conn->return_interface_num < 0 ) {
nss_nl_error("%px: Failed to get return interface number (dev:%s, type:%d)\n",
return_dev, return_dev->name, return_iftype);
return -EINVAL;
}
conn->return_mtu = nss_nlipsec_get_mtu(return_dev, 6, tuple->protocol,
tuple->return_ident, tuple->flow_ident);
break;
case NSS_NL_IFTYPE_TUNNEL_GRE:
conn->return_interface_num = nss_nlgre_redir_cmd_get_ifnum(return_dev, tuple->protocol);
if (conn->return_interface_num < 0 ) {
nss_nl_error("%px: Failed to get return interface number (dev:%s, type:%d)\n",
return_dev, return_dev->name, return_iftype);
return -EINVAL;
}
conn->return_mtu = nss_nlgre_redir_cmd_get_mtu(return_dev, NSS_GRE_REDIR_IP_HDR_TYPE_IPV6, conn->return_interface_num);
break;
case NSS_NL_IFTYPE_VLAN:
conn->return_interface_num = nss_cmn_get_interface_number_by_dev(vlan_dev_real_dev(return_dev));
if (conn->return_interface_num < 0 ) {
nss_nl_error("%px: Failed to get return interface number (dev:%s, type:%d)\n",
return_dev, return_dev->name, return_iftype);
return -EINVAL;
}
conn->return_mtu = return_dev->mtu;
break;
case NSS_NL_IFTYPE_PHYSICAL:
conn->return_interface_num = nss_cmn_get_interface_number_by_dev(return_dev);
if (conn->return_interface_num < 0 ) {
nss_nl_error("%px: Failed to get return interface number (dev:%s, type:%d)\n",
return_dev, return_dev->name, return_iftype);
return -EINVAL;
}
conn->return_mtu = return_dev->mtu;
break;
default:
nss_nl_error("%px: Unsupported return interface type (%d)\n", msg, flow_iftype);
return -EINVAL;
}
nss_nl_info("%px: dev=%s return_ifnum:0x%x return_mtu=%d\n", msg, return_dev->name,
conn->return_interface_num, conn->return_mtu);
nexthop->flow_nexthop = conn->flow_interface_num;
nexthop->return_nexthop = conn->return_interface_num;
nss_nl_info("flow_nexthop:%d return_nexthop:%d\n", nexthop->flow_nexthop, nexthop->return_nexthop);
return 0;
}
/*
* nss_nlipv6_verify_tcp_rule()
* verify and override TCP protocol rule entries
*/
static int nss_nlipv6_verify_tcp_rule(struct nss_ipv6_rule_create_msg *msg)
{
struct nss_ipv6_protocol_tcp_rule *tcp = &msg->tcp_rule;
const size_t rule_sz = sizeof(struct nss_ipv6_protocol_tcp_rule);
bool valid;
/*
* tcp rule is not valid ignore rest of the checks
*/
valid = msg->valid_flags & NSS_IPV6_RULE_CREATE_TCP_VALID;
if (!valid) {
memset(tcp, 0, rule_sz);
return 0;
}
/*
* XXX: add addtional checks as required
*/
return 0;
}
/*
* nss_nlipv6_verify_pppoe_rule()
* verify and override pppoe rule entries
*/
static int nss_nlipv6_verify_pppoe_rule(struct nss_ipv6_rule_create_msg *msg)
{
struct nss_ipv6_pppoe_rule *pppoe = &msg->pppoe_rule;
const size_t rule_sz = sizeof(struct nss_ipv6_pppoe_rule);
bool valid;
/*
* pppoe rule is not valid ignore rest of the checks
*/
valid = msg->valid_flags & NSS_IPV6_RULE_CREATE_PPPOE_VALID;
if (!valid) {
memset(pppoe, 0, rule_sz);
return 0;
}
/*
* XXX: add addtional checks as required
*/
return 0;
}
/*
* nss_nlipv6_verify_igs_rule()
* verify and override ingress shaping rule entries
*/
static int nss_nlipv6_verify_igs_rule(struct nss_ipv6_rule_create_msg *msg)
{
struct nss_ipv6_igs_rule *igs = &msg->igs_rule;
const size_t rule_sz = sizeof(struct nss_ipv6_igs_rule);
bool valid;
/*
* ingress shaping rule is not valid ignore rest of the checks
*/
valid = msg->valid_flags & NSS_IPV6_RULE_CREATE_IGS_VALID;
if (!valid) {
memset(igs, 0, rule_sz);
return 0;
}
return 0;
}
/*
* nss_nlipv6_verify_qos_rule()
* verify and override qos rule entries
*/
static int nss_nlipv6_verify_qos_rule(struct nss_ipv6_rule_create_msg *msg)
{
struct nss_ipv6_qos_rule *qos = &msg->qos_rule;
const size_t rule_sz = sizeof(struct nss_ipv6_qos_rule);
bool valid;
/*
* qos rule is not valid ignore rest of the checks
*/
valid = msg->valid_flags & NSS_IPV6_RULE_CREATE_QOS_VALID;
if (!valid) {
memset(qos, 0, rule_sz);
return 0;
}
/*
* XXX: add addtional checks as required
*/
return 0;
}
/*
* nss_nlipv6_verify_dscp_rule()
* verify and override dscp rule entries
*/
static int nss_nlipv6_verify_dscp_rule(struct nss_ipv6_rule_create_msg *msg)
{
struct nss_ipv6_dscp_rule *dscp = &msg->dscp_rule;
const size_t rule_sz = sizeof(struct nss_ipv6_dscp_rule);
bool valid;
/*
* dscp rule is not valid ignore rest of the checks
*/
valid = msg->valid_flags & NSS_IPV6_RULE_CREATE_DSCP_MARKING_VALID;
if (!valid) {
memset(dscp, 0, rule_sz);
return 0;
}
/*
* XXX: add addtional checks as required
*/
return 0;
}
/*
* nss_nlipv6_verify_vlan_rule()
* verify and override vlan rule entries
*/
static int nss_nlipv6_verify_vlan_rule(struct nss_ipv6_rule_create_msg *msg, struct net_device *flow_dev,
struct net_device *return_dev)
{
struct nss_ipv6_vlan_rule *vlan_primary = &msg->vlan_primary_rule;
struct nss_ipv6_vlan_rule *vlan_secondary = &msg->vlan_secondary_rule;
bool flow_vlan = is_vlan_dev(flow_dev);
bool return_vlan = is_vlan_dev(return_dev);
/*
* Fill all with default values.
*/
vlan_primary->ingress_vlan_tag = NSS_NLIPV6_VLAN_ID_NOT_CONFIGURED;
vlan_primary->egress_vlan_tag = NSS_NLIPV6_VLAN_ID_NOT_CONFIGURED;
vlan_secondary->ingress_vlan_tag = NSS_NLIPV6_VLAN_ID_NOT_CONFIGURED;
vlan_secondary->egress_vlan_tag = NSS_NLIPV6_VLAN_ID_NOT_CONFIGURED;
if (!flow_vlan && !return_vlan)
return 0;
msg->valid_flags |= NSS_IPV6_RULE_CREATE_VLAN_VALID;
/*
* Add single vlan
*/
if (flow_vlan)
vlan_primary->ingress_vlan_tag = ETH_P_8021Q << 16 | vlan_dev_vlan_id(flow_dev);
if (return_vlan)
vlan_primary->egress_vlan_tag = ETH_P_8021Q << 16 | vlan_dev_vlan_id(return_dev);
/*
* XXX: add addtional checks as required
*/
return 0;
}
/*
* nss_nlipv6_verify_identifier()
* verify and override identifier rule entries
*/
static int nss_nlipv6_verify_identifier(struct nss_ipv6_rule_create_msg *msg)
{
struct nss_ipv6_identifier_rule *identifier = &msg->identifier;
const size_t rule_sz = sizeof(struct nss_ipv6_identifier_rule);
uint16_t valid;
/*
* if identifier is not valid, set identifier rule to 0
*/
valid = msg->valid_flags & NSS_IPV6_RULE_CREATE_IDENTIFIER_VALID;
if (!valid) {
memset(identifier, 0, rule_sz);
}
return 0;
}
/*
* nss_nlipv6_process_notify()
* process notification messages from NSS
*/
static int nss_nlipv6_process_notify(struct notifier_block *nb, unsigned long val, void *data)
{
struct nss_nlipv6_rule *nl_rule;
struct sk_buff *skb;
struct nss_ipv6_stats_notification *nss_stats;
skb = nss_nl_new_msg(&nss_nlipv6_family, NSS_NLCMN_SUBSYS_IPV6);
if (!skb) {
nss_nl_error("unable to allocate NSS_NLIPV6 event\n");
return NOTIFY_DONE;
}
nl_rule = nss_nl_get_data(skb);
nss_stats = (struct nss_ipv6_stats_notification *)data;
memcpy(&nl_rule->stats, nss_stats, sizeof(struct nss_ipv6_stats_notification));
nss_nl_mcast_event(&nss_nlipv6_family, skb);
return NOTIFY_DONE;
}
/*
* nss_nlipv6_resp_create_rule()
* handle response for create rule
*/
static void nss_nlipv6_process_resp(void *app_data, struct nss_ipv6_msg *nim)
{
struct sk_buff *resp = (struct sk_buff *)app_data;
struct nss_nlipv6_rule *nl_rule;
struct nss_ipv6_msg *nl_nim;
nl_rule = nss_nl_get_data(resp);
nl_nim = &nl_rule->nim;
/*
* copy the message response data into the NL message buffer. If, FW
* has updated the message then we must updated the same into the NL
* message as the NL message buffer is different from what was sent
* to FW
*/
memcpy(nl_nim, nim, sizeof(struct nss_ipv6_msg));
nss_nl_ucast_resp(resp);
}
/*
* nss_nlipv6_ops_create_rule()
* rule create handler
*/
static int nss_nlipv6_ops_create_rule(struct sk_buff *skb, struct genl_info *info)
{
struct net_device *return_dev;
struct net_device *flow_dev;
struct nss_nlipv6_rule *nl_rule;
struct nss_nlcmn *nl_cm;
struct nss_ipv6_msg *nim;
nss_tx_status_t tx_status;
struct sk_buff *resp;
uint32_t pid;
int error;
/*
* extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nlipv6_family, info, NSS_IPV6_TX_CREATE_RULE_MSG);
if (!nl_cm) {
nss_nl_error("unable to extract rule create data\n");
return -EINVAL;
}
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nlipv6_rule, cm);
nim = &nl_rule->nim;
pid = nl_cm->pid;
/*
* extract netdevices for flow and return
*/
flow_dev = dev_get_by_name(&init_net, nl_rule->flow_ifname);
if (!flow_dev) {
nss_nl_error("%d:flow interface is not available for dev=%s\n", pid, nl_rule->flow_ifname);
return -EINVAL;
}
return_dev = dev_get_by_name(&init_net, nl_rule->return_ifname);
if (!return_dev) {
dev_put(flow_dev);
nss_nl_error("%d:return interface is not available for dev=%s\n", pid, nl_rule->return_ifname);
return -EINVAL;
}
/*
* check 5-tuple
*/
error = nss_nlipv6_verify_5tuple(&nim->msg.rule_create.tuple);
if (error < 0) {
nss_nl_error("%d:invalid 5-tuple information passed\n", pid);
goto done;
}
nss_nl_info("Checking rule for flowdev=%s flow_type=%d returndev=%s return_type=%d\n",
nl_rule->flow_ifname, nl_rule->flow_iftype, nl_rule->return_ifname, nl_rule->return_iftype);
/*
* check connection rule
*/
error = nss_nlipv6_verify_conn_rule(&nim->msg.rule_create, flow_dev, return_dev,
nl_rule->flow_iftype, nl_rule->return_iftype);
if (error < 0) {
nss_nl_error("%d:invalid conn rule information passed\n", pid);
goto done;
}
/*
* check tcp protocol rule
*/
error = nss_nlipv6_verify_tcp_rule(&nim->msg.rule_create);
if (error < 0) {
nss_nl_error("%d:invalid tcp rule information passed\n", pid);
goto done;
}
/*
* check pppoe rule
*/
error = nss_nlipv6_verify_pppoe_rule(&nim->msg.rule_create);
if (error < 0) {
nss_nl_error("%d:invalid pppoe rule information passed\n", pid);
goto done;
}
/*
* check qos rule
*/
error = nss_nlipv6_verify_qos_rule(&nim->msg.rule_create);
if (error < 0) {
nss_nl_error("%d:invalid qos rule information passed\n", pid);
goto done;
}
/*
* check ingress shaping rule
*/
error = nss_nlipv6_verify_igs_rule(&nim->msg.rule_create);
if (error < 0) {
nss_nl_error("%d:invalid ingress shaping rule information passed\n", pid);
goto done;
}
/*
* check dscp rule
*/
error = nss_nlipv6_verify_dscp_rule(&nim->msg.rule_create);
if (error < 0) {
nss_nl_error("%d:invalid dscp rule information passed\n", pid);
goto done;
}
/*
* check vlan rule
*/
error = nss_nlipv6_verify_vlan_rule(&nim->msg.rule_create, flow_dev, return_dev);
if (error < 0) {
nss_nl_error("%d:invalid vlan rule information passed\n", pid);
goto done;
}
/*
* check identifier
*/
error = nss_nlipv6_verify_identifier(&nim->msg.rule_create);
if (error < 0) {
nss_nl_error("%d:invalid identifier rule information passed\n", pid);
goto done;
}
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%d:unable to save response data from NL buffer\n", pid);
error = -ENOMEM;
goto done;
}
/*
* Initialize the common message
*/
nss_ipv6_msg_init(nim, /* ipv6 message */
NSS_IPV6_RX_INTERFACE, /* interface number */
NSS_IPV6_TX_CREATE_RULE_MSG, /* rule */
sizeof(struct nss_ipv6_rule_create_msg),/* message size */
nss_nlipv6_process_resp, /* response callback */
(void *)resp); /* app context */
/*
* Conver the IP addresses to NSS format
*/
nss_nlipv6_swap_addr(nim->msg.rule_create.tuple.flow_ip, nim->msg.rule_create.tuple.flow_ip);
nss_nlipv6_swap_addr(nim->msg.rule_create.tuple.return_ip, nim->msg.rule_create.tuple.return_ip);
/*
* Push Rule to NSS
*/
tx_status = nss_ipv6_tx_sync(gbl_ctx.nss, nim);
/* TODO: Handle the case where firmware has received the response
* and there is a failure in firmware.
*/
if (tx_status != NSS_TX_SUCCESS) {
nss_nl_error("%d:unable to send IPV6 rule create, status(%d)\n", pid, tx_status);
error = -EBUSY;
}
done:
dev_put(flow_dev);
dev_put(return_dev);
return error;
}
/*
* nss_nlipv6_ops_destroy_rule()
* rule delete handler
*/
static int nss_nlipv6_ops_destroy_rule(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nlipv6_rule *nl_rule;
struct nss_nlcmn *nl_cm;
struct nss_ipv6_msg *nim;
nss_tx_status_t tx_status;
struct sk_buff *resp;
uint32_t pid;
int error;
/*
* extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nlipv6_family, info, NSS_IPV6_TX_DESTROY_RULE_MSG);
if (!nl_cm) {
nss_nl_error("unable to extract rule destroy data\n");
return -EINVAL;
}
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nlipv6_rule, cm);
nim = &nl_rule->nim;
pid = nl_cm->pid;
/*
* check 5-tuple
*/
error = nss_nlipv6_verify_5tuple(&nim->msg.rule_destroy.tuple);
if (error < 0) {
nss_nl_error("%d:invalid 5-tuple information passed\n", pid);
goto done;
}
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%d:unable to save response data from NL buffer\n", pid);
error = -ENOMEM;
goto done;
}
/*
* Initialize the common message
*/
nss_ipv6_msg_init(nim, /* ipv6 message */
NSS_IPV6_RX_INTERFACE, /* interface number */
NSS_IPV6_TX_DESTROY_RULE_MSG, /* rule */
sizeof(struct nss_ipv6_rule_destroy_msg), /* message size */
nss_nlipv6_process_resp, /* response callback */
(void *)resp); /* app context */
/*
* Conver the IP addresses to NSS format
*/
nss_nlipv6_swap_addr(nim->msg.rule_destroy.tuple.flow_ip, nim->msg.rule_destroy.tuple.flow_ip);
nss_nlipv6_swap_addr(nim->msg.rule_destroy.tuple.return_ip, nim->msg.rule_destroy.tuple.return_ip);
/*
* Push rule to NSS
*/
tx_status = nss_ipv6_tx_sync(gbl_ctx.nss, nim);
/* TODO: Handle the case where firmware has received the response
* and there is a failure in firmware.
*/
if (tx_status != NSS_TX_SUCCESS) {
nss_nl_error("%d:unable to send IPV6 rule delete, status(%d)\n", pid, tx_status);
return -EBUSY;
}
done:
return error;
}
/*
* nss_nlipv6_init()
* handler init
*/
bool nss_nlipv6_init(void)
{
int error, ret;
nss_nl_info_always("Init NSS netlink IPV6 handler\n");
/*
* register NETLINK ops with the family
*/
error = genl_register_family(&nss_nlipv6_family);
if (error != 0) {
nss_nl_info_always("Error: unable to register IPV6 family\n");
return false;
}
/*
* To get NSS context
*/
gbl_ctx.nss = nss_ipv6_get_mgr();
if (!gbl_ctx.nss) {
nss_nl_info_always("Error: retreiving the NSS Context \n");
goto unreg_family;
}
/*
* register device call back handler for ipv6 from NSS
*/
ret = nss_ipv6_stats_register_notifier(&nss_ipv6_stats_notifier_nb);
if (ret) {
nss_nl_info_always("Error: retreiving the NSS Context \n");
goto unreg_family;
}
return true;
/*
* undo all registeration
*/
unreg_family:
genl_unregister_family(&nss_nlipv6_family);
return false;
}
/*
* nss_nlipv6_exit()
* handler exit
*/
bool nss_nlipv6_exit(void)
{
int error;
nss_nl_info_always("Exit NSS netlink IPV6 handler\n");
/*
* Unregister the device callback handler for ipv6
*/
nss_ipv6_stats_unregister_notifier(&nss_ipv6_stats_notifier_nb);
/*
* unregister the ops family
*/
error = genl_unregister_family(&nss_nlipv6_family);
if (error != 0) {
nss_nl_info_always("unable to unregister IPV6 NETLINK family\n");
return false;
}
gbl_ctx.nss = NULL;
return true;
}