xref: /openbmc/linux/net/core/flow_dissector.c (revision 7490ca1e)
1 #include <linux/skbuff.h>
2 #include <linux/export.h>
3 #include <linux/ip.h>
4 #include <linux/ipv6.h>
5 #include <linux/if_vlan.h>
6 #include <net/ip.h>
7 #include <linux/if_tunnel.h>
8 #include <linux/if_pppox.h>
9 #include <linux/ppp_defs.h>
10 #include <net/flow_keys.h>
11 
12 /* copy saddr & daddr, possibly using 64bit load/store
13  * Equivalent to :	flow->src = iph->saddr;
14  *			flow->dst = iph->daddr;
15  */
16 static void iph_to_flow_copy_addrs(struct flow_keys *flow, const struct iphdr *iph)
17 {
18 	BUILD_BUG_ON(offsetof(typeof(*flow), dst) !=
19 		     offsetof(typeof(*flow), src) + sizeof(flow->src));
20 	memcpy(&flow->src, &iph->saddr, sizeof(flow->src) + sizeof(flow->dst));
21 }
22 
23 bool skb_flow_dissect(const struct sk_buff *skb, struct flow_keys *flow)
24 {
25 	int poff, nhoff = skb_network_offset(skb);
26 	u8 ip_proto;
27 	__be16 proto = skb->protocol;
28 
29 	memset(flow, 0, sizeof(*flow));
30 
31 again:
32 	switch (proto) {
33 	case __constant_htons(ETH_P_IP): {
34 		const struct iphdr *iph;
35 		struct iphdr _iph;
36 ip:
37 		iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_iph);
38 		if (!iph)
39 			return false;
40 
41 		if (ip_is_fragment(iph))
42 			ip_proto = 0;
43 		else
44 			ip_proto = iph->protocol;
45 		iph_to_flow_copy_addrs(flow, iph);
46 		nhoff += iph->ihl * 4;
47 		break;
48 	}
49 	case __constant_htons(ETH_P_IPV6): {
50 		const struct ipv6hdr *iph;
51 		struct ipv6hdr _iph;
52 ipv6:
53 		iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_iph);
54 		if (!iph)
55 			return false;
56 
57 		ip_proto = iph->nexthdr;
58 		flow->src = iph->saddr.s6_addr32[3];
59 		flow->dst = iph->daddr.s6_addr32[3];
60 		nhoff += sizeof(struct ipv6hdr);
61 		break;
62 	}
63 	case __constant_htons(ETH_P_8021Q): {
64 		const struct vlan_hdr *vlan;
65 		struct vlan_hdr _vlan;
66 
67 		vlan = skb_header_pointer(skb, nhoff, sizeof(_vlan), &_vlan);
68 		if (!vlan)
69 			return false;
70 
71 		proto = vlan->h_vlan_encapsulated_proto;
72 		nhoff += sizeof(*vlan);
73 		goto again;
74 	}
75 	case __constant_htons(ETH_P_PPP_SES): {
76 		struct {
77 			struct pppoe_hdr hdr;
78 			__be16 proto;
79 		} *hdr, _hdr;
80 		hdr = skb_header_pointer(skb, nhoff, sizeof(_hdr), &_hdr);
81 		if (!hdr)
82 			return false;
83 		proto = hdr->proto;
84 		nhoff += PPPOE_SES_HLEN;
85 		switch (proto) {
86 		case __constant_htons(PPP_IP):
87 			goto ip;
88 		case __constant_htons(PPP_IPV6):
89 			goto ipv6;
90 		default:
91 			return false;
92 		}
93 	}
94 	default:
95 		return false;
96 	}
97 
98 	switch (ip_proto) {
99 	case IPPROTO_GRE: {
100 		struct gre_hdr {
101 			__be16 flags;
102 			__be16 proto;
103 		} *hdr, _hdr;
104 
105 		hdr = skb_header_pointer(skb, nhoff, sizeof(_hdr), &_hdr);
106 		if (!hdr)
107 			return false;
108 		/*
109 		 * Only look inside GRE if version zero and no
110 		 * routing
111 		 */
112 		if (!(hdr->flags & (GRE_VERSION|GRE_ROUTING))) {
113 			proto = hdr->proto;
114 			nhoff += 4;
115 			if (hdr->flags & GRE_CSUM)
116 				nhoff += 4;
117 			if (hdr->flags & GRE_KEY)
118 				nhoff += 4;
119 			if (hdr->flags & GRE_SEQ)
120 				nhoff += 4;
121 			goto again;
122 		}
123 		break;
124 	}
125 	case IPPROTO_IPIP:
126 		goto again;
127 	default:
128 		break;
129 	}
130 
131 	flow->ip_proto = ip_proto;
132 	poff = proto_ports_offset(ip_proto);
133 	if (poff >= 0) {
134 		__be32 *ports, _ports;
135 
136 		nhoff += poff;
137 		ports = skb_header_pointer(skb, nhoff, sizeof(_ports), &_ports);
138 		if (ports)
139 			flow->ports = *ports;
140 	}
141 
142 	return true;
143 }
144 EXPORT_SYMBOL(skb_flow_dissect);
145