]>
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; | |
33d5a7b1 | 116 | int off = skb_network_offset(skb); |
a20877b5 | 117 | unsigned int len, nh_end; |
33d5a7b1 | 118 | |
a20877b5 LZ |
119 | nh_end = pkt->tprot_set ? pkt->xt.thoff : skb->len; |
120 | len = min_t(unsigned int, nh_end - skb_network_offset(skb), | |
121 | NFT_TRACETYPE_NETWORK_HSIZE); | |
33d5a7b1 FW |
122 | if (trace_fill_header(nlskb, NFTA_TRACE_NETWORK_HEADER, skb, off, len)) |
123 | return -1; | |
124 | ||
a20877b5 LZ |
125 | if (pkt->tprot_set) { |
126 | len = min_t(unsigned int, skb->len - pkt->xt.thoff, | |
127 | NFT_TRACETYPE_TRANSPORT_HSIZE); | |
128 | if (trace_fill_header(nlskb, NFTA_TRACE_TRANSPORT_HEADER, skb, | |
129 | pkt->xt.thoff, len)) | |
130 | return -1; | |
131 | } | |
33d5a7b1 FW |
132 | |
133 | if (!skb_mac_header_was_set(skb)) | |
134 | return 0; | |
135 | ||
136 | if (skb_vlan_tag_get(skb)) | |
137 | return nf_trace_fill_ll_header(nlskb, skb); | |
138 | ||
139 | off = skb_mac_header(skb) - skb->data; | |
140 | len = min_t(unsigned int, -off, NFT_TRACETYPE_LL_HSIZE); | |
141 | return trace_fill_header(nlskb, NFTA_TRACE_LL_HEADER, | |
142 | skb, off, len); | |
143 | } | |
144 | ||
145 | static int nf_trace_fill_rule_info(struct sk_buff *nlskb, | |
146 | const struct nft_traceinfo *info) | |
147 | { | |
148 | if (!info->rule) | |
149 | return 0; | |
150 | ||
151 | /* a continue verdict with ->type == RETURN means that this is | |
152 | * an implicit return (end of chain reached). | |
153 | * | |
154 | * Since no rule matched, the ->rule pointer is invalid. | |
155 | */ | |
156 | if (info->type == NFT_TRACETYPE_RETURN && | |
157 | info->verdict->code == NFT_CONTINUE) | |
158 | return 0; | |
159 | ||
160 | return nla_put_be64(nlskb, NFTA_TRACE_RULE_HANDLE, | |
b46f6ded ND |
161 | cpu_to_be64(info->rule->handle), |
162 | NFTA_TRACE_PAD); | |
33d5a7b1 FW |
163 | } |
164 | ||
165 | void nft_trace_notify(struct nft_traceinfo *info) | |
166 | { | |
167 | const struct nft_pktinfo *pkt = info->pkt; | |
168 | struct nfgenmsg *nfmsg; | |
169 | struct nlmsghdr *nlh; | |
170 | struct sk_buff *skb; | |
171 | unsigned int size; | |
dedb67c4 | 172 | u16 event; |
33d5a7b1 | 173 | |
0e5a1c7e | 174 | if (!nfnetlink_has_listeners(nft_net(pkt), NFNLGRP_NFTRACE)) |
33d5a7b1 FW |
175 | return; |
176 | ||
177 | size = nlmsg_total_size(sizeof(struct nfgenmsg)) + | |
178 | nla_total_size(NFT_TABLE_MAXNAMELEN) + | |
179 | nla_total_size(NFT_CHAIN_MAXNAMELEN) + | |
b46f6ded | 180 | nla_total_size_64bit(sizeof(__be64)) + /* rule handle */ |
33d5a7b1 FW |
181 | nla_total_size(sizeof(__be32)) + /* trace type */ |
182 | nla_total_size(0) + /* VERDICT, nested */ | |
183 | nla_total_size(sizeof(u32)) + /* verdict code */ | |
184 | nla_total_size(NFT_CHAIN_MAXNAMELEN) + /* jump target */ | |
185 | nla_total_size(sizeof(u32)) + /* id */ | |
186 | nla_total_size(NFT_TRACETYPE_LL_HSIZE) + | |
187 | nla_total_size(NFT_TRACETYPE_NETWORK_HSIZE) + | |
188 | nla_total_size(NFT_TRACETYPE_TRANSPORT_HSIZE) + | |
189 | nla_total_size(sizeof(u32)) + /* iif */ | |
190 | nla_total_size(sizeof(__be16)) + /* iiftype */ | |
191 | nla_total_size(sizeof(u32)) + /* oif */ | |
192 | nla_total_size(sizeof(__be16)) + /* oiftype */ | |
193 | nla_total_size(sizeof(u32)) + /* mark */ | |
194 | nla_total_size(sizeof(u32)) + /* nfproto */ | |
195 | nla_total_size(sizeof(u32)); /* policy */ | |
196 | ||
197 | skb = nlmsg_new(size, GFP_ATOMIC); | |
198 | if (!skb) | |
199 | return; | |
200 | ||
dedb67c4 | 201 | event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, NFT_MSG_TRACE); |
33d5a7b1 FW |
202 | nlh = nlmsg_put(skb, 0, 0, event, sizeof(struct nfgenmsg), 0); |
203 | if (!nlh) | |
204 | goto nla_put_failure; | |
205 | ||
206 | nfmsg = nlmsg_data(nlh); | |
207 | nfmsg->nfgen_family = info->basechain->type->family; | |
208 | nfmsg->version = NFNETLINK_V0; | |
209 | nfmsg->res_id = 0; | |
210 | ||
0e5a1c7e | 211 | if (nla_put_be32(skb, NFTA_TRACE_NFPROTO, htonl(nft_pf(pkt)))) |
33d5a7b1 FW |
212 | goto nla_put_failure; |
213 | ||
214 | if (nla_put_be32(skb, NFTA_TRACE_TYPE, htonl(info->type))) | |
215 | goto nla_put_failure; | |
216 | ||
217 | if (trace_fill_id(skb, pkt->skb)) | |
218 | goto nla_put_failure; | |
219 | ||
220 | if (info->chain) { | |
221 | if (nla_put_string(skb, NFTA_TRACE_CHAIN, | |
222 | info->chain->name)) | |
223 | goto nla_put_failure; | |
224 | if (nla_put_string(skb, NFTA_TRACE_TABLE, | |
225 | info->chain->table->name)) | |
226 | goto nla_put_failure; | |
227 | } | |
228 | ||
229 | if (nf_trace_fill_rule_info(skb, info)) | |
230 | goto nla_put_failure; | |
231 | ||
232 | switch (info->type) { | |
233 | case NFT_TRACETYPE_UNSPEC: | |
234 | case __NFT_TRACETYPE_MAX: | |
235 | break; | |
236 | case NFT_TRACETYPE_RETURN: | |
237 | case NFT_TRACETYPE_RULE: | |
238 | if (nft_verdict_dump(skb, NFTA_TRACE_VERDICT, info->verdict)) | |
239 | goto nla_put_failure; | |
240 | break; | |
241 | case NFT_TRACETYPE_POLICY: | |
242 | if (nla_put_be32(skb, NFTA_TRACE_POLICY, | |
5210d393 | 243 | htonl(info->basechain->policy))) |
33d5a7b1 FW |
244 | goto nla_put_failure; |
245 | break; | |
246 | } | |
247 | ||
248 | if (pkt->skb->mark && | |
249 | nla_put_be32(skb, NFTA_TRACE_MARK, htonl(pkt->skb->mark))) | |
250 | goto nla_put_failure; | |
251 | ||
252 | if (!info->packet_dumped) { | |
0e5a1c7e | 253 | if (nf_trace_fill_dev_info(skb, nft_in(pkt), nft_out(pkt))) |
33d5a7b1 FW |
254 | goto nla_put_failure; |
255 | ||
256 | if (nf_trace_fill_pkt_info(skb, pkt)) | |
257 | goto nla_put_failure; | |
258 | info->packet_dumped = true; | |
259 | } | |
260 | ||
261 | nlmsg_end(skb, nlh); | |
0e5a1c7e | 262 | nfnetlink_send(skb, nft_net(pkt), 0, NFNLGRP_NFTRACE, 0, GFP_ATOMIC); |
33d5a7b1 FW |
263 | return; |
264 | ||
265 | nla_put_failure: | |
266 | WARN_ON_ONCE(1); | |
267 | kfree_skb(skb); | |
268 | } | |
269 | ||
270 | void nft_trace_init(struct nft_traceinfo *info, const struct nft_pktinfo *pkt, | |
271 | const struct nft_verdict *verdict, | |
272 | const struct nft_chain *chain) | |
273 | { | |
274 | info->basechain = nft_base_chain(chain); | |
275 | info->trace = true; | |
276 | info->packet_dumped = false; | |
277 | info->pkt = pkt; | |
278 | info->verdict = verdict; | |
279 | } |