blob: a769b3c08eb6eb1fcf138f88eafacc97e66894f3 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 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.
**************************************************************************
*/
/*
* nss_nludp_st.c
* NSS Netlink udp_st Handler
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/netlink.h>
#include <linux/version.h>
#include <linux/vmalloc.h>
#include <net/genetlink.h>
#include <net/sock.h>
#include <net/arp.h>
#include <nss_api_if.h>
#include <nss_cmn.h>
#include <nss_nl_if.h>
#include "nss_nl.h"
#include "nss_nlcmn_if.h"
#include "nss_nludp_st_if.h"
#include "nss_nludp_st.h"
/*
* prototypes
*/
static int nss_nludp_st_ops_uncfg_rule(struct sk_buff *skb, struct genl_info *info);
static int nss_nludp_st_ops_cfg_rule(struct sk_buff *skb, struct genl_info *info);
static int nss_nludp_st_ops_stop(struct sk_buff *skb, struct genl_info *info);
static int nss_nludp_st_ops_start(struct sk_buff *skb, struct genl_info *info);
static int nss_nludp_st_ops_tx_destroy(struct sk_buff *skb, struct genl_info *info);
static int nss_nludp_st_ops_tx_create(struct sk_buff *skb, struct genl_info *info);
static int nss_nludp_st_ops_reset_stats(struct sk_buff *skb, struct genl_info *info);
/*
* multicast group for sending message status & events
*/
static const struct genl_multicast_group nss_nludp_st_mcgrp[] = {
{.name = NSS_NLUDP_ST_MCAST_GRP},
};
/*
* operation table called by the generic netlink layer based on the command
*/
static struct genl_ops nss_nludp_st_ops[] = {
{.cmd = NSS_UDP_ST_CFG_RULE_MSG, .doit = nss_nludp_st_ops_cfg_rule,},
{.cmd = NSS_UDP_ST_UNCFG_RULE_MSG, .doit = nss_nludp_st_ops_uncfg_rule,},
{.cmd = NSS_UDP_ST_START_MSG, .doit = nss_nludp_st_ops_start,},
{.cmd = NSS_UDP_ST_STOP_MSG, .doit = nss_nludp_st_ops_stop,},
{.cmd = NSS_UDP_ST_TX_CREATE_MSG, .doit = nss_nludp_st_ops_tx_create,},
{.cmd = NSS_UDP_ST_TX_DESTROY_MSG, .doit = nss_nludp_st_ops_tx_destroy,},
{.cmd = NSS_UDP_ST_RESET_STATS_MSG, .doit = nss_nludp_st_ops_reset_stats,},
};
/*
* udp_st family definition
*/
static struct genl_family nss_nludp_st_family = {
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 0))
.id = GENL_ID_GENERATE, /* Auto generate ID */
#endif
.name = NSS_NLUDP_ST_FAMILY, /* family name string */
.hdrsize = sizeof(struct nss_nludp_st_rule), /* NSS NETLINK udp_st rule */
.version = NSS_NL_VER, /* Set it to NSS_NLudp_st version */
.maxattr = NSS_UDP_ST_MAX_MSG_TYPES, /* Maximum commands supported */
.netnsok = true,
.pre_doit = NULL,
.post_doit = NULL,
.ops = nss_nludp_st_ops,
.n_ops = ARRAY_SIZE(nss_nludp_st_ops),
.mcgrps = nss_nludp_st_mcgrp,
.n_mcgrps = ARRAY_SIZE(nss_nludp_st_mcgrp)
};
/*
* nss_nludp_st_process_resp()
* handle responses
*/
static void nss_nludp_st_process_resp(void *app_data, struct nss_udp_st_msg *num)
{
struct sk_buff *resp = (struct sk_buff *)app_data;
struct nss_nludp_st_rule *nl_rule;
struct nss_udp_st_msg *nl_num;
nl_rule = nss_nl_get_data(resp);
nl_num = &nl_rule->num;
/*
* clear NSS common message items that are not useful to uspace
*/
num->cm.interface = 0;
num->cm.cb = (nss_ptr_t)NULL;
num->cm.app_data = (nss_ptr_t)NULL;
/*
* 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_num, num, sizeof(struct nss_udp_st_msg));
nss_nl_ucast_resp(resp);
}
/*
* nss_nludp_st_ops_stop
* Stop UDP spedtest
*/
static int nss_nludp_st_ops_stop(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nludp_st_rule *nl_rule;
struct nss_nlcmn *nl_cm;
struct nss_ctx_instance *nss_ctx;
struct nss_udp_st_msg num;
struct sk_buff *resp;
nss_tx_status_t status;
/*
* Extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nludp_st_family, info, NSS_UDP_ST_STOP_MSG);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract udp_st stop data\n", skb);
return -EINVAL;
}
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nludp_st_rule, cm);
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%p:unable to save response data from NL buffer\n", nl_rule);
return -ENOMEM;
}
nss_ctx = nss_udp_st_get_mgr();
if (!nss_ctx) {
nss_nl_info("Couldn't get udp_st ctx\n");
return -EPERM;
}
memset(&num, 0, sizeof(struct nss_udp_st_msg));
nss_udp_st_msg_init(&num, /* udp_st message */
NSS_UDP_ST_INTERFACE, /* interface number */
NSS_UDP_ST_STOP_MSG, /* rule */
sizeof(struct nss_udp_st_stop), /* message size */
nss_nludp_st_process_resp, /* response callback */
(void *)resp); /* app context */
memcpy(&num.msg.stop, &nl_rule->num.msg.stop, sizeof(struct nss_udp_st_stop));
status = nss_udp_st_tx_sync(nss_ctx, &num);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create udp_st message failed %d\n", nss_ctx, status);
return -EBADMSG;
}
return 0;
}
/*
* nss_nludp_st_ops_start
* Start UDP spedtest
*/
static int nss_nludp_st_ops_start(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nludp_st_rule *nl_rule;
struct nss_nlcmn *nl_cm;
struct nss_ctx_instance *nss_ctx;
struct nss_udp_st_msg num;
struct sk_buff *resp;
uint32_t pid;
nss_tx_status_t status;
/*
* Extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nludp_st_family, info, NSS_UDP_ST_START_MSG);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract udp_st start data\n", skb);
return -EINVAL;
}
pid = nl_cm->pid;
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nludp_st_rule, cm);
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%p:unable to save response data from NL buffer\n", nl_rule);
return -ENOMEM;
}
nss_ctx = nss_udp_st_get_mgr();
if (!nss_ctx) {
nss_nl_info("Couldn't get udp_st ctx\n");
return -ENOENT;
}
memset(&num, 0, sizeof(struct nss_udp_st_msg));
nss_udp_st_msg_init(&num, /* udp_st message */
NSS_UDP_ST_INTERFACE, /* interface number */
NSS_UDP_ST_START_MSG, /* rule */
sizeof(struct nss_udp_st_start), /* message size */
nss_nludp_st_process_resp, /* response callback */
(void *)resp); /* app context */
memcpy(&num.msg.start, &nl_rule->num.msg.start, sizeof(struct nss_udp_st_start));
status = nss_udp_st_tx_sync(nss_ctx, &num);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create udp_st message failed %d\n", nss_ctx, status);
return -EBADMSG;
}
return 0;
}
/*
* nss_nludp_st_ops_reset_stats
* Resets UDP speed test statistics.
*/
static int nss_nludp_st_ops_reset_stats(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nludp_st_rule *nl_rule;
struct nss_nlcmn *nl_cm;
struct nss_ctx_instance *nss_ctx;
struct nss_udp_st_msg num;
struct sk_buff *resp;
uint32_t pid;
nss_tx_status_t status;
/*
* Extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nludp_st_family, info, NSS_UDP_ST_RESET_STATS_MSG);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract udp_st reset stats data\n", skb);
return -EINVAL;
}
pid = nl_cm->pid;
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nludp_st_rule, cm);
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%p:unable to save response data from NL buffer\n", nl_rule);
return -ENOMEM;
}
nss_ctx = nss_udp_st_get_mgr();
if (!nss_ctx) {
nss_nl_info("Couldn't get udp_st ctx\n");
return -ENOENT;
}
memset(&num, 0, sizeof(struct nss_udp_st_msg));
nss_udp_st_msg_init(&num, /* udp_st message */
NSS_UDP_ST_INTERFACE, /* interface number */
NSS_UDP_ST_RESET_STATS_MSG, /* UDP ST stats reset */
sizeof(struct nss_udp_st_reset_stats), /* message size */
nss_nludp_st_process_resp, /* response callback */
(void *)resp); /* app context */
status = nss_udp_st_tx_sync(nss_ctx, &num);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create udp_st message failed %d\n", nss_ctx, status);
return -EBADMSG;
}
return 0;
}
/*
* nss_nludp_st_ops_tx_destroy
* Destroys UDP speed test Tx node.
*/
static int nss_nludp_st_ops_tx_destroy(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nludp_st_rule *nl_rule;
struct nss_nlcmn *nl_cm;
struct nss_ctx_instance *nss_ctx;
struct nss_udp_st_msg num;
struct sk_buff *resp;
uint32_t pid;
nss_tx_status_t status;
/*
* Extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nludp_st_family, info, NSS_UDP_ST_TX_DESTROY_MSG);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract udp_st tx destroy data\n", skb);
return -EINVAL;
}
pid = nl_cm->pid;
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nludp_st_rule, cm);
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%p:unable to save response data from NL buffer\n", nl_rule);
return -ENOMEM;
}
nss_ctx = nss_udp_st_get_mgr();
if (!nss_ctx) {
nss_nl_info("Couldn't get udp_st ctx\n");
return -ENOENT;
}
memset(&num, 0, sizeof(struct nss_udp_st_msg));
nss_udp_st_msg_init(&num, /* udp_st message */
NSS_UDP_ST_INTERFACE, /* interface number */
NSS_UDP_ST_TX_DESTROY_MSG, /* UDP ST tx destroy */
sizeof(struct nss_udp_st_tx_destroy), /* message size */
nss_nludp_st_process_resp, /* response callback */
(void *)resp); /* app context */
status = nss_udp_st_tx_sync(nss_ctx, &num);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create udp_st message failed %d\n", nss_ctx, status);
return -EBADMSG;
}
return 0;
}
/*
* nss_nludp_st_ops_tx_create
* Creates UDP speed test Tx node.
*/
static int nss_nludp_st_ops_tx_create(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nludp_st_rule *nl_rule;
struct nss_nlcmn *nl_cm;
struct nss_ctx_instance *nss_ctx;
struct nss_udp_st_msg num;
struct sk_buff *resp;
uint32_t pid;
nss_tx_status_t status;
/*
* Extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nludp_st_family, info, NSS_UDP_ST_TX_CREATE_MSG);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract udp_st tx create data\n", skb);
return -EINVAL;
}
pid = nl_cm->pid;
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nludp_st_rule, cm);
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%p:unable to save response data from NL buffer\n", nl_rule);
return -ENOMEM;
}
nss_ctx = nss_udp_st_get_mgr();
if (!nss_ctx) {
nss_nl_info("Couldn't get udp_st ctx\n");
return -ENOENT;
}
memset(&num, 0, sizeof(struct nss_udp_st_msg));
nss_udp_st_msg_init(&num, /* udp_st message */
NSS_UDP_ST_INTERFACE, /* interface number */
NSS_UDP_ST_TX_CREATE_MSG, /* UDP ST tx create */
sizeof(struct nss_udp_st_tx_create), /* message size */
nss_nludp_st_process_resp, /* response callback */
(void *)resp); /* app context */
memcpy(&num.msg.create, &nl_rule->num.msg.create, sizeof(struct nss_udp_st_tx_create));
status = nss_udp_st_tx_sync(nss_ctx, &num);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create udp_st message failed %d\n", nss_ctx, status);
return -EBADMSG;
}
return 0;
}
/*
* nss_nludp_st_get_neigh_ipv4()
* Returns neighbour reference for a given IP address
*/
static struct neighbour *nss_nludp_st_get_neigh_ipv4(uint32_t ip_addr)
{
struct neighbour *neigh;
struct rtable *rt;
struct dst_entry *dst;
/*
* search for route entry
*/
rt = ip_route_output(&init_net, ip_addr, 0, 0, 0);
if (IS_ERR(rt)) {
return NULL;
}
dst = (struct dst_entry *)rt;
/*
* neighbour lookup using IP address in the route table
*/
neigh = dst_neigh_lookup(dst, &ip_addr);
if (likely(neigh)) {
dst_release(dst);
return neigh;
}
/*
* neighbour lookup using IP address, device in the arp table
*/
neigh = neigh_lookup(&arp_tbl, &ip_addr, dst->dev);
if (likely(neigh)) {
dst_release(dst);
return neigh;
}
/*
* dst reference count was held during the lookup
*/
dst_release(dst);
return NULL;
}
/*
* nss_nludp_st_mac_addr_get_ipv4()
* Return the hardware (MAC) address of the given IPv4 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_nludp_st_get_macaddr_ipv4(uint32_t ip_addr, uint8_t mac_addr[])
{
struct neighbour *neigh;
/*
* handle multicast IP address seperately
*/
if (ipv4_is_multicast(htonl(ip_addr))) {
/*
* fixed
*/
mac_addr[0] = 0x01;
mac_addr[1] = 0x00;
mac_addr[2] = 0x5e;
/*
* from ip_addr
*/
mac_addr[3] = (htonl(ip_addr) & 0x7f0000) >> 16;
mac_addr[4] = (htonl(ip_addr) & 0xff00) >> 8;
mac_addr[5] = (htonl(ip_addr) & 0xff);
return 0;
}
/*
* retrieve the neighbour
*/
rcu_read_lock();
neigh = nss_nludp_st_get_neigh_ipv4(htonl(ip_addr));
if (!neigh) {
rcu_read_unlock();
nss_nl_info("neighbour lookup failed for IP:0x%x\n", htonl(ip_addr));
return -ENODEV;
}
rcu_read_unlock();
if ((neigh->nud_state & NUD_VALID) == 0) {
nss_nl_info("neighbour state is invalid for IP:0x%x\n", htonl(ip_addr));
goto fail;
}
if (!neigh->dev) {
nss_nl_info("neighbour device not found for IP:0x%x\n", htonl(ip_addr));
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_nludp_st_get_neigh_ipv6()
* Returns neighbour reference for a given IP address
*/
static struct neighbour *nss_nludp_st_get_neigh_ipv6(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_nludp_st_get_addr_hton()
* Convert the ipv6 address from host order to network order.
*/
static inline void nss_nludp_st_get_addr_hton(uint32_t src[4], uint32_t dst[4])
{
nss_nludp_st_swap_addr_ipv6(src, dst);
dst[0] = htonl(dst[0]);
dst[1] = htonl(dst[1]);
dst[2] = htonl(dst[2]);
dst[3] = htonl(dst[3]);
}
/*
* nss_nludp_st_mac_addr_get_ipv6()
* 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_nludp_st_get_macaddr_ipv6(uint32_t ip_addr[4], uint8_t mac_addr[])
{
struct neighbour *neigh;
struct in6_addr addr;
nss_nludp_st_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_nludp_st_get_neigh_ipv6(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_nludp_st_destroy_ipv4_rule()
* Destroy a nss entry to accelerate the given IPV4 connection
*/
static int nss_nludp_st_destroy_ipv4_rule(struct sk_buff *skb, struct nss_nludp_st_rule *nl_rule)
{
struct nss_ipv4_rule_destroy_msg *nirdm;
struct nss_ctx_instance *nss_ctx;
struct nss_ipv4_msg nim;
struct sk_buff *resp;
nss_tx_status_t status;
nss_ctx = nss_ipv4_get_mgr();
if (!nss_ctx) {
nss_nl_info("%px: Couldn't get IPv4 ctx\n", nl_rule);
return -EPERM;
}
nss_nl_info("%px: IPv4 rule: src:%pI4h:%d dst:%pI4h:%d p:%d\n",
nl_rule, &nl_rule->num.msg.uncfg.src_ip.ip.ipv4, nl_rule->num.msg.uncfg.src_port,
&nl_rule->num.msg.uncfg.dest_ip.ip.ipv4, nl_rule->num.msg.uncfg.dest_port, IPPROTO_UDP);
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%p:unable to save response data from NL buffer\n", nl_rule);
return -ENOMEM;
}
memset(&nim, 0, sizeof(struct nss_ipv4_msg));
nss_ipv4_msg_init(&nim,
NSS_IPV4_RX_INTERFACE,
NSS_IPV4_TX_DESTROY_RULE_MSG,
sizeof(struct nss_ipv4_rule_destroy_msg),
NULL,
NULL);
nirdm = &nim.msg.rule_destroy;
/*
* Copy over the 5 tuple details.
*/
nirdm->tuple.protocol = (uint8_t)IPPROTO_UDP;
nirdm->tuple.flow_ip = nl_rule->num.msg.uncfg.src_ip.ip.ipv4;
nirdm->tuple.flow_ident = (uint32_t)nl_rule->num.msg.uncfg.src_port;
nirdm->tuple.return_ip = nl_rule->num.msg.uncfg.dest_ip.ip.ipv4;
nirdm->tuple.return_ident = (uint32_t)nl_rule->num.msg.uncfg.dest_port;
status = nss_ipv4_tx(nss_ctx, &nim);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Destroy IPv4 message failed %d\n", nss_ctx, status);
return -EPERM;
}
return 0;
}
/*
* nss_nludp_st_create_ipv4_rule()
* Create a nss entry to accelerate the given IPv4 connection
*/
static int nss_nludp_st_create_ipv4_rule(struct sk_buff *skb, struct nss_nludp_st_rule *nl_rule)
{
struct nss_ipv4_rule_create_msg *nircm;
struct nss_ctx_instance *nss_ctx;
struct nss_ipv4_msg nim;
struct net_device *net_dev;
struct nss_ipv4_nexthop *nexthop;
struct sk_buff *resp;
nss_tx_status_t status;
nss_ctx = nss_ipv4_get_mgr();
if (!nss_ctx) {
nss_nl_info("%px: Couldn't get IPv4 ctx\n", nl_rule);
return -EPERM;
}
nss_nl_info("%px: IPv4 rule: src:%pI4h:%d dst:%pI4h:%d p:%d\n",
nl_rule, &nl_rule->num.msg.cfg.src_ip.ip.ipv4, nl_rule->num.msg.cfg.src_port,
&nl_rule->num.msg.cfg.dest_ip.ip.ipv4, nl_rule->num.msg.cfg.dest_port, IPPROTO_UDP);
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%p:unable to save response data from NL buffer\n", nl_rule);
return -ENOMEM;
}
memset(&nim, 0, sizeof(struct nss_ipv4_msg));
nss_ipv4_msg_init(&nim,
NSS_IPV4_RX_INTERFACE,
NSS_IPV4_TX_CREATE_RULE_MSG,
sizeof(struct nss_ipv4_rule_create_msg),
NULL,
NULL);
nircm = &nim.msg.rule_create;
nircm->valid_flags = 0;
nircm->rule_flags = 0;
/*
* Copy over the 5 tuple details.
*/
nircm->tuple.protocol = (uint8_t)IPPROTO_UDP;
nircm->tuple.flow_ip = nl_rule->num.msg.cfg.src_ip.ip.ipv4;
nircm->conn_rule.flow_ip_xlate = nl_rule->num.msg.cfg.src_ip.ip.ipv4;
nircm->tuple.flow_ident = (uint32_t)nl_rule->num.msg.cfg.src_port;
nircm->conn_rule.flow_ident_xlate = (uint32_t)nl_rule->num.msg.cfg.src_port;
nircm->tuple.return_ip = nl_rule->num.msg.cfg.dest_ip.ip.ipv4;
nircm->conn_rule.return_ip_xlate = nl_rule->num.msg.cfg.dest_ip.ip.ipv4;
nircm->tuple.return_ident = (uint32_t)nl_rule->num.msg.cfg.dest_port;
nircm->conn_rule.return_ident_xlate = (uint32_t)nl_rule->num.msg.cfg.dest_port;
/*
* extract netdevice
*/
net_dev = dev_get_by_name(&init_net, nl_rule->gmac_ifname);
if (!net_dev) {
nss_nl_error("%p: net device(%s) is not available\n", nl_rule, nl_rule->gmac_ifname);
return -EINVAL;
}
/*
* Copy over the connection rules and set the CONN_VALID flag
*/
nircm->conn_rule.flow_interface_num = NSS_UDP_ST_INTERFACE;
memcpy(nircm->conn_rule.flow_mac, net_dev->dev_addr, 6);
/*
* Set the MTU values of the flows.
*/
nircm->conn_rule.flow_mtu = NSS_NLUDP_ST_MAX_MTU;
nircm->conn_rule.return_mtu = net_dev->mtu;
/*
* Special case: RAWIP, the destination address does not have MAC Addr.
* So, set it to 0. Also append Core ID if the net device run in a different core.
*/
if (net_dev->type == ARPHRD_RAWIP) {
#if !defined(NSS_NETLINK_UDP_ST_NO_RMNET_SUPPORT)
nircm->conn_rule.return_interface_num = nss_rmnet_rx_get_ifnum(net_dev);
memset(nircm->conn_rule.return_mac, 0, ETH_ALEN);
#else
nss_nl_info("Error. no RAWIP support \n");
dev_put(net_dev);
return -EINVAL;
#endif
} else {
nircm->conn_rule.return_interface_num = nss_cmn_get_interface_number_by_dev(net_dev);
if (nss_nludp_st_get_macaddr_ipv4(nircm->tuple.return_ip, (uint8_t *)&nircm->conn_rule.return_mac)) {
nss_nl_info("Error in Updating the Return MAC Address \n");
dev_put(net_dev);
return -EINVAL;
}
}
nircm->valid_flags |= NSS_IPV4_RULE_CREATE_CONN_VALID;
nexthop = &nircm->nexthop_rule;
nexthop->flow_nexthop = nircm->conn_rule.flow_interface_num;
nexthop->return_nexthop = nircm->conn_rule.return_interface_num;
nss_nl_info("flow_nexthop:%d return_nexthop:%d\n", nexthop->flow_nexthop, nexthop->return_nexthop);
status = nss_ipv4_tx(nss_ctx, &nim);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create IPv4 message failed %d\n", nss_ctx, status);
}
dev_put(net_dev);
return 0;
}
/*
* nss_nludp_st_destroy_ipv6_rule()
* Destroy a nss entry to accelerate the given IPV6 connection
*/
static int nss_nludp_st_destroy_ipv6_rule(struct sk_buff *skb, struct nss_nludp_st_rule *nl_rule)
{
struct nss_ipv6_rule_destroy_msg *nirdm;
struct nss_ctx_instance *nss_ctx;
struct nss_ipv6_msg nim;
struct sk_buff *resp;
nss_tx_status_t status;
nss_ctx = nss_ipv6_get_mgr();
if (!nss_ctx) {
nss_nl_info("%px: Couldn't get IPv6 ctx\n", nl_rule);
return -EPERM;
}
nss_nl_info("%px: IPv6 rule: src:%pI4h:%d dst:%pI4h:%d p:%d\n",
nl_rule, nl_rule->num.msg.uncfg.src_ip.ip.ipv6, nl_rule->num.msg.uncfg.src_port,
nl_rule->num.msg.uncfg.dest_ip.ip.ipv6, nl_rule->num.msg.uncfg.dest_port, IPPROTO_UDP);
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%p:unable to save response data from NL buffer\n", nl_rule);
return -ENOMEM;
}
memset(&nim, 0, sizeof(struct nss_ipv6_msg));
nss_ipv6_msg_init(&nim,
NSS_IPV6_RX_INTERFACE,
NSS_IPV6_TX_DESTROY_RULE_MSG,
sizeof(struct nss_ipv6_rule_destroy_msg),
NULL,
NULL);
nirdm = &nim.msg.rule_destroy;
/*
* Copy over the 5 tuple details.
*/
nirdm->tuple.protocol = (uint8_t)IPPROTO_UDP;
memcpy(nirdm->tuple.flow_ip, nl_rule->num.msg.uncfg.src_ip.ip.ipv6, sizeof(uint32_t) * 4);
nirdm->tuple.flow_ident = (uint32_t)nl_rule->num.msg.uncfg.src_port;
memcpy(nirdm->tuple.return_ip, nl_rule->num.msg.uncfg.dest_ip.ip.ipv6, sizeof(uint32_t) * 4);
nirdm->tuple.return_ident = (uint32_t)nl_rule->num.msg.uncfg.dest_port;
status = nss_ipv6_tx(nss_ctx, &nim);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Destroy IPv6 message failed %d\n", nss_ctx, status);
return -EPERM;
}
return 0;
}
/*
* nss_nludp_st_create_ipv6_rule()
* Create a nss entry to accelerate the given IPV6 connection
*/
static int nss_nludp_st_create_ipv6_rule(struct sk_buff *skb, struct nss_nludp_st_rule *nl_rule)
{
struct nss_ipv6_rule_create_msg *nircm;
struct nss_ctx_instance *nss_ctx;
struct nss_ipv6_msg nim;
struct net_device *net_dev;
struct nss_ipv6_nexthop *nexthop;
struct sk_buff *resp;
nss_tx_status_t status;
nss_ctx = nss_ipv6_get_mgr();
if (!nss_ctx) {
nss_nl_info("%px: Couldn't get IPv6 ctx\n", nl_rule);
return -EPERM;
}
nss_nl_info("%px: Create IPv6 rule: %pI6:%d %pI6:%d p:%d\n",
nl_rule, nl_rule->num.msg.cfg.src_ip.ip.ipv6, nl_rule->num.msg.cfg.src_port,
nl_rule->num.msg.cfg.dest_ip.ip.ipv6, nl_rule->num.msg.cfg.dest_port, IPPROTO_UDP);
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("%p:unable to save response data from NL buffer\n", nl_rule);
return -ENOMEM;
}
memset(&nim, 0, sizeof(struct nss_ipv6_msg));
nss_ipv6_msg_init(&nim,
NSS_IPV6_RX_INTERFACE,
NSS_IPV6_TX_CREATE_RULE_MSG,
sizeof(struct nss_ipv6_rule_create_msg),
NULL,
NULL);
nircm = &nim.msg.rule_create;
nircm->rule_flags = 0;
nircm->valid_flags = 0;
/*
* Copy over the 5 tuple information.
*/
nircm->tuple.protocol = (uint8_t)IPPROTO_UDP;
memcpy(nircm->tuple.flow_ip, nl_rule->num.msg.cfg.src_ip.ip.ipv6, sizeof(nircm->tuple.flow_ip));
memcpy(nircm->tuple.return_ip, nl_rule->num.msg.cfg.dest_ip.ip.ipv6, sizeof(nircm->tuple.return_ip));
nircm->tuple.flow_ident = (uint32_t)nl_rule->num.msg.cfg.src_port;
nircm->tuple.return_ident = (uint32_t)nl_rule->num.msg.cfg.dest_port;
/*
* extract netdevice
*/
net_dev = dev_get_by_name(&init_net, nl_rule->gmac_ifname);
if (!net_dev) {
nss_nl_error("%p: net device(%s) is not available\n", nl_rule, nl_rule->gmac_ifname);
return -EINVAL;
}
/*
* Copy over the connection rules and set CONN_VALID flag
*/
nircm->conn_rule.flow_interface_num = NSS_UDP_ST_INTERFACE;
memcpy(nircm->conn_rule.flow_mac, net_dev->dev_addr, 6);
/*
* Special case: RAWIP, the destination address does not have MAC Addr.
* So, set it to 0. Also append Core ID if the net device run in a different core.
*/
if (net_dev->type == ARPHRD_RAWIP) {
#if !defined(NSS_NETLINK_UDP_ST_NO_RMNET_SUPPORT)
nircm->conn_rule.return_interface_num = nss_rmnet_rx_get_ifnum(net_dev);
memset(nircm->conn_rule.return_mac, 0, ETH_ALEN);
#else
nss_nl_info("Error. no RAWIP support \n");
dev_put(net_dev);
return -EINVAL;
#endif
} else {
nircm->conn_rule.return_interface_num = nss_cmn_get_interface_number_by_dev(net_dev);
if (nss_nludp_st_get_macaddr_ipv6(nircm->tuple.return_ip, (uint8_t *)&nircm->conn_rule.return_mac)) {
nss_nl_info("Error in Updating the Return MAC Address \n");
dev_put(net_dev);
return -EINVAL;
}
}
nircm->valid_flags |= NSS_IPV6_RULE_CREATE_CONN_VALID;
nexthop = &nircm->nexthop_rule;
nexthop->flow_nexthop = nircm->conn_rule.flow_interface_num;
nexthop->return_nexthop = nircm->conn_rule.return_interface_num;
nss_nl_info("flow_nexthop:%d return_nexthop:%d\n", nexthop->flow_nexthop, nexthop->return_nexthop);
status = nss_ipv6_tx(nss_ctx, &nim);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create IPv6 message failed %d\n", nss_ctx, status);
return -EPERM;
}
dev_put(net_dev);
return 0;
}
/*
* nss_nludp_st_uncfg_udp_st_rule()
* Unconfigures the entry from udp_st
*/
static int nss_nludp_st_uncfg_udp_st_rule(struct sk_buff *skb, struct nss_udp_st_cfg *uncfg)
{
struct nss_ctx_instance *nss_ctx;
struct nss_udp_st_msg num;
struct sk_buff *resp;
nss_tx_status_t status;
int ret = 0;
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("unable to save response data from NL buffer\n");
return -ENOMEM;
}
nss_ctx = nss_udp_st_get_mgr();
if (!nss_ctx) {
nss_nl_info("Couldn't get udp_st ctx\n");
return -EPERM;
}
memset(&num, 0, sizeof(struct nss_udp_st_msg));
nss_udp_st_msg_init(&num, NSS_UDP_ST_INTERFACE,
NSS_UDP_ST_UNCFG_RULE_MSG,
sizeof(struct nss_udp_st_cfg),
nss_nludp_st_process_resp,
(void *)resp);
memcpy(&num.msg.uncfg, uncfg, sizeof(struct nss_udp_st_cfg));
status = nss_udp_st_tx_sync(nss_ctx, &num);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create udp_st message failed %d\n", nss_ctx, status);
return -EBADMSG;
}
return ret;
}
/*
* nss_nludp_st_cfg_udp_st_rule()
* Configures the entry from udp_st
*/
static int nss_nludp_st_cfg_udp_st_rule(struct sk_buff *skb, struct nss_udp_st_cfg *cfg)
{
struct nss_ctx_instance *nss_ctx;
struct nss_udp_st_msg num;
struct sk_buff *resp;
nss_tx_status_t status;
int ret = 0;
/*
* copy the NL message for response
*/
resp = nss_nl_copy_msg(skb);
if (!resp) {
nss_nl_error("unable to save response data from NL buffer\n");
return -ENOMEM;
}
nss_ctx = nss_udp_st_get_mgr();
if (!nss_ctx) {
nss_nl_info("Couldn't get udp_st ctx\n");
return -EPERM;
}
memset(&num, 0, sizeof(struct nss_udp_st_msg));
nss_udp_st_msg_init(&num, NSS_UDP_ST_INTERFACE,
NSS_UDP_ST_CFG_RULE_MSG,
sizeof(struct nss_udp_st_cfg),
nss_nludp_st_process_resp,
(void *)resp);
memcpy(&num.msg.cfg, cfg, sizeof(struct nss_udp_st_cfg));
status = nss_udp_st_tx_sync(nss_ctx, &num);
if (status != NSS_TX_SUCCESS) {
nss_nl_info("%px: Create udp_st message failed %d\n", nss_ctx, status);
return -EBADMSG;
}
return ret;
}
/*
* nss_nludp_st_ops_uncfg_rule
* Handler for unconfiguring rules
*/
static int nss_nludp_st_ops_uncfg_rule(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nludp_st_rule *nl_rule;
struct nss_nlcmn *nl_cm;
int ret = 0;
/*
* Extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nludp_st_family, info, NSS_UDP_ST_UNCFG_RULE_MSG);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract unconfigure rule data\n", skb);
return -EINVAL;
}
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nludp_st_rule, cm);
/*
* Unconfigure udp_st only for the transmit node.
* It is sent for received node to just get an ACK for this uncfg rule.
*/
ret = nss_nludp_st_uncfg_udp_st_rule(skb, &nl_rule->num.msg.uncfg);
if (ret < 0) {
nss_nl_info("%px: Unable to remove a rule entry for udp st\n", skb);
return -EBADMSG;
}
/*
* Destroy rule based on ip version
*/
if (nl_rule->num.msg.uncfg.ip_version == NSS_UDP_ST_FLAG_IPV4) {
ret = nss_nludp_st_destroy_ipv4_rule(skb, nl_rule);
} else if (nl_rule->num.msg.uncfg.ip_version == NSS_UDP_ST_FLAG_IPV6) {
ret = nss_nludp_st_destroy_ipv6_rule(skb, nl_rule);
} else {
goto fail;
}
if (ret < 0) {
nss_nl_error("%px: Unable to delete a rule entry for ipv%d.\n", skb, nl_rule->num.msg.uncfg.ip_version);
goto fail;
}
return ret;
fail:
/*
* num.msg.cfg and num.msg.uncfg are of same type, struct nss_udp_st_cfg
*/
if (nl_rule->num.msg.uncfg.type == NSS_UDP_ST_TEST_TX) {
nss_nludp_st_cfg_udp_st_rule(skb, &nl_rule->num.msg.uncfg);
}
return -EAGAIN;
}
/*
* nss_nludp_st_ops_cfg_rule()
* Handler for configuring rules
*/
static int nss_nludp_st_ops_cfg_rule(struct sk_buff *skb, struct genl_info *info)
{
struct nss_nludp_st_rule *nl_rule;
struct nss_nlcmn *nl_cm;
int ret = 0;
/*
* Extract the message payload
*/
nl_cm = nss_nl_get_msg(&nss_nludp_st_family, info, NSS_UDP_ST_CFG_RULE_MSG);
if (!nl_cm) {
nss_nl_error("%px: Unable to extract configure rule data\n", skb);
return -EINVAL;
}
/*
* Message validation required before accepting the configuration
*/
nl_rule = container_of(nl_cm, struct nss_nludp_st_rule, cm);
/*
* Configures udp_st only for the transmit node.
* It is sent for received node to just get an ACK for this cfg rule.
*/
ret = nss_nludp_st_cfg_udp_st_rule(skb, &nl_rule->num.msg.cfg);
if (ret < 0) {
nss_nl_info("%px: Unable to add a rule entry for udp st\n", skb);
return -EBADMSG;
}
/*
* Create rule based on ip version
*/
if (nl_rule->num.msg.cfg.ip_version == NSS_UDP_ST_FLAG_IPV4) {
ret = nss_nludp_st_create_ipv4_rule(skb, nl_rule);
} else if (nl_rule->num.msg.cfg.ip_version == NSS_UDP_ST_FLAG_IPV6) {
ret = nss_nludp_st_create_ipv6_rule(skb, nl_rule);
} else {
goto fail;
}
if (ret < 0) {
nss_nl_error("%px: Unable to add a rule entry for ipv%d.\n", skb, nl_rule->num.msg.cfg.ip_version);
goto fail;
}
return 0;
fail:
/*
* num.msg.cfg and num.msg.uncfg are of same type, struct nss_udp_st_cfg
*/
if (nl_rule->num.msg.cfg.type == NSS_UDP_ST_TEST_TX) {
nss_nludp_st_uncfg_udp_st_rule(skb, &nl_rule->num.msg.cfg);
}
return -EAGAIN;
}
/*
* nss_nludp_st_exit()
* handler exit
*/
bool nss_nludp_st_exit(void)
{
int error;
nss_nl_info_always("Exit NSS netlink udp_st handler\n");
/*
* unregister the ops family
*/
error = genl_unregister_family(&nss_nludp_st_family);
if (error) {
nss_nl_info_always("unable to unregister udp_st NETLINK family\n");
return false;
}
return true;
}
/*
* nss_nludp_st_init()
* handler init
*/
bool nss_nludp_st_init(void)
{
int error;
nss_nl_info_always("Init NSS netlink udp_st handler\n");
/*
* register Netlink ops with the family
*/
error = genl_register_family(&nss_nludp_st_family);
if (error) {
nss_nl_info_always("Error: unable to register udp_st family\n");
return false;
}
return true;
}