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