flow_dissector.c 9.8 KB
Newer Older
E
Eric Dumazet 已提交
1
#include <linux/skbuff.h>
2
#include <linux/export.h>
E
Eric Dumazet 已提交
3 4 5 6
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/if_vlan.h>
#include <net/ip.h>
E
Eric Dumazet 已提交
7
#include <net/ipv6.h>
8 9 10 11
#include <linux/igmp.h>
#include <linux/icmp.h>
#include <linux/sctp.h>
#include <linux/dccp.h>
E
Eric Dumazet 已提交
12 13 14 15 16
#include <linux/if_tunnel.h>
#include <linux/if_pppox.h>
#include <linux/ppp_defs.h>
#include <net/flow_keys.h>

17 18 19 20 21 22 23 24 25 26
/* copy saddr & daddr, possibly using 64bit load/store
 * Equivalent to :	flow->src = iph->saddr;
 *			flow->dst = iph->daddr;
 */
static void iph_to_flow_copy_addrs(struct flow_keys *flow, const struct iphdr *iph)
{
	BUILD_BUG_ON(offsetof(typeof(*flow), dst) !=
		     offsetof(typeof(*flow), src) + sizeof(flow->src));
	memcpy(&flow->src, &iph->saddr, sizeof(flow->src) + sizeof(flow->dst));
}
E
Eric Dumazet 已提交
27

28 29 30 31 32 33 34 35 36
/**
 * skb_flow_get_ports - extract the upper layer ports and return them
 * @skb: buffer to extract the ports from
 * @thoff: transport header offset
 * @ip_proto: protocol for which to get port offset
 *
 * The function will try to retrieve the ports at offset thoff + poff where poff
 * is the protocol port offset returned from proto_ports_offset
 */
37 38
__be32 __skb_flow_get_ports(const struct sk_buff *skb, int thoff, u8 ip_proto,
			    void *data, int hlen)
39 40 41
{
	int poff = proto_ports_offset(ip_proto);

42 43 44 45 46
	if (!data) {
		data = skb->data;
		hlen = skb_headlen(skb);
	}

47 48 49
	if (poff >= 0) {
		__be32 *ports, _ports;

50 51
		ports = __skb_header_pointer(skb, thoff + poff,
					     sizeof(_ports), data, hlen, &_ports);
52 53 54 55 56 57
		if (ports)
			return *ports;
	}

	return 0;
}
58
EXPORT_SYMBOL(__skb_flow_get_ports);
59

60
bool __skb_flow_dissect(const struct sk_buff *skb, struct flow_keys *flow, void *data, int hlen)
E
Eric Dumazet 已提交
61
{
62
	int nhoff = skb_network_offset(skb);
E
Eric Dumazet 已提交
63 64 65
	u8 ip_proto;
	__be16 proto = skb->protocol;

66 67 68 69 70
	if (!data) {
		data = skb->data;
		hlen = skb_headlen(skb);
	}

E
Eric Dumazet 已提交
71 72 73 74
	memset(flow, 0, sizeof(*flow));

again:
	switch (proto) {
75
	case htons(ETH_P_IP): {
E
Eric Dumazet 已提交
76 77 78
		const struct iphdr *iph;
		struct iphdr _iph;
ip:
79
		iph = __skb_header_pointer(skb, nhoff, sizeof(_iph), data, hlen, &_iph);
80
		if (!iph || iph->ihl < 5)
E
Eric Dumazet 已提交
81
			return false;
82
		nhoff += iph->ihl * 4;
E
Eric Dumazet 已提交
83

84
		ip_proto = iph->protocol;
E
Eric Dumazet 已提交
85 86
		if (ip_is_fragment(iph))
			ip_proto = 0;
87

88
		iph_to_flow_copy_addrs(flow, iph);
E
Eric Dumazet 已提交
89 90
		break;
	}
91
	case htons(ETH_P_IPV6): {
E
Eric Dumazet 已提交
92 93
		const struct ipv6hdr *iph;
		struct ipv6hdr _iph;
94 95
		__be32 flow_label;

E
Eric Dumazet 已提交
96
ipv6:
97
		iph = __skb_header_pointer(skb, nhoff, sizeof(_iph), data, hlen, &_iph);
E
Eric Dumazet 已提交
98 99 100 101
		if (!iph)
			return false;

		ip_proto = iph->nexthdr;
E
Eric Dumazet 已提交
102 103
		flow->src = (__force __be32)ipv6_addr_hash(&iph->saddr);
		flow->dst = (__force __be32)ipv6_addr_hash(&iph->daddr);
E
Eric Dumazet 已提交
104
		nhoff += sizeof(struct ipv6hdr);
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119

		flow_label = ip6_flowlabel(iph);
		if (flow_label) {
			/* Awesome, IPv6 packet has a flow label so we can
			 * use that to represent the ports without any
			 * further dissection.
			 */
			flow->n_proto = proto;
			flow->ip_proto = ip_proto;
			flow->ports = flow_label;
			flow->thoff = (u16)nhoff;

			return true;
		}

E
Eric Dumazet 已提交
120 121
		break;
	}
122 123
	case htons(ETH_P_8021AD):
	case htons(ETH_P_8021Q): {
E
Eric Dumazet 已提交
124 125 126
		const struct vlan_hdr *vlan;
		struct vlan_hdr _vlan;

127
		vlan = __skb_header_pointer(skb, nhoff, sizeof(_vlan), data, hlen, &_vlan);
E
Eric Dumazet 已提交
128 129 130 131 132 133 134
		if (!vlan)
			return false;

		proto = vlan->h_vlan_encapsulated_proto;
		nhoff += sizeof(*vlan);
		goto again;
	}
135
	case htons(ETH_P_PPP_SES): {
E
Eric Dumazet 已提交
136 137 138 139
		struct {
			struct pppoe_hdr hdr;
			__be16 proto;
		} *hdr, _hdr;
140
		hdr = __skb_header_pointer(skb, nhoff, sizeof(_hdr), data, hlen, &_hdr);
E
Eric Dumazet 已提交
141 142 143 144 145
		if (!hdr)
			return false;
		proto = hdr->proto;
		nhoff += PPPOE_SES_HLEN;
		switch (proto) {
146
		case htons(PPP_IP):
E
Eric Dumazet 已提交
147
			goto ip;
148
		case htons(PPP_IPV6):
E
Eric Dumazet 已提交
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
			goto ipv6;
		default:
			return false;
		}
	}
	default:
		return false;
	}

	switch (ip_proto) {
	case IPPROTO_GRE: {
		struct gre_hdr {
			__be16 flags;
			__be16 proto;
		} *hdr, _hdr;

165
		hdr = __skb_header_pointer(skb, nhoff, sizeof(_hdr), data, hlen, &_hdr);
E
Eric Dumazet 已提交
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
		if (!hdr)
			return false;
		/*
		 * Only look inside GRE if version zero and no
		 * routing
		 */
		if (!(hdr->flags & (GRE_VERSION|GRE_ROUTING))) {
			proto = hdr->proto;
			nhoff += 4;
			if (hdr->flags & GRE_CSUM)
				nhoff += 4;
			if (hdr->flags & GRE_KEY)
				nhoff += 4;
			if (hdr->flags & GRE_SEQ)
				nhoff += 4;
M
Michael Dalton 已提交
181 182 183 184
			if (proto == htons(ETH_P_TEB)) {
				const struct ethhdr *eth;
				struct ethhdr _eth;

185 186 187
				eth = __skb_header_pointer(skb, nhoff,
							   sizeof(_eth),
							   data, hlen, &_eth);
M
Michael Dalton 已提交
188 189 190 191 192
				if (!eth)
					return false;
				proto = eth->h_proto;
				nhoff += sizeof(*eth);
			}
E
Eric Dumazet 已提交
193 194 195 196 197
			goto again;
		}
		break;
	}
	case IPPROTO_IPIP:
T
Tom Herbert 已提交
198 199
		proto = htons(ETH_P_IP);
		goto ip;
200 201 202
	case IPPROTO_IPV6:
		proto = htons(ETH_P_IPV6);
		goto ipv6;
E
Eric Dumazet 已提交
203 204 205 206
	default:
		break;
	}

207
	flow->n_proto = proto;
E
Eric Dumazet 已提交
208
	flow->ip_proto = ip_proto;
209
	flow->ports = __skb_flow_get_ports(skb, nhoff, ip_proto, data, hlen);
210 211
	flow->thoff = (u16) nhoff;

E
Eric Dumazet 已提交
212 213
	return true;
}
214
EXPORT_SYMBOL(__skb_flow_dissect);
215 216

static u32 hashrnd __read_mostly;
217 218 219 220 221 222 223 224 225 226 227
static __always_inline void __flow_hash_secret_init(void)
{
	net_get_random_once(&hashrnd, sizeof(hashrnd));
}

static __always_inline u32 __flow_hash_3words(u32 a, u32 b, u32 c)
{
	__flow_hash_secret_init();
	return jhash_3words(a, b, c, hashrnd);
}

228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
static inline u32 __flow_hash_from_keys(struct flow_keys *keys)
{
	u32 hash;

	/* get a consistent hash (same value on both flow directions) */
	if (((__force u32)keys->dst < (__force u32)keys->src) ||
	    (((__force u32)keys->dst == (__force u32)keys->src) &&
	     ((__force u16)keys->port16[1] < (__force u16)keys->port16[0]))) {
		swap(keys->dst, keys->src);
		swap(keys->port16[0], keys->port16[1]);
	}

	hash = __flow_hash_3words((__force u32)keys->dst,
				  (__force u32)keys->src,
				  (__force u32)keys->ports);
	if (!hash)
		hash = 1;

	return hash;
}

u32 flow_hash_from_keys(struct flow_keys *keys)
{
	return __flow_hash_from_keys(keys);
}
EXPORT_SYMBOL(flow_hash_from_keys);

255
/*
256
 * __skb_get_hash: calculate a flow hash based on src/dst addresses
257 258
 * and src/dst port numbers.  Sets hash in skb to non-zero hash value
 * on success, zero indicates no valid hash.  Also, sets l4_hash in skb
259 260
 * if hash is a canonical 4-tuple hash over transport ports.
 */
261
void __skb_get_hash(struct sk_buff *skb)
262 263 264 265 266 267 268
{
	struct flow_keys keys;

	if (!skb_flow_dissect(skb, &keys))
		return;

	if (keys.ports)
269
		skb->l4_hash = 1;
270

271 272
	skb->sw_hash = 1;

273
	skb->hash = __flow_hash_from_keys(&keys);
274
}
275
EXPORT_SYMBOL(__skb_get_hash);
276 277 278 279 280

/*
 * Returns a Tx hash based on the given packet descriptor a Tx queues' number
 * to be used as a distribution range.
 */
281
u16 __skb_tx_hash(const struct net_device *dev, struct sk_buff *skb,
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
		  unsigned int num_tx_queues)
{
	u32 hash;
	u16 qoffset = 0;
	u16 qcount = num_tx_queues;

	if (skb_rx_queue_recorded(skb)) {
		hash = skb_get_rx_queue(skb);
		while (unlikely(hash >= num_tx_queues))
			hash -= num_tx_queues;
		return hash;
	}

	if (dev->num_tc) {
		u8 tc = netdev_get_prio_tc_map(dev, skb->priority);
		qoffset = dev->tc_to_txq[tc].offset;
		qcount = dev->tc_to_txq[tc].count;
	}

301
	return (u16) reciprocal_scale(skb_get_hash(skb), qcount) + qoffset;
302 303 304
}
EXPORT_SYMBOL(__skb_tx_hash);

305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
/* __skb_get_poff() returns the offset to the payload as far as it could
 * be dissected. The main user is currently BPF, so that we can dynamically
 * truncate packets without needing to push actual payload to the user
 * space and can analyze headers only, instead.
 */
u32 __skb_get_poff(const struct sk_buff *skb)
{
	struct flow_keys keys;
	u32 poff = 0;

	if (!skb_flow_dissect(skb, &keys))
		return 0;

	poff += keys.thoff;
	switch (keys.ip_proto) {
	case IPPROTO_TCP: {
		const struct tcphdr *tcph;
		struct tcphdr _tcph;

		tcph = skb_header_pointer(skb, poff, sizeof(_tcph), &_tcph);
		if (!tcph)
			return poff;

		poff += max_t(u32, sizeof(struct tcphdr), tcph->doff * 4);
		break;
	}
	case IPPROTO_UDP:
	case IPPROTO_UDPLITE:
		poff += sizeof(struct udphdr);
		break;
	/* For the rest, we do not really care about header
	 * extensions at this point for now.
	 */
	case IPPROTO_ICMP:
		poff += sizeof(struct icmphdr);
		break;
	case IPPROTO_ICMPV6:
		poff += sizeof(struct icmp6hdr);
		break;
	case IPPROTO_IGMP:
		poff += sizeof(struct igmphdr);
		break;
	case IPPROTO_DCCP:
		poff += sizeof(struct dccp_hdr);
		break;
	case IPPROTO_SCTP:
		poff += sizeof(struct sctphdr);
		break;
	}

	return poff;
}

358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
static inline int get_xps_queue(struct net_device *dev, struct sk_buff *skb)
{
#ifdef CONFIG_XPS
	struct xps_dev_maps *dev_maps;
	struct xps_map *map;
	int queue_index = -1;

	rcu_read_lock();
	dev_maps = rcu_dereference(dev->xps_maps);
	if (dev_maps) {
		map = rcu_dereference(
		    dev_maps->cpu_map[raw_smp_processor_id()]);
		if (map) {
			if (map->len == 1)
				queue_index = map->queues[0];
373
			else
374 375
				queue_index = map->queues[reciprocal_scale(skb_get_hash(skb),
									   map->len)];
376 377 378 379 380 381 382 383 384 385 386 387
			if (unlikely(queue_index >= dev->real_num_tx_queues))
				queue_index = -1;
		}
	}
	rcu_read_unlock();

	return queue_index;
#else
	return -1;
#endif
}

388
static u16 __netdev_pick_tx(struct net_device *dev, struct sk_buff *skb)
389 390 391 392 393 394 395 396 397 398
{
	struct sock *sk = skb->sk;
	int queue_index = sk_tx_queue_get(sk);

	if (queue_index < 0 || skb->ooo_okay ||
	    queue_index >= dev->real_num_tx_queues) {
		int new_index = get_xps_queue(dev, skb);
		if (new_index < 0)
			new_index = skb_tx_hash(dev, skb);

399 400
		if (queue_index != new_index && sk &&
		    rcu_access_pointer(sk->sk_dst_cache))
E
Eric Dumazet 已提交
401
			sk_tx_queue_set(sk, new_index);
402 403 404 405 406 407 408 409

		queue_index = new_index;
	}

	return queue_index;
}

struct netdev_queue *netdev_pick_tx(struct net_device *dev,
410 411
				    struct sk_buff *skb,
				    void *accel_priv)
412 413 414 415 416 417
{
	int queue_index = 0;

	if (dev->real_num_tx_queues != 1) {
		const struct net_device_ops *ops = dev->netdev_ops;
		if (ops->ndo_select_queue)
418 419
			queue_index = ops->ndo_select_queue(dev, skb, accel_priv,
							    __netdev_pick_tx);
420 421
		else
			queue_index = __netdev_pick_tx(dev, skb);
422 423

		if (!accel_priv)
424
			queue_index = netdev_cap_txqueue(dev, queue_index);
425 426 427 428 429
	}

	skb_set_queue_mapping(skb, queue_index);
	return netdev_get_tx_queue(dev, queue_index);
}