1635 lines · 43.5 KB · C
RawDownload
/* * Copyright (c) 2015 Nicira, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ #include <linux/module.h>#include <linux/openvswitch.h>#include <linux/tcp.h>#include <linux/udp.h>#include <linux/sctp.h>#include <net/ip.h>#include <net/netfilter/nf_conntrack_core.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_zones.h>#include <net/netfilter/ipv6/nf_defrag_ipv6.h> #ifdef CONFIG_NF_NAT_NEEDED#include <linux/netfilter/nf_nat.h>#include <net/netfilter/nf_nat_core.h>#include <net/netfilter/nf_nat_l3proto.h>#endif #include "datapath.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;#ifdef CONFIG_NF_NAT_NEEDED	struct nf_nat_range range;  /* Only present for SRC NAT and DST NAT. */#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 ? 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 = ct ? nf_ct_labels_find(ct) : NULL; 	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){	ovs_ct_update_key(skb, NULL, key, false, false);} #define IN6_ADDR_INITIALIZER(ADDR) \	{ (ADDR).s6_addr32[0], (ADDR).s6_addr32[1], \	  (ADDR).s6_addr32[2], (ADDR).s6_addr32[3] } 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 = {				output->ipv4.ct_orig.src,				output->ipv4.ct_orig.dst,				output->ct.orig_tp.src,				output->ct.orig_tp.dst,				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 = {				IN6_ADDR_INITIALIZER(output->ipv6.ct_orig.src),				IN6_ADDR_INITIALIZER(output->ipv6.ct_orig.dst),				output->ct.orig_tp.src,				output->ct.orig_tp.dst,				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 | (ct->mark & ~(mask));	if (ct->mark != new_mark) {		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;} /* '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. */static int handle_fragments(struct net *net, struct sw_flow_key *key,			    u16 zone, struct sk_buff *skb){	struct ovs_skb_cb ovs_cb = *OVS_CB(skb);	int err; 	if (key->eth.type == htons(ETH_P_IP)) {		enum ip_defrag_users user = IP_DEFRAG_CONNTRACK_IN + zone; 		memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));		err = ip_defrag(net, skb, user);		if (err)			return err; 		ovs_cb.mru = IPCB(skb)->frag_max_size;#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)	} else if (key->eth.type == htons(ETH_P_IPV6)) {		enum ip6_defrag_users user = IP6_DEFRAG_CONNTRACK_IN + zone; 		memset(IP6CB(skb), 0, sizeof(struct inet6_skb_parm));		err = nf_ct_frag6_gather(net, skb, user);		if (err) {			if (err != -EINPROGRESS)				kfree_skb(skb);			return err;		} 		key->ip.proto = ipv6_hdr(skb)->nexthdr;		ovs_cb.mru = IP6CB(skb)->frag_max_size;#endif

Lines 1–500 of 1635.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t