// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* -
* net/sched/act_ct.c Connection Tracking action
*
* Authors: Paul Blakey <paulb@mellanox.com>
* Yossi Kuperman <yossiku@mellanox.com>
* Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/rtnetlink.h>
#include <linux/pkt_cls.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/rhashtable.h>
#include <net/netlink.h>
#include <net/pkt_sched.h>
#include <net/pkt_cls.h>
#include <net/act_api.h>
#include <net/ip.h>
#include <net/ipv6_frag.h>
#include <uapi/linux/tc_act/tc_ct.h>
#include <net/tc_act/tc_ct.h>
#include <net/netfilter/nf_flow_table.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_zones.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/ipv6/nf_defrag_ipv6.h>
#include <uapi/linux/netfilter/nf_nat.h>
static struct workqueue_struct *act_ct_wq;
static struct rhashtable zones_ht;
static DEFINE_MUTEX(zones_mutex);
struct tcf_ct_flow_table {
struct rhash_head node; /* In zones tables */
struct rcu_work rwork;
struct nf_flowtable nf_ft;
refcount_t ref;
u16 zone;
bool dying;
};
static const struct rhashtable_params zones_params = {
.head_offset = offsetof(struct tcf_ct_flow_table, node),
.key_offset = offsetof(struct tcf_ct_flow_table, zone),
.key_len = sizeof_field(struct tcf_ct_flow_table, zone),
.automatic_shrinking = true,
};
static struct flow_action_entry *
tcf_ct_flow_table_flow_action_get_next(struct flow_action *flow_action)
{
int i = flow_action->num_entries++;
return &flow_action->entries[i];
}
static void tcf_ct_add_mangle_action(struct flow_action *action,
enum flow_action_mangle_base htype,
u32 offset,
u32 mask,
u32 val)
{
struct flow_action_entry *entry;
entry = tcf_ct_flow_table_flow_action_get_next(action);
entry->id = FLOW_ACTION_MANGLE;
entry->mangle.htype = htype;
entry->mangle.mask = ~mask;
entry->mangle.offset = offset;
entry->mangle.val = val;
}
/* The following nat helper functions check if the inverted reverse tuple
* (target) is different then the current dir tuple - meaning nat for ports
* and/or ip is needed, and add the relevant mangle actions.
*/
static void
tcf_ct_flow_table_add_action_nat_ipv4(const struct nf_conntrack_tuple *tuple,
struct nf_conntrack_tuple target,
struct flow_action *action)
{
if (memcmp(&target.src.u3, &tuple->src.u3, sizeof(target.src.u3)))
tcf_ct_add_mangle_action(action, FLOW_ACT_MANGLE_HDR_TYPE_IP4,
offsetof(struct iphdr, saddr),
0xFFFFFFFF,
be32_to_cpu(target.src.u3.ip));
if (memcmp(&target.dst.u3, &tuple->dst.u3, sizeof(target.dst.u3)))
tcf_ct_add_mangle_action(action, FLOW_ACT_MANGLE_HDR_TYPE_IP4,
offsetof(struct iphdr, daddr),
0xFFFFFFFF,
be32_to_cpu(target.dst.u3.ip));
}
static void
tcf_ct_add_ipv6_addr_mangle_action(struct flow_action *action,
union nf_inet_addr *addr,
u32 offset)
{
int i;
for (i = 0; i < sizeof(struct in6_addr) / sizeof(u32); i++)
tcf_ct_add_mangle_action(action, FLOW_ACT_MANGLE_HDR_TYPE_IP6,
i * sizeof(u32) + offset,
0xFFFFFFFF, be32_to_cpu(addr->ip6[i]));
}
static void
tcf_ct_flow_table_add_action_nat_ipv6(const struct nf_conntrack_tuple *tuple,
struct nf_conntrack_tuple target,
struct flow_action *action)
{
if (memcmp(&target.src.u3, &tuple->src.u3, sizeof(target.src.u3)))
tcf_ct_add_ipv6_addr_mangle_action(action, &target.src.u3,
offsetof(struct ipv6hdr,
saddr));
if (memcmp(&target.dst.u3, &tuple->dst.u3, sizeof(target.dst.u3)))
tcf_ct_add_ipv6_addr_mangle_action(action, &target.dst.u3,
offsetof(struct ipv6hdr,
daddr));
}
static void
tcf_ct_flow_table_add_action_nat_tcp(const struct nf_conntrack_tuple *tuple,
struct nf_conntrack_tuple target,
struct flow_action *action)
{
__be16 target_src = target.src.u.tcp.port;
__be16 target_dst = target.dst.u.tcp.port;
if (target_src != tuple->src.u.tcp.port)
tcf_ct_add_mangle_action(action, FLOW_ACT_MANGLE_HDR_TYPE_TCP,
offsetof(struct tcphdr, source),
0xFFFF, be16_to_cpu(target_src));
if (target_dst != tuple->dst.u.tcp.port)
tcf_ct_add_mangle_action(action, FLOW_ACT_MANGLE_HDR_TYPE_TCP,
offsetof(struct tcphdr, dest),
0xFFFF, be16_to_cpu(target_dst));
}
static void
tcf_ct_flow_table_add_action_nat_udp(const struct nf_conntrack_tuple *tuple,
struct nf_conntrack_tuple target,
struct flow_action *action)
{
__be16 target_src = target.src.u.udp.port;
__be16 target_dst = target.dst.u.udp.port;
if (target_src != tuple->src.u.udp.port)
tcf_ct_add_mangle_action(action, FLOW_ACT_MANGLE_HDR_TYPE_TCP,
offsetof(struct udphdr, source),
0xFFFF, be16_to_cpu(target_src));
if (target_dst != tuple->dst.u.udp.port)
tcf_ct_add_mangle_action(action, FLOW_ACT_MANGLE_HDR_TYPE_TCP,
offsetof(struct udphdr, dest),
0xFFFF, be16_to_cpu(target_dst));
}
static void tcf_ct_flow_table_add_action_meta(struct nf_conn *ct,
enum ip_conntrack_dir dir,
struct flow_action *action)
{
struct nf_conn_labels *ct_labels;
struct flow_action_entry *entry;
enum ip_conntrack_info ctinfo;
u32 *act_ct_labels;
entry = tcf_ct_flow_table_flow_action_get_next(action);
entry->id = FLOW_ACTION_CT_METADATA;
#if IS_ENABLED(CONFIG_NF_CONNTRACK_MARK)
entry->ct_metadata.mark = ct->mark;
#endif
ctinfo = dir == IP_CT_DIR_ORIGINAL ? IP_CT_ESTABLISHED :
IP_CT_ESTABLISHED_REPLY;
/* aligns with the CT reference on the SKB nf_ct_set */
entry->ct_metadata.cookie = (unsigned long)ct | ctinfo;
act_ct_labels = entry->ct_metadata.labels;
ct_labels = nf_ct_labels_find(ct);
if (ct_labels)
memcpy(act_ct_labels, ct_labels->bits, NF_CT_LABELS_MAX_SIZE);
else
memset(act_ct_labels, 0, NF_CT_LABELS_MAX_SIZE);
}
static int tcf_ct_flow_table_add_action_nat(struct net *net,
struct nf_conn *ct,
enum ip_conntrack_dir dir,
struct flow_action *action)
{
const struct nf_conntrack_tuple *tuple = &ct->tuplehash[dir].tuple;
struct nf_conntrack_tuple target;
nf_
|