1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2008-2009 Patrick McHardy <kaber@trash.net> |
4 | * Copyright (c) 2012-2014 Pablo Neira Ayuso <pablo@netfilter.org> |
5 | * |
6 | * Development of this code funded by Astaro AG (http://www.astaro.com/) |
7 | */ |
8 | |
9 | #include <linux/audit.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/init.h> |
12 | #include <linux/module.h> |
13 | #include <linux/netlink.h> |
14 | #include <linux/netfilter.h> |
15 | #include <linux/netfilter/nf_tables.h> |
16 | #include <net/ipv6.h> |
17 | #include <net/ip.h> |
18 | #include <net/netfilter/nf_tables.h> |
19 | #include <net/netfilter/nf_log.h> |
20 | #include <linux/netdevice.h> |
21 | |
22 | static const char *nft_log_null_prefix = "" ; |
23 | |
24 | struct nft_log { |
25 | struct nf_loginfo loginfo; |
26 | char *prefix; |
27 | }; |
28 | |
29 | static bool audit_ip4(struct audit_buffer *ab, struct sk_buff *skb) |
30 | { |
31 | struct iphdr _iph; |
32 | const struct iphdr *ih; |
33 | |
34 | ih = skb_header_pointer(skb, offset: skb_network_offset(skb), len: sizeof(_iph), buffer: &_iph); |
35 | if (!ih) |
36 | return false; |
37 | |
38 | audit_log_format(ab, fmt: " saddr=%pI4 daddr=%pI4 proto=%hhu" , |
39 | &ih->saddr, &ih->daddr, ih->protocol); |
40 | |
41 | return true; |
42 | } |
43 | |
44 | static bool audit_ip6(struct audit_buffer *ab, struct sk_buff *skb) |
45 | { |
46 | struct ipv6hdr _ip6h; |
47 | const struct ipv6hdr *ih; |
48 | u8 nexthdr; |
49 | __be16 frag_off; |
50 | |
51 | ih = skb_header_pointer(skb, offset: skb_network_offset(skb), len: sizeof(_ip6h), buffer: &_ip6h); |
52 | if (!ih) |
53 | return false; |
54 | |
55 | nexthdr = ih->nexthdr; |
56 | ipv6_skip_exthdr(skb, start: skb_network_offset(skb) + sizeof(_ip6h), nexthdrp: &nexthdr, frag_offp: &frag_off); |
57 | |
58 | audit_log_format(ab, fmt: " saddr=%pI6c daddr=%pI6c proto=%hhu" , |
59 | &ih->saddr, &ih->daddr, nexthdr); |
60 | |
61 | return true; |
62 | } |
63 | |
64 | static void nft_log_eval_audit(const struct nft_pktinfo *pkt) |
65 | { |
66 | struct sk_buff *skb = pkt->skb; |
67 | struct audit_buffer *ab; |
68 | int fam = -1; |
69 | |
70 | if (!audit_enabled) |
71 | return; |
72 | |
73 | ab = audit_log_start(NULL, GFP_ATOMIC, AUDIT_NETFILTER_PKT); |
74 | if (!ab) |
75 | return; |
76 | |
77 | audit_log_format(ab, fmt: "mark=%#x" , skb->mark); |
78 | |
79 | switch (nft_pf(pkt)) { |
80 | case NFPROTO_BRIDGE: |
81 | switch (eth_hdr(skb)->h_proto) { |
82 | case htons(ETH_P_IP): |
83 | fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1; |
84 | break; |
85 | case htons(ETH_P_IPV6): |
86 | fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1; |
87 | break; |
88 | } |
89 | break; |
90 | case NFPROTO_IPV4: |
91 | fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1; |
92 | break; |
93 | case NFPROTO_IPV6: |
94 | fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1; |
95 | break; |
96 | } |
97 | |
98 | if (fam == -1) |
99 | audit_log_format(ab, fmt: " saddr=? daddr=? proto=-1" ); |
100 | |
101 | audit_log_end(ab); |
102 | } |
103 | |
104 | static void nft_log_eval(const struct nft_expr *expr, |
105 | struct nft_regs *regs, |
106 | const struct nft_pktinfo *pkt) |
107 | { |
108 | const struct nft_log *priv = nft_expr_priv(expr); |
109 | |
110 | if (priv->loginfo.type == NF_LOG_TYPE_LOG && |
111 | priv->loginfo.u.log.level == NFT_LOGLEVEL_AUDIT) { |
112 | nft_log_eval_audit(pkt); |
113 | return; |
114 | } |
115 | |
116 | nf_log_packet(net: nft_net(pkt), pf: nft_pf(pkt), hooknum: nft_hook(pkt), skb: pkt->skb, |
117 | in: nft_in(pkt), out: nft_out(pkt), li: &priv->loginfo, fmt: "%s" , |
118 | priv->prefix); |
119 | } |
120 | |
121 | static const struct nla_policy nft_log_policy[NFTA_LOG_MAX + 1] = { |
122 | [NFTA_LOG_GROUP] = { .type = NLA_U16 }, |
123 | [NFTA_LOG_PREFIX] = { .type = NLA_STRING, |
124 | .len = NF_LOG_PREFIXLEN - 1 }, |
125 | [NFTA_LOG_SNAPLEN] = { .type = NLA_U32 }, |
126 | [NFTA_LOG_QTHRESHOLD] = { .type = NLA_U16 }, |
127 | [NFTA_LOG_LEVEL] = { .type = NLA_U32 }, |
128 | [NFTA_LOG_FLAGS] = { .type = NLA_U32 }, |
129 | }; |
130 | |
131 | static int nft_log_modprobe(struct net *net, enum nf_log_type t) |
132 | { |
133 | switch (t) { |
134 | case NF_LOG_TYPE_LOG: |
135 | return nft_request_module(net, fmt: "%s" , "nf_log_syslog" ); |
136 | case NF_LOG_TYPE_ULOG: |
137 | return nft_request_module(net, fmt: "%s" , "nfnetlink_log" ); |
138 | case NF_LOG_TYPE_MAX: |
139 | break; |
140 | } |
141 | |
142 | return -ENOENT; |
143 | } |
144 | |
145 | static int nft_log_init(const struct nft_ctx *ctx, |
146 | const struct nft_expr *expr, |
147 | const struct nlattr * const tb[]) |
148 | { |
149 | struct nft_log *priv = nft_expr_priv(expr); |
150 | struct nf_loginfo *li = &priv->loginfo; |
151 | const struct nlattr *nla; |
152 | int err; |
153 | |
154 | li->type = NF_LOG_TYPE_LOG; |
155 | if (tb[NFTA_LOG_LEVEL] != NULL && |
156 | tb[NFTA_LOG_GROUP] != NULL) |
157 | return -EINVAL; |
158 | if (tb[NFTA_LOG_GROUP] != NULL) { |
159 | li->type = NF_LOG_TYPE_ULOG; |
160 | if (tb[NFTA_LOG_FLAGS] != NULL) |
161 | return -EINVAL; |
162 | } |
163 | |
164 | nla = tb[NFTA_LOG_PREFIX]; |
165 | if (nla != NULL) { |
166 | priv->prefix = kmalloc(size: nla_len(nla) + 1, GFP_KERNEL); |
167 | if (priv->prefix == NULL) |
168 | return -ENOMEM; |
169 | nla_strscpy(dst: priv->prefix, nla, dstsize: nla_len(nla) + 1); |
170 | } else { |
171 | priv->prefix = (char *)nft_log_null_prefix; |
172 | } |
173 | |
174 | switch (li->type) { |
175 | case NF_LOG_TYPE_LOG: |
176 | if (tb[NFTA_LOG_LEVEL] != NULL) { |
177 | li->u.log.level = |
178 | ntohl(nla_get_be32(tb[NFTA_LOG_LEVEL])); |
179 | } else { |
180 | li->u.log.level = NFT_LOGLEVEL_WARNING; |
181 | } |
182 | if (li->u.log.level > NFT_LOGLEVEL_AUDIT) { |
183 | err = -EINVAL; |
184 | goto err1; |
185 | } |
186 | |
187 | if (tb[NFTA_LOG_FLAGS] != NULL) { |
188 | li->u.log.logflags = |
189 | ntohl(nla_get_be32(tb[NFTA_LOG_FLAGS])); |
190 | if (li->u.log.logflags & ~NF_LOG_MASK) { |
191 | err = -EINVAL; |
192 | goto err1; |
193 | } |
194 | } |
195 | break; |
196 | case NF_LOG_TYPE_ULOG: |
197 | li->u.ulog.group = ntohs(nla_get_be16(tb[NFTA_LOG_GROUP])); |
198 | if (tb[NFTA_LOG_SNAPLEN] != NULL) { |
199 | li->u.ulog.flags |= NF_LOG_F_COPY_LEN; |
200 | li->u.ulog.copy_len = |
201 | ntohl(nla_get_be32(tb[NFTA_LOG_SNAPLEN])); |
202 | } |
203 | if (tb[NFTA_LOG_QTHRESHOLD] != NULL) { |
204 | li->u.ulog.qthreshold = |
205 | ntohs(nla_get_be16(tb[NFTA_LOG_QTHRESHOLD])); |
206 | } |
207 | break; |
208 | } |
209 | |
210 | if (li->u.log.level == NFT_LOGLEVEL_AUDIT) |
211 | return 0; |
212 | |
213 | err = nf_logger_find_get(pf: ctx->family, type: li->type); |
214 | if (err < 0) { |
215 | if (nft_log_modprobe(net: ctx->net, t: li->type) == -EAGAIN) |
216 | err = -EAGAIN; |
217 | |
218 | goto err1; |
219 | } |
220 | |
221 | return 0; |
222 | |
223 | err1: |
224 | if (priv->prefix != nft_log_null_prefix) |
225 | kfree(objp: priv->prefix); |
226 | return err; |
227 | } |
228 | |
229 | static void nft_log_destroy(const struct nft_ctx *ctx, |
230 | const struct nft_expr *expr) |
231 | { |
232 | struct nft_log *priv = nft_expr_priv(expr); |
233 | struct nf_loginfo *li = &priv->loginfo; |
234 | |
235 | if (priv->prefix != nft_log_null_prefix) |
236 | kfree(objp: priv->prefix); |
237 | |
238 | if (li->u.log.level == NFT_LOGLEVEL_AUDIT) |
239 | return; |
240 | |
241 | nf_logger_put(pf: ctx->family, type: li->type); |
242 | } |
243 | |
244 | static int nft_log_dump(struct sk_buff *skb, |
245 | const struct nft_expr *expr, bool reset) |
246 | { |
247 | const struct nft_log *priv = nft_expr_priv(expr); |
248 | const struct nf_loginfo *li = &priv->loginfo; |
249 | |
250 | if (priv->prefix != nft_log_null_prefix) |
251 | if (nla_put_string(skb, attrtype: NFTA_LOG_PREFIX, str: priv->prefix)) |
252 | goto nla_put_failure; |
253 | switch (li->type) { |
254 | case NF_LOG_TYPE_LOG: |
255 | if (nla_put_be32(skb, attrtype: NFTA_LOG_LEVEL, htonl(li->u.log.level))) |
256 | goto nla_put_failure; |
257 | |
258 | if (li->u.log.logflags) { |
259 | if (nla_put_be32(skb, attrtype: NFTA_LOG_FLAGS, |
260 | htonl(li->u.log.logflags))) |
261 | goto nla_put_failure; |
262 | } |
263 | break; |
264 | case NF_LOG_TYPE_ULOG: |
265 | if (nla_put_be16(skb, attrtype: NFTA_LOG_GROUP, htons(li->u.ulog.group))) |
266 | goto nla_put_failure; |
267 | |
268 | if (li->u.ulog.flags & NF_LOG_F_COPY_LEN) { |
269 | if (nla_put_be32(skb, attrtype: NFTA_LOG_SNAPLEN, |
270 | htonl(li->u.ulog.copy_len))) |
271 | goto nla_put_failure; |
272 | } |
273 | if (li->u.ulog.qthreshold) { |
274 | if (nla_put_be16(skb, attrtype: NFTA_LOG_QTHRESHOLD, |
275 | htons(li->u.ulog.qthreshold))) |
276 | goto nla_put_failure; |
277 | } |
278 | break; |
279 | } |
280 | return 0; |
281 | |
282 | nla_put_failure: |
283 | return -1; |
284 | } |
285 | |
286 | static struct nft_expr_type nft_log_type; |
287 | static const struct nft_expr_ops nft_log_ops = { |
288 | .type = &nft_log_type, |
289 | .size = NFT_EXPR_SIZE(sizeof(struct nft_log)), |
290 | .eval = nft_log_eval, |
291 | .init = nft_log_init, |
292 | .destroy = nft_log_destroy, |
293 | .dump = nft_log_dump, |
294 | .reduce = NFT_REDUCE_READONLY, |
295 | }; |
296 | |
297 | static struct nft_expr_type nft_log_type __read_mostly = { |
298 | .name = "log" , |
299 | .ops = &nft_log_ops, |
300 | .policy = nft_log_policy, |
301 | .maxattr = NFTA_LOG_MAX, |
302 | .owner = THIS_MODULE, |
303 | }; |
304 | |
305 | static int __init nft_log_module_init(void) |
306 | { |
307 | return nft_register_expr(&nft_log_type); |
308 | } |
309 | |
310 | static void __exit nft_log_module_exit(void) |
311 | { |
312 | nft_unregister_expr(&nft_log_type); |
313 | } |
314 | |
315 | module_init(nft_log_module_init); |
316 | module_exit(nft_log_module_exit); |
317 | |
318 | MODULE_LICENSE("GPL" ); |
319 | MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>" ); |
320 | MODULE_ALIAS_NFT_EXPR("log" ); |
321 | MODULE_DESCRIPTION("Netfilter nf_tables log module" ); |
322 | |