2063 lines · 53.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Copyright (c) 2015 Nicira, Inc. */ #include <linux/module.h>#include <linux/openvswitch.h>#include <linux/tcp.h>#include <linux/udp.h>#include <linux/sctp.h>#include <linux/static_key.h>#include <linux/string_helpers.h>#include <net/ip.h>#include <net/genetlink.h>#include <net/netfilter/nf_conntrack_core.h>#include <net/netfilter/nf_conntrack_count.h>#include <net/netfilter/nf_conntrack_helper.h>#include <net/netfilter/nf_conntrack_labels.h>#include <net/netfilter/nf_conntrack_seqadj.h>#include <net/netfilter/nf_conntrack_timeout.h>#include <net/netfilter/nf_conntrack_zones.h>#include <net/netfilter/ipv6/nf_defrag_ipv6.h>#include <net/ipv6_frag.h> #if IS_ENABLED(CONFIG_NF_NAT)#include <net/netfilter/nf_nat.h>#endif #include <net/netfilter/nf_conntrack_act_ct.h> #include "datapath.h"#include "drop.h"#include "conntrack.h"#include "flow.h"#include "flow_netlink.h" struct ovs_ct_len_tbl {	int maxlen;	int minlen;}; /* Metadata mark for masked write to conntrack mark */struct md_mark {	u32 value;	u32 mask;}; /* Metadata label for masked write to conntrack label. */struct md_labels {	struct ovs_key_ct_labels value;	struct ovs_key_ct_labels mask;}; enum ovs_ct_nat {	OVS_CT_NAT = 1 << 0,     /* NAT for committed connections only. */	OVS_CT_SRC_NAT = 1 << 1, /* Source NAT for NEW connections. */	OVS_CT_DST_NAT = 1 << 2, /* Destination NAT for NEW connections. */}; /* Conntrack action context for execution. */struct ovs_conntrack_info {	struct nf_conntrack_helper *helper;	struct nf_conntrack_zone zone;	struct nf_conn *ct;	u8 commit : 1;	u8 nat : 3;                 /* enum ovs_ct_nat */	u8 force : 1;	u8 have_eventmask : 1;	u16 family;	u32 eventmask;              /* Mask of 1 << IPCT_*. */	struct md_mark mark;	struct md_labels labels;	char timeout[CTNL_TIMEOUT_NAME_MAX];	struct nf_ct_timeout *nf_ct_timeout;#if IS_ENABLED(CONFIG_NF_NAT)	struct nf_nat_range2 range;  /* Only present for SRC NAT and DST NAT. */#endif}; #if	IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)#define OVS_CT_LIMIT_UNLIMITED	0#define OVS_CT_LIMIT_DEFAULT OVS_CT_LIMIT_UNLIMITED#define CT_LIMIT_HASH_BUCKETS 512static DEFINE_STATIC_KEY_FALSE(ovs_ct_limit_enabled); struct ovs_ct_limit {	/* Elements in ovs_ct_limit_info->limits hash table */	struct hlist_node hlist_node;	struct rcu_head rcu;	u16 zone;	u32 limit;}; struct ovs_ct_limit_info {	u32 default_limit;	struct hlist_head *limits;	struct nf_conncount_data *data;}; static const struct nla_policy ct_limit_policy[OVS_CT_LIMIT_ATTR_MAX + 1] = {	[OVS_CT_LIMIT_ATTR_ZONE_LIMIT] = { .type = NLA_NESTED, },};#endif static bool labels_nonzero(const struct ovs_key_ct_labels *labels); static void __ovs_ct_free_action(struct ovs_conntrack_info *ct_info); static u16 key_to_nfproto(const struct sw_flow_key *key){	switch (ntohs(key->eth.type)) {	case ETH_P_IP:		return NFPROTO_IPV4;	case ETH_P_IPV6:		return NFPROTO_IPV6;	default:		return NFPROTO_UNSPEC;	}} /* Map SKB connection state into the values used by flow definition. */static u8 ovs_ct_get_state(enum ip_conntrack_info ctinfo){	u8 ct_state = OVS_CS_F_TRACKED; 	switch (ctinfo) {	case IP_CT_ESTABLISHED_REPLY:	case IP_CT_RELATED_REPLY:		ct_state |= OVS_CS_F_REPLY_DIR;		break;	default:		break;	} 	switch (ctinfo) {	case IP_CT_ESTABLISHED:	case IP_CT_ESTABLISHED_REPLY:		ct_state |= OVS_CS_F_ESTABLISHED;		break;	case IP_CT_RELATED:	case IP_CT_RELATED_REPLY:		ct_state |= OVS_CS_F_RELATED;		break;	case IP_CT_NEW:		ct_state |= OVS_CS_F_NEW;		break;	default:		break;	} 	return ct_state;} static u32 ovs_ct_get_mark(const struct nf_conn *ct){#if IS_ENABLED(CONFIG_NF_CONNTRACK_MARK)	return ct ? READ_ONCE(ct->mark) : 0;#else	return 0;#endif} /* Guard against conntrack labels max size shrinking below 128 bits. */#if NF_CT_LABELS_MAX_SIZE < 16#error NF_CT_LABELS_MAX_SIZE must be at least 16 bytes#endif static void ovs_ct_get_labels(const struct nf_conn *ct,			      struct ovs_key_ct_labels *labels){	struct nf_conn_labels *cl = NULL; 	if (ct) {		if (ct->master && !nf_ct_is_confirmed(ct))			ct = ct->master;		cl = nf_ct_labels_find(ct);	}	if (cl)		memcpy(labels, cl->bits, OVS_CT_LABELS_LEN);	else		memset(labels, 0, OVS_CT_LABELS_LEN);} static void __ovs_ct_update_key_orig_tp(struct sw_flow_key *key,					const struct nf_conntrack_tuple *orig,					u8 icmp_proto){	key->ct_orig_proto = orig->dst.protonum;	if (orig->dst.protonum == icmp_proto) {		key->ct.orig_tp.src = htons(orig->dst.u.icmp.type);		key->ct.orig_tp.dst = htons(orig->dst.u.icmp.code);	} else {		key->ct.orig_tp.src = orig->src.u.all;		key->ct.orig_tp.dst = orig->dst.u.all;	}} 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 = ovs_ct_get_mark(ct);	ovs_ct_get_labels(ct, &key->ct.labels); 	if (ct) {		const struct nf_conntrack_tuple *orig; 		/* Use the master if we have one. */		if (ct->master)			ct = ct->master;		orig = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; 		/* IP version must match with the master connection. */		if (key->eth.type == htons(ETH_P_IP) &&		    nf_ct_l3num(ct) == NFPROTO_IPV4) {			key->ipv4.ct_orig.src = orig->src.u3.ip;			key->ipv4.ct_orig.dst = orig->dst.u3.ip;			__ovs_ct_update_key_orig_tp(key, orig, IPPROTO_ICMP);			return;		} else if (key->eth.type == htons(ETH_P_IPV6) &&			   !sw_flow_key_is_nd(key) &&			   nf_ct_l3num(ct) == NFPROTO_IPV6) {			key->ipv6.ct_orig.src = orig->src.u3.in6;			key->ipv6.ct_orig.dst = orig->dst.u3.in6;			__ovs_ct_update_key_orig_tp(key, orig, NEXTHDR_ICMP);			return;		}	}	/* Clear 'ct_orig_proto' to mark the non-existence of conntrack	 * original direction key fields.	 */	key->ct_orig_proto = 0;} /* Update 'key' based on skb->_nfct.  If 'post_ct' is true, then OVS has * previously sent the packet to conntrack via the ct action.  If * 'keep_nat_flags' is true, the existing NAT flags retained, else they are * initialized from the connection status. */static void ovs_ct_update_key(const struct sk_buff *skb,			      const struct ovs_conntrack_info *info,			      struct sw_flow_key *key, bool post_ct,			      bool keep_nat_flags){	const struct nf_conntrack_zone *zone = &nf_ct_zone_dflt;	enum ip_conntrack_info ctinfo;	struct nf_conn *ct;	u8 state = 0; 	ct = nf_ct_get(skb, &ctinfo);	if (ct) {		state = ovs_ct_get_state(ctinfo);		/* All unconfirmed entries are NEW connections. */		if (!nf_ct_is_confirmed(ct))			state |= OVS_CS_F_NEW;		/* OVS persists the related flag for the duration of the		 * connection.		 */		if (ct->master)			state |= OVS_CS_F_RELATED;		if (keep_nat_flags) {			state |= key->ct_state & OVS_CS_F_NAT_MASK;		} else {			if (ct->status & IPS_SRC_NAT)				state |= OVS_CS_F_SRC_NAT;			if (ct->status & IPS_DST_NAT)				state |= OVS_CS_F_DST_NAT;		}		zone = nf_ct_zone(ct);	} else if (post_ct) {		state = OVS_CS_F_TRACKED | OVS_CS_F_INVALID;		if (info)			zone = &info->zone;	}	__ovs_ct_update_key(key, state, zone, ct);} /* This is called to initialize CT key fields possibly coming in from the local * stack. */void ovs_ct_fill_key(const struct sk_buff *skb,		     struct sw_flow_key *key,		     bool post_ct){	ovs_ct_update_key(skb, NULL, key, post_ct, false);} int ovs_ct_put_key(const struct sw_flow_key *swkey,		   const struct sw_flow_key *output, struct sk_buff *skb){	if (nla_put_u32(skb, OVS_KEY_ATTR_CT_STATE, output->ct_state))		return -EMSGSIZE; 	if (IS_ENABLED(CONFIG_NF_CONNTRACK_ZONES) &&	    nla_put_u16(skb, OVS_KEY_ATTR_CT_ZONE, output->ct_zone))		return -EMSGSIZE; 	if (IS_ENABLED(CONFIG_NF_CONNTRACK_MARK) &&	    nla_put_u32(skb, OVS_KEY_ATTR_CT_MARK, output->ct.mark))		return -EMSGSIZE; 	if (IS_ENABLED(CONFIG_NF_CONNTRACK_LABELS) &&	    nla_put(skb, OVS_KEY_ATTR_CT_LABELS, sizeof(output->ct.labels),		    &output->ct.labels))		return -EMSGSIZE; 	if (swkey->ct_orig_proto) {		if (swkey->eth.type == htons(ETH_P_IP)) {			struct ovs_key_ct_tuple_ipv4 orig; 			memset(&orig, 0, sizeof(orig));			orig.ipv4_src = output->ipv4.ct_orig.src;			orig.ipv4_dst = output->ipv4.ct_orig.dst;			orig.src_port = output->ct.orig_tp.src;			orig.dst_port = output->ct.orig_tp.dst;			orig.ipv4_proto = output->ct_orig_proto; 			if (nla_put(skb, OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4,				    sizeof(orig), &orig))				return -EMSGSIZE;		} else if (swkey->eth.type == htons(ETH_P_IPV6)) {			struct ovs_key_ct_tuple_ipv6 orig; 			memset(&orig, 0, sizeof(orig));			memcpy(orig.ipv6_src, output->ipv6.ct_orig.src.s6_addr32,			       sizeof(orig.ipv6_src));			memcpy(orig.ipv6_dst, output->ipv6.ct_orig.dst.s6_addr32,			       sizeof(orig.ipv6_dst));			orig.src_port = output->ct.orig_tp.src;			orig.dst_port = output->ct.orig_tp.dst;			orig.ipv6_proto = output->ct_orig_proto; 			if (nla_put(skb, OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6,				    sizeof(orig), &orig))				return -EMSGSIZE;		}	} 	return 0;} static int ovs_ct_set_mark(struct nf_conn *ct, struct sw_flow_key *key,			   u32 ct_mark, u32 mask){#if IS_ENABLED(CONFIG_NF_CONNTRACK_MARK)	u32 new_mark; 	new_mark = ct_mark | (READ_ONCE(ct->mark) & ~(mask));	if (READ_ONCE(ct->mark) != new_mark) {		WRITE_ONCE(ct->mark, new_mark);		if (nf_ct_is_confirmed(ct))			nf_conntrack_event_cache(IPCT_MARK, ct);		key->ct.mark = new_mark;	} 	return 0;#else	return -ENOTSUPP;#endif} static struct nf_conn_labels *ovs_ct_get_conn_labels(struct nf_conn *ct){	struct nf_conn_labels *cl; 	cl = nf_ct_labels_find(ct);	if (!cl) {		nf_ct_labels_ext_add(ct);		cl = nf_ct_labels_find(ct);	} 	return cl;} /* Initialize labels for a new, yet to be committed conntrack entry.  Note that * since the new connection is not yet confirmed, and thus no-one else has * access to it's labels, we simply write them over. */static int ovs_ct_init_labels(struct nf_conn *ct, struct sw_flow_key *key,			      const struct ovs_key_ct_labels *labels,			      const struct ovs_key_ct_labels *mask){	struct nf_conn_labels *cl, *master_cl;	bool have_mask = labels_nonzero(mask); 	/* Inherit master's labels to the related connection? */	master_cl = ct->master ? nf_ct_labels_find(ct->master) : NULL; 	if (!master_cl && !have_mask)		return 0;   /* Nothing to do. */ 	cl = ovs_ct_get_conn_labels(ct);	if (!cl)		return -ENOSPC; 	/* Inherit the master's labels, if any. */	if (master_cl)		*cl = *master_cl; 	if (have_mask) {		u32 *dst = (u32 *)cl->bits;		int i; 		for (i = 0; i < OVS_CT_LABELS_LEN_32; i++)			dst[i] = (dst[i] & ~mask->ct_labels_32[i]) |				(labels->ct_labels_32[i]				 & mask->ct_labels_32[i]);	} 	/* Labels are included in the IPCTNL_MSG_CT_NEW event only if the	 * IPCT_LABEL bit is set in the event cache.	 */	nf_conntrack_event_cache(IPCT_LABEL, ct); 	memcpy(&key->ct.labels, cl->bits, OVS_CT_LABELS_LEN); 	return 0;} static int ovs_ct_set_labels(struct nf_conn *ct, struct sw_flow_key *key,			     const struct ovs_key_ct_labels *labels,			     const struct ovs_key_ct_labels *mask){	struct nf_conn_labels *cl;	int err; 	cl = ovs_ct_get_conn_labels(ct);	if (!cl)		return -ENOSPC; 	err = nf_connlabels_replace(ct, labels->ct_labels_32,				    mask->ct_labels_32,				    OVS_CT_LABELS_LEN_32);	if (err)		return err; 	memcpy(&key->ct.labels, cl->bits, OVS_CT_LABELS_LEN); 	return 0;} static int ovs_ct_handle_fragments(struct net *net, struct sw_flow_key *key,				   u16 zone, int family, struct sk_buff *skb){	struct ovs_skb_cb ovs_cb = *OVS_CB(skb);	int err; 	err = nf_ct_handle_fragments(net, skb, zone, family, &key->ip.proto, &ovs_cb.mru);	if (err)		return err; 	/* The key extracted from the fragment that completed this datagram	 * likely didn't have an L4 header, so regenerate it.	 */	ovs_flow_key_update_l3l4(skb, key);	key->ip.frag = OVS_FRAG_TYPE_NONE;	*OVS_CB(skb) = ovs_cb; 	return 0;} /* This replicates logic from nf_conntrack_core.c that is not exported. */static enum ip_conntrack_infoovs_ct_get_info(const struct nf_conntrack_tuple_hash *h){	const struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(h); 	if (NF_CT_DIRECTION(h) == IP_CT_DIR_REPLY)		return IP_CT_ESTABLISHED_REPLY;	/* Once we've had two way comms, always ESTABLISHED. */	if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status))		return IP_CT_ESTABLISHED;	if (test_bit(IPS_EXPECTED_BIT, &ct->status))		return IP_CT_RELATED;	return IP_CT_NEW;} /* Find an existing connection which this packet belongs to without * re-attributing statistics or modifying the connection state.  This allows an * skb->_nfct lost due to an upcall to be recovered during actions execution. * * Must be called with rcu_read_lock. * * On success, populates skb->_nfct and returns the connection.  Returns NULL * if there is no existing entry. */static struct nf_conn *ovs_ct_find_existing(struct net *net, const struct nf_conntrack_zone *zone,		     u8 l3num, struct sk_buff *skb, bool natted){	struct nf_conntrack_tuple tuple;	struct nf_conntrack_tuple_hash *h;	struct nf_conn *ct; 	if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb), l3num,			       net, &tuple)) {		pr_debug("ovs_ct_find_existing: Can't get tuple\n");		return NULL;

Lines 1–500 of 2063.