1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C)2003,2004 USAGI/WIDE Project |
4 | * |
5 | * Author: |
6 | * Yasuyuki Kozakai @USAGI <yasuyuki.kozakai@toshiba.co.jp> |
7 | */ |
8 | |
9 | #include <linux/types.h> |
10 | #include <linux/timer.h> |
11 | #include <linux/module.h> |
12 | #include <linux/netfilter.h> |
13 | #include <linux/in6.h> |
14 | #include <linux/icmpv6.h> |
15 | #include <linux/ipv6.h> |
16 | #include <net/ipv6.h> |
17 | #include <net/ip6_checksum.h> |
18 | #include <linux/seq_file.h> |
19 | #include <linux/netfilter_ipv6.h> |
20 | #include <net/netfilter/nf_conntrack_tuple.h> |
21 | #include <net/netfilter/nf_conntrack_l4proto.h> |
22 | #include <net/netfilter/nf_conntrack_core.h> |
23 | #include <net/netfilter/nf_conntrack_timeout.h> |
24 | #include <net/netfilter/nf_conntrack_zones.h> |
25 | #include <net/netfilter/nf_log.h> |
26 | |
27 | #include "nf_internals.h" |
28 | |
29 | static const unsigned int nf_ct_icmpv6_timeout = 30*HZ; |
30 | |
31 | bool icmpv6_pkt_to_tuple(const struct sk_buff *skb, |
32 | unsigned int dataoff, |
33 | struct net *net, |
34 | struct nf_conntrack_tuple *tuple) |
35 | { |
36 | const struct icmp6hdr *hp; |
37 | struct icmp6hdr _hdr; |
38 | |
39 | hp = skb_header_pointer(skb, offset: dataoff, len: sizeof(_hdr), buffer: &_hdr); |
40 | if (hp == NULL) |
41 | return false; |
42 | tuple->dst.u.icmp.type = hp->icmp6_type; |
43 | tuple->src.u.icmp.id = hp->icmp6_identifier; |
44 | tuple->dst.u.icmp.code = hp->icmp6_code; |
45 | |
46 | return true; |
47 | } |
48 | |
49 | /* Add 1; spaces filled with 0. */ |
50 | static const u_int8_t invmap[] = { |
51 | [ICMPV6_ECHO_REQUEST - 128] = ICMPV6_ECHO_REPLY + 1, |
52 | [ICMPV6_ECHO_REPLY - 128] = ICMPV6_ECHO_REQUEST + 1, |
53 | [ICMPV6_NI_QUERY - 128] = ICMPV6_NI_REPLY + 1, |
54 | [ICMPV6_NI_REPLY - 128] = ICMPV6_NI_QUERY + 1 |
55 | }; |
56 | |
57 | static const u_int8_t noct_valid_new[] = { |
58 | [ICMPV6_MGM_QUERY - 130] = 1, |
59 | [ICMPV6_MGM_REPORT - 130] = 1, |
60 | [ICMPV6_MGM_REDUCTION - 130] = 1, |
61 | [NDISC_ROUTER_SOLICITATION - 130] = 1, |
62 | [NDISC_ROUTER_ADVERTISEMENT - 130] = 1, |
63 | [NDISC_NEIGHBOUR_SOLICITATION - 130] = 1, |
64 | [NDISC_NEIGHBOUR_ADVERTISEMENT - 130] = 1, |
65 | [ICMPV6_MLD2_REPORT - 130] = 1 |
66 | }; |
67 | |
68 | bool nf_conntrack_invert_icmpv6_tuple(struct nf_conntrack_tuple *tuple, |
69 | const struct nf_conntrack_tuple *orig) |
70 | { |
71 | int type = orig->dst.u.icmp.type - 128; |
72 | if (type < 0 || type >= sizeof(invmap) || !invmap[type]) |
73 | return false; |
74 | |
75 | tuple->src.u.icmp.id = orig->src.u.icmp.id; |
76 | tuple->dst.u.icmp.type = invmap[type] - 1; |
77 | tuple->dst.u.icmp.code = orig->dst.u.icmp.code; |
78 | return true; |
79 | } |
80 | |
81 | static unsigned int *icmpv6_get_timeouts(struct net *net) |
82 | { |
83 | return &nf_icmpv6_pernet(net)->timeout; |
84 | } |
85 | |
86 | /* Returns verdict for packet, or -1 for invalid. */ |
87 | int nf_conntrack_icmpv6_packet(struct nf_conn *ct, |
88 | struct sk_buff *skb, |
89 | enum ip_conntrack_info ctinfo, |
90 | const struct nf_hook_state *state) |
91 | { |
92 | unsigned int *timeout = nf_ct_timeout_lookup(ct); |
93 | static const u8 valid_new[] = { |
94 | [ICMPV6_ECHO_REQUEST - 128] = 1, |
95 | [ICMPV6_NI_QUERY - 128] = 1 |
96 | }; |
97 | |
98 | if (state->pf != NFPROTO_IPV6) |
99 | return -NF_ACCEPT; |
100 | |
101 | if (!nf_ct_is_confirmed(ct)) { |
102 | int type = ct->tuplehash[0].tuple.dst.u.icmp.type - 128; |
103 | |
104 | if (type < 0 || type >= sizeof(valid_new) || !valid_new[type]) { |
105 | /* Can't create a new ICMPv6 `conn' with this. */ |
106 | pr_debug("icmpv6: can't create new conn with type %u\n" , |
107 | type + 128); |
108 | nf_ct_dump_tuple_ipv6(t: &ct->tuplehash[0].tuple); |
109 | return -NF_ACCEPT; |
110 | } |
111 | } |
112 | |
113 | if (!timeout) |
114 | timeout = icmpv6_get_timeouts(net: nf_ct_net(ct)); |
115 | |
116 | /* Do not immediately delete the connection after the first |
117 | successful reply to avoid excessive conntrackd traffic |
118 | and also to handle correctly ICMP echo reply duplicates. */ |
119 | nf_ct_refresh_acct(ct, ctinfo, skb, extra_jiffies: *timeout); |
120 | |
121 | return NF_ACCEPT; |
122 | } |
123 | |
124 | |
125 | static void icmpv6_error_log(const struct sk_buff *skb, |
126 | const struct nf_hook_state *state, |
127 | const char *msg) |
128 | { |
129 | nf_l4proto_log_invalid(skb, state, IPPROTO_ICMPV6, fmt: "%s" , msg); |
130 | } |
131 | |
132 | static noinline_for_stack int |
133 | nf_conntrack_icmpv6_redirect(struct nf_conn *tmpl, struct sk_buff *skb, |
134 | unsigned int dataoff, |
135 | const struct nf_hook_state *state) |
136 | { |
137 | u8 hl = ipv6_hdr(skb)->hop_limit; |
138 | union nf_inet_addr outer_daddr; |
139 | union { |
140 | struct nd_opt_hdr nd_opt; |
141 | struct rd_msg rd_msg; |
142 | } tmp; |
143 | const struct nd_opt_hdr *nd_opt; |
144 | const struct rd_msg *rd_msg; |
145 | |
146 | rd_msg = skb_header_pointer(skb, offset: dataoff, len: sizeof(*rd_msg), buffer: &tmp.rd_msg); |
147 | if (!rd_msg) { |
148 | icmpv6_error_log(skb, state, msg: "short redirect" ); |
149 | return -NF_ACCEPT; |
150 | } |
151 | |
152 | if (rd_msg->icmph.icmp6_code != 0) |
153 | return NF_ACCEPT; |
154 | |
155 | if (hl != 255 || !(ipv6_addr_type(addr: &ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL)) { |
156 | icmpv6_error_log(skb, state, msg: "invalid saddr or hoplimit for redirect" ); |
157 | return -NF_ACCEPT; |
158 | } |
159 | |
160 | dataoff += sizeof(*rd_msg); |
161 | |
162 | /* warning: rd_msg no longer usable after this call */ |
163 | nd_opt = skb_header_pointer(skb, offset: dataoff, len: sizeof(*nd_opt), buffer: &tmp.nd_opt); |
164 | if (!nd_opt || nd_opt->nd_opt_len == 0) { |
165 | icmpv6_error_log(skb, state, msg: "redirect without options" ); |
166 | return -NF_ACCEPT; |
167 | } |
168 | |
169 | /* We could call ndisc_parse_options(), but it would need |
170 | * skb_linearize() and a bit more work. |
171 | */ |
172 | if (nd_opt->nd_opt_type != ND_OPT_REDIRECT_HDR) |
173 | return NF_ACCEPT; |
174 | |
175 | memcpy(&outer_daddr.ip6, &ipv6_hdr(skb)->daddr, |
176 | sizeof(outer_daddr.ip6)); |
177 | dataoff += 8; |
178 | return nf_conntrack_inet_error(tmpl, skb, dataoff, state, |
179 | IPPROTO_ICMPV6, outer_daddr: &outer_daddr); |
180 | } |
181 | |
182 | int nf_conntrack_icmpv6_error(struct nf_conn *tmpl, |
183 | struct sk_buff *skb, |
184 | unsigned int dataoff, |
185 | const struct nf_hook_state *state) |
186 | { |
187 | union nf_inet_addr outer_daddr; |
188 | const struct icmp6hdr *icmp6h; |
189 | struct icmp6hdr _ih; |
190 | int type; |
191 | |
192 | icmp6h = skb_header_pointer(skb, offset: dataoff, len: sizeof(_ih), buffer: &_ih); |
193 | if (icmp6h == NULL) { |
194 | icmpv6_error_log(skb, state, msg: "short packet" ); |
195 | return -NF_ACCEPT; |
196 | } |
197 | |
198 | if (state->hook == NF_INET_PRE_ROUTING && |
199 | state->net->ct.sysctl_checksum && |
200 | nf_ip6_checksum(skb, hook: state->hook, dataoff, IPPROTO_ICMPV6)) { |
201 | icmpv6_error_log(skb, state, msg: "ICMPv6 checksum failed" ); |
202 | return -NF_ACCEPT; |
203 | } |
204 | |
205 | type = icmp6h->icmp6_type - 130; |
206 | if (type >= 0 && type < sizeof(noct_valid_new) && |
207 | noct_valid_new[type]) { |
208 | nf_ct_set(skb, NULL, info: IP_CT_UNTRACKED); |
209 | return NF_ACCEPT; |
210 | } |
211 | |
212 | if (icmp6h->icmp6_type == NDISC_REDIRECT) |
213 | return nf_conntrack_icmpv6_redirect(tmpl, skb, dataoff, state); |
214 | |
215 | /* is not error message ? */ |
216 | if (icmp6h->icmp6_type >= 128) |
217 | return NF_ACCEPT; |
218 | |
219 | memcpy(&outer_daddr.ip6, &ipv6_hdr(skb)->daddr, |
220 | sizeof(outer_daddr.ip6)); |
221 | dataoff += sizeof(*icmp6h); |
222 | return nf_conntrack_inet_error(tmpl, skb, dataoff, state, |
223 | IPPROTO_ICMPV6, outer_daddr: &outer_daddr); |
224 | } |
225 | |
226 | #if IS_ENABLED(CONFIG_NF_CT_NETLINK) |
227 | |
228 | #include <linux/netfilter/nfnetlink.h> |
229 | #include <linux/netfilter/nfnetlink_conntrack.h> |
230 | static int icmpv6_tuple_to_nlattr(struct sk_buff *skb, |
231 | const struct nf_conntrack_tuple *t) |
232 | { |
233 | if (nla_put_be16(skb, attrtype: CTA_PROTO_ICMPV6_ID, value: t->src.u.icmp.id) || |
234 | nla_put_u8(skb, attrtype: CTA_PROTO_ICMPV6_TYPE, value: t->dst.u.icmp.type) || |
235 | nla_put_u8(skb, attrtype: CTA_PROTO_ICMPV6_CODE, value: t->dst.u.icmp.code)) |
236 | goto nla_put_failure; |
237 | return 0; |
238 | |
239 | nla_put_failure: |
240 | return -1; |
241 | } |
242 | |
243 | static const struct nla_policy icmpv6_nla_policy[CTA_PROTO_MAX+1] = { |
244 | [CTA_PROTO_ICMPV6_TYPE] = { .type = NLA_U8 }, |
245 | [CTA_PROTO_ICMPV6_CODE] = { .type = NLA_U8 }, |
246 | [CTA_PROTO_ICMPV6_ID] = { .type = NLA_U16 }, |
247 | }; |
248 | |
249 | static int icmpv6_nlattr_to_tuple(struct nlattr *tb[], |
250 | struct nf_conntrack_tuple *tuple, |
251 | u_int32_t flags) |
252 | { |
253 | if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_TYPE)) { |
254 | if (!tb[CTA_PROTO_ICMPV6_TYPE]) |
255 | return -EINVAL; |
256 | |
257 | tuple->dst.u.icmp.type = nla_get_u8(nla: tb[CTA_PROTO_ICMPV6_TYPE]); |
258 | if (tuple->dst.u.icmp.type < 128 || |
259 | tuple->dst.u.icmp.type - 128 >= sizeof(invmap) || |
260 | !invmap[tuple->dst.u.icmp.type - 128]) |
261 | return -EINVAL; |
262 | } |
263 | |
264 | if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_CODE)) { |
265 | if (!tb[CTA_PROTO_ICMPV6_CODE]) |
266 | return -EINVAL; |
267 | |
268 | tuple->dst.u.icmp.code = nla_get_u8(nla: tb[CTA_PROTO_ICMPV6_CODE]); |
269 | } |
270 | |
271 | if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_ID)) { |
272 | if (!tb[CTA_PROTO_ICMPV6_ID]) |
273 | return -EINVAL; |
274 | |
275 | tuple->src.u.icmp.id = nla_get_be16(nla: tb[CTA_PROTO_ICMPV6_ID]); |
276 | } |
277 | |
278 | return 0; |
279 | } |
280 | |
281 | static unsigned int icmpv6_nlattr_tuple_size(void) |
282 | { |
283 | static unsigned int size __read_mostly; |
284 | |
285 | if (!size) |
286 | size = nla_policy_len(icmpv6_nla_policy, CTA_PROTO_MAX + 1); |
287 | |
288 | return size; |
289 | } |
290 | #endif |
291 | |
292 | #ifdef CONFIG_NF_CONNTRACK_TIMEOUT |
293 | |
294 | #include <linux/netfilter/nfnetlink.h> |
295 | #include <linux/netfilter/nfnetlink_cttimeout.h> |
296 | |
297 | static int icmpv6_timeout_nlattr_to_obj(struct nlattr *tb[], |
298 | struct net *net, void *data) |
299 | { |
300 | unsigned int *timeout = data; |
301 | struct nf_icmp_net *in = nf_icmpv6_pernet(net); |
302 | |
303 | if (!timeout) |
304 | timeout = icmpv6_get_timeouts(net); |
305 | if (tb[CTA_TIMEOUT_ICMPV6_TIMEOUT]) { |
306 | *timeout = |
307 | ntohl(nla_get_be32(tb[CTA_TIMEOUT_ICMPV6_TIMEOUT])) * HZ; |
308 | } else { |
309 | /* Set default ICMPv6 timeout. */ |
310 | *timeout = in->timeout; |
311 | } |
312 | return 0; |
313 | } |
314 | |
315 | static int |
316 | icmpv6_timeout_obj_to_nlattr(struct sk_buff *skb, const void *data) |
317 | { |
318 | const unsigned int *timeout = data; |
319 | |
320 | if (nla_put_be32(skb, attrtype: CTA_TIMEOUT_ICMPV6_TIMEOUT, htonl(*timeout / HZ))) |
321 | goto nla_put_failure; |
322 | return 0; |
323 | |
324 | nla_put_failure: |
325 | return -ENOSPC; |
326 | } |
327 | |
328 | static const struct nla_policy |
329 | icmpv6_timeout_nla_policy[CTA_TIMEOUT_ICMPV6_MAX+1] = { |
330 | [CTA_TIMEOUT_ICMPV6_TIMEOUT] = { .type = NLA_U32 }, |
331 | }; |
332 | #endif /* CONFIG_NF_CONNTRACK_TIMEOUT */ |
333 | |
334 | void nf_conntrack_icmpv6_init_net(struct net *net) |
335 | { |
336 | struct nf_icmp_net *in = nf_icmpv6_pernet(net); |
337 | |
338 | in->timeout = nf_ct_icmpv6_timeout; |
339 | } |
340 | |
341 | const struct nf_conntrack_l4proto nf_conntrack_l4proto_icmpv6 = |
342 | { |
343 | .l4proto = IPPROTO_ICMPV6, |
344 | #if IS_ENABLED(CONFIG_NF_CT_NETLINK) |
345 | .tuple_to_nlattr = icmpv6_tuple_to_nlattr, |
346 | .nlattr_tuple_size = icmpv6_nlattr_tuple_size, |
347 | .nlattr_to_tuple = icmpv6_nlattr_to_tuple, |
348 | .nla_policy = icmpv6_nla_policy, |
349 | #endif |
350 | #ifdef CONFIG_NF_CONNTRACK_TIMEOUT |
351 | .ctnl_timeout = { |
352 | .nlattr_to_obj = icmpv6_timeout_nlattr_to_obj, |
353 | .obj_to_nlattr = icmpv6_timeout_obj_to_nlattr, |
354 | .nlattr_max = CTA_TIMEOUT_ICMP_MAX, |
355 | .obj_size = sizeof(unsigned int), |
356 | .nla_policy = icmpv6_timeout_nla_policy, |
357 | }, |
358 | #endif /* CONFIG_NF_CONNTRACK_TIMEOUT */ |
359 | }; |
360 | |