Skip to content

Commit

Permalink
Merge branch 'net-add-helper-support-in-tc-act_ct-for-ovs-offloading'
Browse files Browse the repository at this point in the history
Xin Long says:

====================
net: add helper support in tc act_ct for ovs offloading

Ilya reported an issue that FTP traffic would be broken when the OVS flow
with ct(commit,alg=ftp) installed in the OVS kernel module, and it was
caused by that TC didn't support the ftp helper offloaded from OVS.

This patchset is to add the helper support in act_ct for OVS offloading
in kernel net/sched.

The 1st and 2nd patches move some common code into nf_conntrack_helper from
openvswitch so that they could be used by net/sched in the 4th patch (Note
there are still some other common code used in both OVS and TC, and I will
extract it in other patches). The 3rd patch extracts another function in
net/sched to make the 4th patch easier to write. The 4th patch adds this
feature in net/sched.

The user space part will be added in another patch, and with it these OVS
flows (FTP over SNAT) can be used to test this feature:

  table=0, in_port=veth1,tcp,tcp_dst=2121,ct_state=-trk \
    actions=ct(table=1, nat), normal
  table=0, in_port=veth2,tcp,ct_state=-trk actions=ct(table=1, nat)
  table=0, in_port=veth1,tcp,ct_state=-trk actions=ct(table=0, nat)
  table=0, in_port=veth1,tcp,ct_state=+trk+rel actions=ct(commit, nat),normal
  table=0, in_port=veth1,tcp,ct_state=+trk+est actions=veth2"

  table=1, in_port=veth1,tcp,tcp_dst=2121,ct_state=+trk+new \
    actions=ct(commit, nat(src=7.7.16.1), alg=ftp),normal"
  table=1, in_port=veth1,tcp,tcp_dst=2121,ct_state=+trk+est actions=veth2"
  table=1, in_port=veth2,tcp,ct_state=+trk+est actions=veth1"

====================

Link: https://lore.kernel.org/r/cover.1667766782.git.lucien.xin@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
  • Loading branch information
Paolo Abeni committed Nov 8, 2022
2 parents 47f3ecf + a21b06e commit b2140e9
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 124 deletions.
5 changes: 5 additions & 0 deletions include/net/netfilter/nf_conntrack_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ struct nf_conn_help *nf_ct_helper_ext_add(struct nf_conn *ct, gfp_t gfp);
int __nf_ct_try_assign_helper(struct nf_conn *ct, struct nf_conn *tmpl,
gfp_t flags);

int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct,
enum ip_conntrack_info ctinfo, u16 proto);
int nf_ct_add_helper(struct nf_conn *ct, const char *name, u8 family,
u8 proto, bool nat, struct nf_conntrack_helper **hp);

void nf_ct_helper_destroy(struct nf_conn *ct);

static inline struct nf_conn_help *nfct_help(const struct nf_conn *ct)
Expand Down
1 change: 1 addition & 0 deletions include/net/tc_act/tc_ct.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <net/netfilter/nf_conntrack_labels.h>

struct tcf_ct_params {
struct nf_conntrack_helper *helper;
struct nf_conn *tmpl;
u16 zone;

Expand Down
3 changes: 3 additions & 0 deletions include/uapi/linux/tc_act/tc_ct.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ enum {
TCA_CT_NAT_PORT_MIN, /* be16 */
TCA_CT_NAT_PORT_MAX, /* be16 */
TCA_CT_PAD,
TCA_CT_HELPER_NAME, /* string */
TCA_CT_HELPER_FAMILY, /* u8 */
TCA_CT_HELPER_PROTO, /* u8 */
__TCA_CT_MAX
};

Expand Down
100 changes: 100 additions & 0 deletions net/netfilter/nf_conntrack_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
#include <net/netfilter/nf_conntrack_extend.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/nf_conntrack_l4proto.h>
#include <net/netfilter/nf_conntrack_seqadj.h>
#include <net/netfilter/nf_log.h>
#include <net/ip.h>

static DEFINE_MUTEX(nf_ct_helper_mutex);
struct hlist_head *nf_ct_helper_hash __read_mostly;
Expand Down Expand Up @@ -240,6 +242,104 @@ int __nf_ct_try_assign_helper(struct nf_conn *ct, struct nf_conn *tmpl,
}
EXPORT_SYMBOL_GPL(__nf_ct_try_assign_helper);

/* 'skb' should already be pulled to nh_ofs. */
int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct,
enum ip_conntrack_info ctinfo, u16 proto)
{
const struct nf_conntrack_helper *helper;
const struct nf_conn_help *help;
unsigned int protoff;
int err;

if (ctinfo == IP_CT_RELATED_REPLY)
return NF_ACCEPT;

help = nfct_help(ct);
if (!help)
return NF_ACCEPT;

helper = rcu_dereference(help->helper);
if (!helper)
return NF_ACCEPT;

if (helper->tuple.src.l3num != NFPROTO_UNSPEC &&
helper->tuple.src.l3num != proto)
return NF_ACCEPT;

switch (proto) {
case NFPROTO_IPV4:
protoff = ip_hdrlen(skb);
proto = ip_hdr(skb)->protocol;
break;
case NFPROTO_IPV6: {
u8 nexthdr = ipv6_hdr(skb)->nexthdr;
__be16 frag_off;
int ofs;

ofs = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr,
&frag_off);
if (ofs < 0 || (frag_off & htons(~0x7)) != 0) {
pr_debug("proto header not found\n");
return NF_ACCEPT;
}
protoff = ofs;
proto = nexthdr;
break;
}
default:
WARN_ONCE(1, "helper invoked on non-IP family!");
return NF_DROP;
}

if (helper->tuple.dst.protonum != proto)
return NF_ACCEPT;

err = helper->help(skb, protoff, ct, ctinfo);
if (err != NF_ACCEPT)
return err;

/* Adjust seqs after helper. This is needed due to some helpers (e.g.,
* FTP with NAT) adusting the TCP payload size when mangling IP
* addresses and/or port numbers in the text-based control connection.
*/
if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) &&
!nf_ct_seq_adjust(skb, ct, ctinfo, protoff))
return NF_DROP;
return NF_ACCEPT;
}
EXPORT_SYMBOL_GPL(nf_ct_helper);

int nf_ct_add_helper(struct nf_conn *ct, const char *name, u8 family,
u8 proto, bool nat, struct nf_conntrack_helper **hp)
{
struct nf_conntrack_helper *helper;
struct nf_conn_help *help;
int ret = 0;

helper = nf_conntrack_helper_try_module_get(name, family, proto);
if (!helper)
return -EINVAL;

help = nf_ct_helper_ext_add(ct, GFP_KERNEL);
if (!help) {
nf_conntrack_helper_put(helper);
return -ENOMEM;
}
#if IS_ENABLED(CONFIG_NF_NAT)
if (nat) {
ret = nf_nat_helper_try_module_get(name, family, proto);
if (ret) {
nf_conntrack_helper_put(helper);
return ret;
}
}
#endif
rcu_assign_pointer(help->helper, helper);
*hp = helper;
return ret;
}
EXPORT_SYMBOL_GPL(nf_ct_add_helper);

/* appropriate ct lock protecting must be taken by caller */
static int unhelp(struct nf_conn *ct, void *me)
{
Expand Down
105 changes: 6 additions & 99 deletions net/openvswitch/conntrack.c
Original file line number Diff line number Diff line change
Expand Up @@ -434,65 +434,6 @@ static int ovs_ct_set_labels(struct nf_conn *ct, struct sw_flow_key *key,
return 0;
}

/* 'skb' should already be pulled to nh_ofs. */
static int ovs_ct_helper(struct sk_buff *skb, u16 proto)
{
const struct nf_conntrack_helper *helper;
const struct nf_conn_help *help;
enum ip_conntrack_info ctinfo;
unsigned int protoff;
struct nf_conn *ct;
int err;

ct = nf_ct_get(skb, &ctinfo);
if (!ct || ctinfo == IP_CT_RELATED_REPLY)
return NF_ACCEPT;

help = nfct_help(ct);
if (!help)
return NF_ACCEPT;

helper = rcu_dereference(help->helper);
if (!helper)
return NF_ACCEPT;

switch (proto) {
case NFPROTO_IPV4:
protoff = ip_hdrlen(skb);
break;
case NFPROTO_IPV6: {
u8 nexthdr = ipv6_hdr(skb)->nexthdr;
__be16 frag_off;
int ofs;

ofs = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr,
&frag_off);
if (ofs < 0 || (frag_off & htons(~0x7)) != 0) {
pr_debug("proto header not found\n");
return NF_ACCEPT;
}
protoff = ofs;
break;
}
default:
WARN_ONCE(1, "helper invoked on non-IP family!");
return NF_DROP;
}

err = helper->help(skb, protoff, ct, ctinfo);
if (err != NF_ACCEPT)
return err;

/* Adjust seqs after helper. This is needed due to some helpers (e.g.,
* FTP with NAT) adusting the TCP payload size when mangling IP
* addresses and/or port numbers in the text-based control connection.
*/
if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) &&
!nf_ct_seq_adjust(skb, ct, ctinfo, protoff))
return NF_DROP;
return NF_ACCEPT;
}

/* Returns 0 on success, -EINPROGRESS if 'skb' is stolen, or other nonzero
* value if 'skb' is freed.
*/
Expand Down Expand Up @@ -1038,7 +979,7 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key,
*/
if ((nf_ct_is_confirmed(ct) ? !cached || add_helper :
info->commit) &&
ovs_ct_helper(skb, info->family) != NF_ACCEPT) {
nf_ct_helper(skb, ct, ctinfo, info->family) != NF_ACCEPT) {
return -EINVAL;
}

Expand Down Expand Up @@ -1350,43 +1291,6 @@ int ovs_ct_clear(struct sk_buff *skb, struct sw_flow_key *key)
return 0;
}

static int ovs_ct_add_helper(struct ovs_conntrack_info *info, const char *name,
const struct sw_flow_key *key, bool log)
{
struct nf_conntrack_helper *helper;
struct nf_conn_help *help;
int ret = 0;

helper = nf_conntrack_helper_try_module_get(name, info->family,
key->ip.proto);
if (!helper) {
OVS_NLERR(log, "Unknown helper \"%s\"", name);
return -EINVAL;
}

help = nf_ct_helper_ext_add(info->ct, GFP_KERNEL);
if (!help) {
nf_conntrack_helper_put(helper);
return -ENOMEM;
}

#if IS_ENABLED(CONFIG_NF_NAT)
if (info->nat) {
ret = nf_nat_helper_try_module_get(name, info->family,
key->ip.proto);
if (ret) {
nf_conntrack_helper_put(helper);
OVS_NLERR(log, "Failed to load \"%s\" NAT helper, error: %d",
name, ret);
return ret;
}
}
#endif
rcu_assign_pointer(help->helper, helper);
info->helper = helper;
return ret;
}

#if IS_ENABLED(CONFIG_NF_NAT)
static int parse_nat(const struct nlattr *attr,
struct ovs_conntrack_info *info, bool log)
Expand Down Expand Up @@ -1720,9 +1624,12 @@ int ovs_ct_copy_action(struct net *net, const struct nlattr *attr,
}

if (helper) {
err = ovs_ct_add_helper(&ct_info, helper, key, log);
if (err)
err = nf_ct_add_helper(ct_info.ct, helper, ct_info.family,
key->ip.proto, ct_info.nat, &ct_info.helper);
if (err) {
OVS_NLERR(log, "Failed to add %s helper %d", helper, err);
goto err_free_ct;
}
}

err = ovs_nla_add_action(sfa, OVS_ACTION_ATTR_CT, &ct_info,
Expand Down
Loading

0 comments on commit b2140e9

Please sign in to comment.