Skip to content

Commit

Permalink
openvswitch: Allow matching on conntrack label
Browse files Browse the repository at this point in the history
Allow matching and setting the ct_label field. As with ct_mark, this is
populated by executing the CT action. The label field may be modified by
specifying a label and mask nested under the CT action. It is stored as
metadata attached to the connection. Label modification occurs after
lookup, and will only persist when the conntrack entry is committed by
providing the COMMIT flag to the CT action. Labels are currently fixed
to 128 bits in size.

Signed-off-by: Joe Stringer <joestringer@nicira.com>
Acked-by: Thomas Graf <tgraf@suug.ch>
Acked-by: Pravin B Shelar <pshelar@nicira.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Joe Stringer authored and David S. Miller committed Aug 27, 2015
1 parent 86ca02e commit c2ac667
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 34 deletions.
10 changes: 10 additions & 0 deletions include/uapi/linux/openvswitch.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ enum ovs_key_attr {
OVS_KEY_ATTR_CT_STATE, /* u8 bitmask of OVS_CS_F_* */
OVS_KEY_ATTR_CT_ZONE, /* u16 connection tracking zone. */
OVS_KEY_ATTR_CT_MARK, /* u32 connection tracking mark */
OVS_KEY_ATTR_CT_LABEL, /* 16-octet connection tracking label */

#ifdef __KERNEL__
OVS_KEY_ATTR_TUNNEL_INFO, /* struct ip_tunnel_info */
Expand Down Expand Up @@ -438,6 +439,11 @@ struct ovs_key_nd {
__u8 nd_tll[ETH_ALEN];
};

#define OVS_CT_LABEL_LEN 16
struct ovs_key_ct_label {
__u8 ct_label[OVS_CT_LABEL_LEN];
};

/* OVS_KEY_ATTR_CT_STATE flags */
#define OVS_CS_F_NEW 0x01 /* Beginning of a new connection. */
#define OVS_CS_F_ESTABLISHED 0x02 /* Part of an existing connection. */
Expand Down Expand Up @@ -617,12 +623,16 @@ struct ovs_action_hash {
* @OVS_CT_ATTR_MARK: u32 value followed by u32 mask. For each bit set in the
* mask, the corresponding bit in the value is copied to the connection
* tracking mark field in the connection.
* @OVS_CT_ATTR_LABEL: %OVS_CT_LABEL_LEN value followed by %OVS_CT_LABEL_LEN
* mask. For each bit set in the mask, the corresponding bit in the value is
* copied to the connection tracking label field in the connection.
*/
enum ovs_ct_attr {
OVS_CT_ATTR_UNSPEC,
OVS_CT_ATTR_FLAGS, /* u8 bitmask of OVS_CT_F_*. */
OVS_CT_ATTR_ZONE, /* u16 zone id. */
OVS_CT_ATTR_MARK, /* mark to associate with this connection. */
OVS_CT_ATTR_LABEL, /* label to associate with this connection. */
__OVS_CT_ATTR_MAX
};

Expand Down
1 change: 1 addition & 0 deletions net/openvswitch/actions.c
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,7 @@ static int execute_masked_set_action(struct sk_buff *skb,
case OVS_KEY_ATTR_CT_STATE:
case OVS_KEY_ATTR_CT_ZONE:
case OVS_KEY_ATTR_CT_MARK:
case OVS_KEY_ATTR_CT_LABEL:
err = -EINVAL;
break;
}
Expand Down
128 changes: 126 additions & 2 deletions net/openvswitch/conntrack.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <linux/openvswitch.h>
#include <net/ip.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_labels.h>
#include <net/netfilter/nf_conntrack_zones.h>
#include <net/netfilter/ipv6/nf_defrag_ipv6.h>

Expand All @@ -34,13 +35,20 @@ struct md_mark {
u32 mask;
};

/* Metadata label for masked write to conntrack label. */
struct md_label {
struct ovs_key_ct_label value;
struct ovs_key_ct_label mask;
};

/* Conntrack action context for execution. */
struct ovs_conntrack_info {
struct nf_conntrack_zone zone;
struct nf_conn *ct;
u32 flags;
u16 family;
struct md_mark mark;
struct md_label label;
};

static u16 key_to_nfproto(const struct sw_flow_key *key)
Expand Down Expand Up @@ -90,13 +98,32 @@ static u8 ovs_ct_get_state(enum ip_conntrack_info ctinfo)
return ct_state;
}

static void ovs_ct_get_label(const struct nf_conn *ct,
struct ovs_key_ct_label *label)
{
struct nf_conn_labels *cl = ct ? nf_ct_labels_find(ct) : NULL;

if (cl) {
size_t len = cl->words * sizeof(long);

if (len > OVS_CT_LABEL_LEN)
len = OVS_CT_LABEL_LEN;
else if (len < OVS_CT_LABEL_LEN)
memset(label, 0, OVS_CT_LABEL_LEN);
memcpy(label, cl->bits, len);
} else {
memset(label, 0, OVS_CT_LABEL_LEN);
}
}

static void __ovs_ct_update_key(struct sw_flow_key *key, u8 state,
const struct nf_conntrack_zone *zone,
const struct nf_conn *ct)
{
key->ct.state = state;
key->ct.zone = zone->id;
key->ct.mark = ct ? ct->mark : 0;
ovs_ct_get_label(ct, &key->ct.label);
}

/* Update 'key' based on skb->nfct. If 'post_ct' is true, then OVS has
Expand Down Expand Up @@ -140,6 +167,11 @@ int ovs_ct_put_key(const struct sw_flow_key *key, struct sk_buff *skb)
nla_put_u32(skb, OVS_KEY_ATTR_CT_MARK, key->ct.mark))
return -EMSGSIZE;

if (IS_ENABLED(CONFIG_NF_CONNTRACK_LABEL) &&
nla_put(skb, OVS_KEY_ATTR_CT_LABEL, sizeof(key->ct.label),
&key->ct.label))
return -EMSGSIZE;

return 0;
}

Expand Down Expand Up @@ -168,6 +200,40 @@ static int ovs_ct_set_mark(struct sk_buff *skb, struct sw_flow_key *key,
return 0;
}

static int ovs_ct_set_label(struct sk_buff *skb, struct sw_flow_key *key,
const struct ovs_key_ct_label *label,
const struct ovs_key_ct_label *mask)
{
enum ip_conntrack_info ctinfo;
struct nf_conn_labels *cl;
struct nf_conn *ct;
int err;

if (!IS_ENABLED(CONFIG_NF_CONNTRACK_LABELS))
return -ENOTSUPP;

/* The connection could be invalid, in which case set_label is no-op.*/
ct = nf_ct_get(skb, &ctinfo);
if (!ct)
return 0;

cl = nf_ct_labels_find(ct);
if (!cl) {
nf_ct_labels_ext_add(ct);
cl = nf_ct_labels_find(ct);
}
if (!cl || cl->words * sizeof(long) < OVS_CT_LABEL_LEN)
return -ENOSPC;

err = nf_connlabels_replace(ct, (u32 *)label, (u32 *)mask,
OVS_CT_LABEL_LEN / sizeof(u32));
if (err)
return err;

ovs_ct_get_label(ct, &key->ct.label);
return 0;
}

static int handle_fragments(struct net *net, struct sw_flow_key *key,
u16 zone, struct sk_buff *skb)
{
Expand Down Expand Up @@ -327,6 +393,17 @@ static int ovs_ct_commit(struct net *net, struct sw_flow_key *key,
return 0;
}

static bool label_nonzero(const struct ovs_key_ct_label *label)
{
size_t i;

for (i = 0; i < sizeof(*label); i++)
if (label->ct_label[i])
return true;

return false;
}

int ovs_ct_execute(struct net *net, struct sk_buff *skb,
struct sw_flow_key *key,
const struct ovs_conntrack_info *info)
Expand All @@ -351,9 +428,15 @@ int ovs_ct_execute(struct net *net, struct sk_buff *skb,
if (err)
goto err;

if (info->mark.mask)
if (info->mark.mask) {
err = ovs_ct_set_mark(skb, key, info->mark.value,
info->mark.mask);
if (err)
goto err;
}
if (label_nonzero(&info->label.mask))
err = ovs_ct_set_label(skb, key, &info->label.value,
&info->label.mask);
err:
skb_push(skb, nh_ofs);
return err;
Expand All @@ -366,6 +449,8 @@ static const struct ovs_ct_len_tbl ovs_ct_attr_lens[OVS_CT_ATTR_MAX + 1] = {
.maxlen = sizeof(u16) },
[OVS_CT_ATTR_MARK] = { .minlen = sizeof(struct md_mark),
.maxlen = sizeof(struct md_mark) },
[OVS_CT_ATTR_LABEL] = { .minlen = sizeof(struct md_label),
.maxlen = sizeof(struct md_label) },
};

static int parse_ct(const struct nlattr *attr, struct ovs_conntrack_info *info,
Expand Down Expand Up @@ -408,6 +493,14 @@ static int parse_ct(const struct nlattr *attr, struct ovs_conntrack_info *info,
info->mark = *mark;
break;
}
#endif
#ifdef CONFIG_NF_CONNTRACK_LABELS
case OVS_CT_ATTR_LABEL: {
struct md_label *label = nla_data(a);

info->label = *label;
break;
}
#endif
default:
OVS_NLERR(log, "Unknown conntrack attr (%d)",
Expand All @@ -424,7 +517,7 @@ static int parse_ct(const struct nlattr *attr, struct ovs_conntrack_info *info,
return 0;
}

bool ovs_ct_verify(enum ovs_key_attr attr)
bool ovs_ct_verify(struct net *net, enum ovs_key_attr attr)
{
if (attr == OVS_KEY_ATTR_CT_STATE)
return true;
Expand All @@ -434,6 +527,12 @@ bool ovs_ct_verify(enum ovs_key_attr attr)
if (IS_ENABLED(CONFIG_NF_CONNTRACK_MARK) &&
attr == OVS_KEY_ATTR_CT_MARK)
return true;
if (IS_ENABLED(CONFIG_NF_CONNTRACK_LABELS) &&
attr == OVS_KEY_ATTR_CT_LABEL) {
struct ovs_net *ovs_net = net_generic(net, ovs_net_id);

return ovs_net->xt_label;
}

return false;
}
Expand Down Expand Up @@ -500,6 +599,10 @@ int ovs_ct_action_to_attr(const struct ovs_conntrack_info *ct_info,
nla_put(skb, OVS_CT_ATTR_MARK, sizeof(ct_info->mark),
&ct_info->mark))
return -EMSGSIZE;
if (IS_ENABLED(CONFIG_NF_CONNTRACK_LABELS) &&
nla_put(skb, OVS_CT_ATTR_LABEL, sizeof(ct_info->label),
&ct_info->label))
return -EMSGSIZE;

nla_nest_end(skb, start);

Expand All @@ -513,3 +616,24 @@ void ovs_ct_free_action(const struct nlattr *a)
if (ct_info->ct)
nf_ct_put(ct_info->ct);
}

void ovs_ct_init(struct net *net)
{
unsigned int n_bits = sizeof(struct ovs_key_ct_label) * BITS_PER_BYTE;
struct ovs_net *ovs_net = net_generic(net, ovs_net_id);

if (nf_connlabels_get(net, n_bits)) {
ovs_net->xt_label = false;
OVS_NLERR(true, "Failed to set connlabel length");
} else {
ovs_net->xt_label = true;
}
}

void ovs_ct_exit(struct net *net)
{
struct ovs_net *ovs_net = net_generic(net, ovs_net_id);

if (ovs_net->xt_label)
nf_connlabels_put(net);
}
11 changes: 9 additions & 2 deletions net/openvswitch/conntrack.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ struct ovs_conntrack_info;
enum ovs_key_attr;

#if defined(CONFIG_OPENVSWITCH_CONNTRACK)
bool ovs_ct_verify(enum ovs_key_attr attr);
void ovs_ct_init(struct net *);
void ovs_ct_exit(struct net *);
bool ovs_ct_verify(struct net *, enum ovs_key_attr attr);
int ovs_ct_copy_action(struct net *, const struct nlattr *,
const struct sw_flow_key *, struct sw_flow_actions **,
bool log);
Expand All @@ -35,7 +37,11 @@ void ovs_ct_free_action(const struct nlattr *a);
#else
#include <linux/errno.h>

static inline bool ovs_ct_verify(int attr)
static inline void ovs_ct_init(struct net *net) { }

static inline void ovs_ct_exit(struct net *net) { }

static inline bool ovs_ct_verify(struct net *net, int attr)
{
return false;
}
Expand Down Expand Up @@ -66,6 +72,7 @@ static inline void ovs_ct_fill_key(const struct sk_buff *skb,
key->ct.state = 0;
key->ct.zone = 0;
key->ct.mark = 0;
memset(&key->ct.label, 0, sizeof(key->ct.label));
}

static inline int ovs_ct_put_key(const struct sw_flow_key *key,
Expand Down
18 changes: 11 additions & 7 deletions net/openvswitch/datapath.c
Original file line number Diff line number Diff line change
Expand Up @@ -599,8 +599,8 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)
if (IS_ERR(flow))
goto err_kfree_skb;

err = ovs_flow_key_extract_userspace(a[OVS_PACKET_ATTR_KEY], packet,
&flow->key, log);
err = ovs_flow_key_extract_userspace(net, a[OVS_PACKET_ATTR_KEY],
packet, &flow->key, log);
if (err)
goto err_flow_free;

Expand Down Expand Up @@ -947,7 +947,7 @@ static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)

/* Extract key. */
ovs_match_init(&match, &key, &mask);
error = ovs_nla_get_match(&match, a[OVS_FLOW_ATTR_KEY],
error = ovs_nla_get_match(net, &match, a[OVS_FLOW_ATTR_KEY],
a[OVS_FLOW_ATTR_MASK], log);
if (error)
goto err_kfree_flow;
Expand Down Expand Up @@ -1118,7 +1118,7 @@ static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info *info)

ufid_present = ovs_nla_get_ufid(&sfid, a[OVS_FLOW_ATTR_UFID], log);
ovs_match_init(&match, &key, &mask);
error = ovs_nla_get_match(&match, a[OVS_FLOW_ATTR_KEY],
error = ovs_nla_get_match(net, &match, a[OVS_FLOW_ATTR_KEY],
a[OVS_FLOW_ATTR_MASK], log);
if (error)
goto error;
Expand Down Expand Up @@ -1208,6 +1208,7 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr **a = info->attrs;
struct ovs_header *ovs_header = info->userhdr;
struct net *net = sock_net(skb->sk);
struct sw_flow_key key;
struct sk_buff *reply;
struct sw_flow *flow;
Expand All @@ -1222,7 +1223,7 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
ufid_present = ovs_nla_get_ufid(&ufid, a[OVS_FLOW_ATTR_UFID], log);
if (a[OVS_FLOW_ATTR_KEY]) {
ovs_match_init(&match, &key, NULL);
err = ovs_nla_get_match(&match, a[OVS_FLOW_ATTR_KEY], NULL,
err = ovs_nla_get_match(net, &match, a[OVS_FLOW_ATTR_KEY], NULL,
log);
} else if (!ufid_present) {
OVS_NLERR(log,
Expand Down Expand Up @@ -1266,6 +1267,7 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr **a = info->attrs;
struct ovs_header *ovs_header = info->userhdr;
struct net *net = sock_net(skb->sk);
struct sw_flow_key key;
struct sk_buff *reply;
struct sw_flow *flow = NULL;
Expand All @@ -1280,8 +1282,8 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
ufid_present = ovs_nla_get_ufid(&ufid, a[OVS_FLOW_ATTR_UFID], log);
if (a[OVS_FLOW_ATTR_KEY]) {
ovs_match_init(&match, &key, NULL);
err = ovs_nla_get_match(&match, a[OVS_FLOW_ATTR_KEY], NULL,
log);
err = ovs_nla_get_match(net, &match, a[OVS_FLOW_ATTR_KEY],
NULL, log);
if (unlikely(err))
return err;
}
Expand Down Expand Up @@ -2237,6 +2239,7 @@ static int __net_init ovs_init_net(struct net *net)

INIT_LIST_HEAD(&ovs_net->dps);
INIT_WORK(&ovs_net->dp_notify_work, ovs_dp_notify_wq);
ovs_ct_init(net);
return 0;
}

Expand Down Expand Up @@ -2271,6 +2274,7 @@ static void __net_exit ovs_exit_net(struct net *dnet)
struct net *net;
LIST_HEAD(head);

ovs_ct_exit(dnet);
ovs_lock();
list_for_each_entry_safe(dp, dp_next, &ovs_net->dps, list_node)
__dp_destroy(dp);
Expand Down
3 changes: 3 additions & 0 deletions net/openvswitch/datapath.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ struct ovs_net {
struct list_head dps;
struct work_struct dp_notify_work;
struct vport_net vport_net;

/* Module reference for configuring conntrack. */
bool xt_label;
};

extern int ovs_net_id;
Expand Down
Loading

0 comments on commit c2ac667

Please sign in to comment.