Commit | Line | Data |
---|---|---|
33d5a7b1 FW |
1 | /* |
2 | * (C) 2015 Red Hat GmbH | |
3 | * Author: Florian Westphal <fw@strlen.de> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | */ | |
9 | ||
10 | #include <linux/module.h> | |
e639f7ab | 11 | #include <linux/static_key.h> |
33d5a7b1 FW |
12 | #include <linux/hash.h> |
13 | #include <linux/jhash.h> | |
14 | #include <linux/if_vlan.h> | |
15 | #include <linux/init.h> | |
16 | #include <linux/skbuff.h> | |
17 | #include <linux/netlink.h> | |
18 | #include <linux/netfilter.h> | |
19 | #include <linux/netfilter/nfnetlink.h> | |
20 | #include <linux/netfilter/nf_tables.h> | |
21 | #include <net/netfilter/nf_tables_core.h> | |
22 | #include <net/netfilter/nf_tables.h> | |
23 | ||
24 | #define NFT_TRACETYPE_LL_HSIZE 20 | |
25 | #define NFT_TRACETYPE_NETWORK_HSIZE 40 | |
26 | #define NFT_TRACETYPE_TRANSPORT_HSIZE 20 | |
27 | ||
e639f7ab FW |
28 | DEFINE_STATIC_KEY_FALSE(nft_trace_enabled); |
29 | EXPORT_SYMBOL_GPL(nft_trace_enabled); | |
30 | ||
33d5a7b1 FW |
31 | static int trace_fill_id(struct sk_buff *nlskb, struct sk_buff *skb) |
32 | { | |
33 | __be32 id; | |
34 | ||
35 | /* using skb address as ID results in a limited number of | |
36 | * values (and quick reuse). | |
37 | * | |
38 | * So we attempt to use as many skb members that will not | |
39 | * change while skb is with netfilter. | |
40 | */ | |
41 | id = (__be32)jhash_2words(hash32_ptr(skb), skb_get_hash(skb), | |
42 | skb->skb_iif); | |
43 | ||
44 | return nla_put_be32(nlskb, NFTA_TRACE_ID, id); | |
45 | } | |
46 | ||
47 | static int trace_fill_header(struct sk_buff *nlskb, u16 type, | |
48 | const struct sk_buff *skb, | |
49 | int off, unsigned int len) | |
50 | { | |
51 | struct nlattr *nla; | |
52 | ||
53 | if (len == 0) | |
54 | return 0; | |
55 | ||
56 | nla = nla_reserve(nlskb, type, len); | |
57 | if (!nla || skb_copy_bits(skb, off, nla_data(nla), len)) | |
58 | return -1; | |
59 | ||
60 | return 0; | |
61 | } | |
62 | ||
63 | static int nf_trace_fill_ll_header(struct sk_buff *nlskb, | |
64 | const struct sk_buff *skb) | |
65 | { | |
66 | struct vlan_ethhdr veth; | |
67 | int off; | |
68 | ||
69 | BUILD_BUG_ON(sizeof(veth) > NFT_TRACETYPE_LL_HSIZE); | |
70 | ||
71 | off = skb_mac_header(skb) - skb->data; | |
72 | if (off != -ETH_HLEN) | |
73 | return -1; | |
74 | ||
75 | if (skb_copy_bits(skb, off, &veth, ETH_HLEN)) | |
76 | return -1; | |
77 | ||
78 | veth.h_vlan_proto = skb->vlan_proto; | |
79 | veth.h_vlan_TCI = htons(skb_vlan_tag_get(skb)); | |
80 | veth.h_vlan_encapsulated_proto = skb->protocol; | |
81 | ||
82 | return nla_put(nlskb, NFTA_TRACE_LL_HEADER, sizeof(veth), &veth); | |
83 | } | |
84 | ||
85 | static int nf_trace_fill_dev_info(struct sk_buff *nlskb, | |
86 | const struct net_device *indev, | |
87 | const struct net_device *outdev) | |
88 | { | |
89 | if (indev) { | |
90 | if (nla_put_be32(nlskb, NFTA_TRACE_IIF, | |
91 | htonl(indev->ifindex))) | |
92 | return -1; | |
93 | ||
94 | if (nla_put_be16(nlskb, NFTA_TRACE_IIFTYPE, | |
95 | htons(indev->type))) | |
96 | return -1; | |
97 | } | |
98 | ||
99 | if (outdev) { | |
100 | if (nla_put_be32(nlskb, NFTA_TRACE_OIF, | |
101 | htonl(outdev->ifindex))) | |
102 | return -1; | |
103 | ||
104 | if (nla_put_be16(nlskb, NFTA_TRACE_OIFTYPE, | |
105 | htons(outdev->type))) | |
106 | return -1; | |
107 | } | |
108 | ||
109 | return 0; | |
110 | } | |
111 | ||
112 | static int nf_trace_fill_pkt_info(struct sk_buff *nlskb, | |
113 | const struct nft_pktinfo *pkt) | |
114 | { | |
115 | const struct sk_buff *skb = pkt->skb; | |
116 | unsigned int len = min_t(unsigned int, | |
117 | pkt->xt.thoff - skb_network_offset(skb), | |
118 | NFT_TRACETYPE_NETWORK_HSIZE); | |
119 | int off = skb_network_offset(skb); | |
120 | ||
121 | if (trace_fill_header(nlskb, NFTA_TRACE_NETWORK_HEADER, skb, off, len)) | |
122 | return -1; | |
123 | ||
124 | len = min_t(unsigned int, skb->len - pkt->xt.thoff, | |
125 | NFT_TRACETYPE_TRANSPORT_HSIZE); | |
126 | ||
127 | if (trace_fill_header(nlskb, NFTA_TRACE_TRANSPORT_HEADER, skb, | |
128 | pkt->xt.thoff, len)) | |
129 | return -1; | |
130 | ||
131 | if (!skb_mac_header_was_set(skb)) | |
132 | return 0; | |
133 | ||
134 | if (skb_vlan_tag_get(skb)) | |
135 | return nf_trace_fill_ll_header(nlskb, skb); | |
136 | ||
137 | off = skb_mac_header(skb) - skb->data; | |
138 | len = min_t(unsigned int, -off, NFT_TRACETYPE_LL_HSIZE); | |
139 | return trace_fill_header(nlskb, NFTA_TRACE_LL_HEADER, | |
140 | skb, off, len); | |
141 | } | |
142 | ||
143 | static int nf_trace_fill_rule_info(struct sk_buff *nlskb, | |
144 | const struct nft_traceinfo *info) | |
145 | { | |
146 | if (!info->rule) | |
147 | return 0; | |
148 | ||
149 | /* a continue verdict with ->type == RETURN means that this is | |
150 | * an implicit return (end of chain reached). | |
151 | * | |
152 | * Since no rule matched, the ->rule pointer is invalid. | |
153 | */ | |
154 | if (info->type == NFT_TRACETYPE_RETURN && | |
155 | info->verdict->code == NFT_CONTINUE) | |
156 | return 0; | |
157 | ||
158 | return nla_put_be64(nlskb, NFTA_TRACE_RULE_HANDLE, | |
b46f6ded ND |
159 | cpu_to_be64(info->rule->handle), |
160 | NFTA_TRACE_PAD); | |
33d5a7b1 FW |
161 | } |
162 | ||
163 | void nft_trace_notify(struct nft_traceinfo *info) | |
164 | { | |
165 | const struct nft_pktinfo *pkt = info->pkt; | |
166 | struct nfgenmsg *nfmsg; | |
167 | struct nlmsghdr *nlh; | |
168 | struct sk_buff *skb; | |
169 | unsigned int size; | |
170 | int event = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_TRACE; | |
171 | ||
172 | if (!nfnetlink_has_listeners(pkt->net, NFNLGRP_NFTRACE)) | |
173 | return; | |
174 | ||
175 | size = nlmsg_total_size(sizeof(struct nfgenmsg)) + | |
176 | nla_total_size(NFT_TABLE_MAXNAMELEN) + | |
177 | nla_total_size(NFT_CHAIN_MAXNAMELEN) + | |
b46f6ded | 178 | nla_total_size_64bit(sizeof(__be64)) + /* rule handle */ |
33d5a7b1 FW |
179 | nla_total_size(sizeof(__be32)) + /* trace type */ |
180 | nla_total_size(0) + /* VERDICT, nested */ | |
181 | nla_total_size(sizeof(u32)) + /* verdict code */ | |
182 | nla_total_size(NFT_CHAIN_MAXNAMELEN) + /* jump target */ | |
183 | nla_total_size(sizeof(u32)) + /* id */ | |
184 | nla_total_size(NFT_TRACETYPE_LL_HSIZE) + | |
185 | nla_total_size(NFT_TRACETYPE_NETWORK_HSIZE) + | |
186 | nla_total_size(NFT_TRACETYPE_TRANSPORT_HSIZE) + | |
187 | nla_total_size(sizeof(u32)) + /* iif */ | |
188 | nla_total_size(sizeof(__be16)) + /* iiftype */ | |
189 | nla_total_size(sizeof(u32)) + /* oif */ | |
190 | nla_total_size(sizeof(__be16)) + /* oiftype */ | |
191 | nla_total_size(sizeof(u32)) + /* mark */ | |
192 | nla_total_size(sizeof(u32)) + /* nfproto */ | |
193 | nla_total_size(sizeof(u32)); /* policy */ | |
194 | ||
195 | skb = nlmsg_new(size, GFP_ATOMIC); | |
196 | if (!skb) | |
197 | return; | |
198 | ||
199 | nlh = nlmsg_put(skb, 0, 0, event, sizeof(struct nfgenmsg), 0); | |
200 | if (!nlh) | |
201 | goto nla_put_failure; | |
202 | ||
203 | nfmsg = nlmsg_data(nlh); | |
204 | nfmsg->nfgen_family = info->basechain->type->family; | |
205 | nfmsg->version = NFNETLINK_V0; | |
206 | nfmsg->res_id = 0; | |
207 | ||
208 | if (nla_put_be32(skb, NFTA_TRACE_NFPROTO, htonl(pkt->pf))) | |
209 | goto nla_put_failure; | |
210 | ||
211 | if (nla_put_be32(skb, NFTA_TRACE_TYPE, htonl(info->type))) | |
212 | goto nla_put_failure; | |
213 | ||
214 | if (trace_fill_id(skb, pkt->skb)) | |
215 | goto nla_put_failure; | |
216 | ||
217 | if (info->chain) { | |
218 | if (nla_put_string(skb, NFTA_TRACE_CHAIN, | |
219 | info->chain->name)) | |
220 | goto nla_put_failure; | |
221 | if (nla_put_string(skb, NFTA_TRACE_TABLE, | |
222 | info->chain->table->name)) | |
223 | goto nla_put_failure; | |
224 | } | |
225 | ||
226 | if (nf_trace_fill_rule_info(skb, info)) | |
227 | goto nla_put_failure; | |
228 | ||
229 | switch (info->type) { | |
230 | case NFT_TRACETYPE_UNSPEC: | |
231 | case __NFT_TRACETYPE_MAX: | |
232 | break; | |
233 | case NFT_TRACETYPE_RETURN: | |
234 | case NFT_TRACETYPE_RULE: | |
235 | if (nft_verdict_dump(skb, NFTA_TRACE_VERDICT, info->verdict)) | |
236 | goto nla_put_failure; | |
237 | break; | |
238 | case NFT_TRACETYPE_POLICY: | |
239 | if (nla_put_be32(skb, NFTA_TRACE_POLICY, | |
240 | info->basechain->policy)) | |
241 | goto nla_put_failure; | |
242 | break; | |
243 | } | |
244 | ||
245 | if (pkt->skb->mark && | |
246 | nla_put_be32(skb, NFTA_TRACE_MARK, htonl(pkt->skb->mark))) | |
247 | goto nla_put_failure; | |
248 | ||
249 | if (!info->packet_dumped) { | |
250 | if (nf_trace_fill_dev_info(skb, pkt->in, pkt->out)) | |
251 | goto nla_put_failure; | |
252 | ||
253 | if (nf_trace_fill_pkt_info(skb, pkt)) | |
254 | goto nla_put_failure; | |
255 | info->packet_dumped = true; | |
256 | } | |
257 | ||
258 | nlmsg_end(skb, nlh); | |
259 | nfnetlink_send(skb, pkt->net, 0, NFNLGRP_NFTRACE, 0, GFP_ATOMIC); | |
260 | return; | |
261 | ||
262 | nla_put_failure: | |
263 | WARN_ON_ONCE(1); | |
264 | kfree_skb(skb); | |
265 | } | |
266 | ||
267 | void nft_trace_init(struct nft_traceinfo *info, const struct nft_pktinfo *pkt, | |
268 | const struct nft_verdict *verdict, | |
269 | const struct nft_chain *chain) | |
270 | { | |
271 | info->basechain = nft_base_chain(chain); | |
272 | info->trace = true; | |
273 | info->packet_dumped = false; | |
274 | info->pkt = pkt; | |
275 | info->verdict = verdict; | |
276 | } |