]>
Commit | Line | Data |
---|---|---|
9fb9cbb1 YK |
1 | /* |
2 | * Copyright (C)2004 USAGI/WIDE Project | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License version 2 as | |
6 | * published by the Free Software Foundation. | |
7 | * | |
8 | * Author: | |
9 | * Yasuyuki Kozakai @USAGI <yasuyuki.kozakai@toshiba.co.jp> | |
9fb9cbb1 YK |
10 | */ |
11 | ||
9fb9cbb1 YK |
12 | #include <linux/types.h> |
13 | #include <linux/ipv6.h> | |
14 | #include <linux/in6.h> | |
15 | #include <linux/netfilter.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/skbuff.h> | |
18 | #include <linux/icmp.h> | |
9fb9cbb1 | 19 | #include <net/ipv6.h> |
04128f23 | 20 | #include <net/inet_frag.h> |
9fb9cbb1 | 21 | |
8fa9ff68 | 22 | #include <linux/netfilter_bridge.h> |
9fb9cbb1 | 23 | #include <linux/netfilter_ipv6.h> |
121d1e09 | 24 | #include <linux/netfilter_ipv6/ip6_tables.h> |
9fb9cbb1 YK |
25 | #include <net/netfilter/nf_conntrack.h> |
26 | #include <net/netfilter/nf_conntrack_helper.h> | |
605dcad6 | 27 | #include <net/netfilter/nf_conntrack_l4proto.h> |
9fb9cbb1 YK |
28 | #include <net/netfilter/nf_conntrack_l3proto.h> |
29 | #include <net/netfilter/nf_conntrack_core.h> | |
5d0aa2cc | 30 | #include <net/netfilter/nf_conntrack_zones.h> |
41d73ec0 | 31 | #include <net/netfilter/nf_conntrack_seqadj.h> |
9d2493f8 | 32 | #include <net/netfilter/ipv6/nf_conntrack_ipv6.h> |
58a317f1 | 33 | #include <net/netfilter/nf_nat_helper.h> |
e97c3e27 | 34 | #include <net/netfilter/ipv6/nf_defrag_ipv6.h> |
74f7a655 | 35 | #include <net/netfilter/nf_log.h> |
9fb9cbb1 | 36 | |
0c66dc1e FW |
37 | static int conntrack6_net_id; |
38 | static DEFINE_MUTEX(register_ipv6_hooks); | |
39 | ||
40 | struct conntrack6_net { | |
41 | unsigned int users; | |
42 | }; | |
43 | ||
8ce8439a JE |
44 | static bool ipv6_pkt_to_tuple(const struct sk_buff *skb, unsigned int nhoff, |
45 | struct nf_conntrack_tuple *tuple) | |
9fb9cbb1 | 46 | { |
32948588 JE |
47 | const u_int32_t *ap; |
48 | u_int32_t _addrs[8]; | |
9fb9cbb1 YK |
49 | |
50 | ap = skb_header_pointer(skb, nhoff + offsetof(struct ipv6hdr, saddr), | |
51 | sizeof(_addrs), _addrs); | |
52 | if (ap == NULL) | |
8ce8439a | 53 | return false; |
9fb9cbb1 YK |
54 | |
55 | memcpy(tuple->src.u3.ip6, ap, sizeof(tuple->src.u3.ip6)); | |
56 | memcpy(tuple->dst.u3.ip6, ap + 4, sizeof(tuple->dst.u3.ip6)); | |
57 | ||
8ce8439a | 58 | return true; |
9fb9cbb1 YK |
59 | } |
60 | ||
8ce8439a JE |
61 | static bool ipv6_invert_tuple(struct nf_conntrack_tuple *tuple, |
62 | const struct nf_conntrack_tuple *orig) | |
9fb9cbb1 YK |
63 | { |
64 | memcpy(tuple->src.u3.ip6, orig->dst.u3.ip6, sizeof(tuple->src.u3.ip6)); | |
65 | memcpy(tuple->dst.u3.ip6, orig->src.u3.ip6, sizeof(tuple->dst.u3.ip6)); | |
66 | ||
8ce8439a | 67 | return true; |
9fb9cbb1 YK |
68 | } |
69 | ||
824f1fbe | 70 | static void ipv6_print_tuple(struct seq_file *s, |
9fb9cbb1 YK |
71 | const struct nf_conntrack_tuple *tuple) |
72 | { | |
824f1fbe JP |
73 | seq_printf(s, "src=%pI6 dst=%pI6 ", |
74 | tuple->src.u3.ip6, tuple->dst.u3.ip6); | |
9fb9cbb1 YK |
75 | } |
76 | ||
ffc30690 YK |
77 | static int ipv6_get_l4proto(const struct sk_buff *skb, unsigned int nhoff, |
78 | unsigned int *dataoff, u_int8_t *protonum) | |
9fb9cbb1 | 79 | { |
ffc30690 | 80 | unsigned int extoff = nhoff + sizeof(struct ipv6hdr); |
2b60af01 | 81 | __be16 frag_off; |
ffc30690 | 82 | int protoff; |
2b60af01 | 83 | u8 nexthdr; |
ffc30690 YK |
84 | |
85 | if (skb_copy_bits(skb, nhoff + offsetof(struct ipv6hdr, nexthdr), | |
2b60af01 | 86 | &nexthdr, sizeof(nexthdr)) != 0) { |
ffc30690 YK |
87 | pr_debug("ip6_conntrack_core: can't get nexthdr\n"); |
88 | return -NF_ACCEPT; | |
89 | } | |
2b60af01 | 90 | protoff = ipv6_skip_exthdr(skb, extoff, &nexthdr, &frag_off); |
9fb9cbb1 | 91 | /* |
d7a769ff FF |
92 | * (protoff == skb->len) means the packet has not data, just |
93 | * IPv6 and possibly extensions headers, but it is tracked anyway | |
9fb9cbb1 | 94 | */ |
2b60af01 | 95 | if (protoff < 0 || (frag_off & htons(~0x7)) != 0) { |
0d53778e | 96 | pr_debug("ip6_conntrack_core: can't find proto in pkt\n"); |
9fb9cbb1 YK |
97 | return -NF_ACCEPT; |
98 | } | |
99 | ||
100 | *dataoff = protoff; | |
2b60af01 | 101 | *protonum = nexthdr; |
9fb9cbb1 YK |
102 | return NF_ACCEPT; |
103 | } | |
104 | ||
06198b34 | 105 | static unsigned int ipv6_helper(void *priv, |
12f7a505 | 106 | struct sk_buff *skb, |
238e54c9 | 107 | const struct nf_hook_state *state) |
9fb9cbb1 YK |
108 | { |
109 | struct nf_conn *ct; | |
32948588 JE |
110 | const struct nf_conn_help *help; |
111 | const struct nf_conntrack_helper *helper; | |
9fb9cbb1 | 112 | enum ip_conntrack_info ctinfo; |
4cdd3408 PM |
113 | __be16 frag_off; |
114 | int protoff; | |
115 | u8 nexthdr; | |
9fb9cbb1 YK |
116 | |
117 | /* This is where we call the helper: as the packet goes out. */ | |
3db05fea | 118 | ct = nf_ct_get(skb, &ctinfo); |
fb048833 | 119 | if (!ct || ctinfo == IP_CT_RELATED_REPLY) |
12f7a505 | 120 | return NF_ACCEPT; |
dc808fe2 HW |
121 | |
122 | help = nfct_help(ct); | |
3c158f7f | 123 | if (!help) |
12f7a505 | 124 | return NF_ACCEPT; |
e2361cb9 | 125 | /* rcu_read_lock()ed by nf_hook_thresh */ |
3c158f7f PM |
126 | helper = rcu_dereference(help->helper); |
127 | if (!helper) | |
12f7a505 | 128 | return NF_ACCEPT; |
dc808fe2 | 129 | |
4cdd3408 PM |
130 | nexthdr = ipv6_hdr(skb)->nexthdr; |
131 | protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr, | |
132 | &frag_off); | |
133 | if (protoff < 0 || (frag_off & htons(~0x7)) != 0) { | |
0d53778e | 134 | pr_debug("proto header not found\n"); |
dc808fe2 | 135 | return NF_ACCEPT; |
9fb9cbb1 YK |
136 | } |
137 | ||
b20ab9cc | 138 | return helper->help(skb, protoff, ct, ctinfo); |
12f7a505 PNA |
139 | } |
140 | ||
06198b34 | 141 | static unsigned int ipv6_confirm(void *priv, |
12f7a505 | 142 | struct sk_buff *skb, |
238e54c9 | 143 | const struct nf_hook_state *state) |
12f7a505 | 144 | { |
58a317f1 PM |
145 | struct nf_conn *ct; |
146 | enum ip_conntrack_info ctinfo; | |
147 | unsigned char pnum = ipv6_hdr(skb)->nexthdr; | |
148 | int protoff; | |
149 | __be16 frag_off; | |
150 | ||
151 | ct = nf_ct_get(skb, &ctinfo); | |
152 | if (!ct || ctinfo == IP_CT_RELATED_REPLY) | |
153 | goto out; | |
154 | ||
155 | protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &pnum, | |
156 | &frag_off); | |
157 | if (protoff < 0 || (frag_off & htons(~0x7)) != 0) { | |
158 | pr_debug("proto header not found\n"); | |
159 | goto out; | |
160 | } | |
161 | ||
162 | /* adjust seqs for loopback traffic only in outgoing direction */ | |
163 | if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) && | |
164 | !nf_is_loopback_packet(skb)) { | |
41d73ec0 | 165 | if (!nf_ct_seq_adjust(skb, ct, ctinfo, protoff)) { |
58a317f1 PM |
166 | NF_CT_STAT_INC_ATOMIC(nf_ct_net(ct), drop); |
167 | return NF_DROP; | |
168 | } | |
169 | } | |
170 | out: | |
9fb9cbb1 | 171 | /* We've seen it coming out the other side: confirm it */ |
3db05fea | 172 | return nf_conntrack_confirm(skb); |
9fb9cbb1 YK |
173 | } |
174 | ||
06198b34 | 175 | static unsigned int ipv6_conntrack_in(void *priv, |
a702a65f | 176 | struct sk_buff *skb, |
238e54c9 | 177 | const struct nf_hook_state *state) |
a702a65f | 178 | { |
082a758f | 179 | return nf_conntrack_in(state->net, PF_INET6, state->hook, skb); |
9fb9cbb1 YK |
180 | } |
181 | ||
06198b34 | 182 | static unsigned int ipv6_conntrack_local(void *priv, |
3db05fea | 183 | struct sk_buff *skb, |
238e54c9 | 184 | const struct nf_hook_state *state) |
9fb9cbb1 YK |
185 | { |
186 | /* root is playing with raw sockets. */ | |
3db05fea | 187 | if (skb->len < sizeof(struct ipv6hdr)) { |
e87cc472 | 188 | net_notice_ratelimited("ipv6_conntrack_local: packet too short\n"); |
9fb9cbb1 YK |
189 | return NF_ACCEPT; |
190 | } | |
082a758f | 191 | return nf_conntrack_in(state->net, PF_INET6, state->hook, skb); |
9fb9cbb1 YK |
192 | } |
193 | ||
1999414a | 194 | static struct nf_hook_ops ipv6_conntrack_ops[] __read_mostly = { |
964ddaa1 PM |
195 | { |
196 | .hook = ipv6_conntrack_in, | |
57750a22 | 197 | .pf = NFPROTO_IPV6, |
6e23ae2a | 198 | .hooknum = NF_INET_PRE_ROUTING, |
964ddaa1 PM |
199 | .priority = NF_IP6_PRI_CONNTRACK, |
200 | }, | |
201 | { | |
202 | .hook = ipv6_conntrack_local, | |
57750a22 | 203 | .pf = NFPROTO_IPV6, |
6e23ae2a | 204 | .hooknum = NF_INET_LOCAL_OUT, |
964ddaa1 PM |
205 | .priority = NF_IP6_PRI_CONNTRACK, |
206 | }, | |
12f7a505 PNA |
207 | { |
208 | .hook = ipv6_helper, | |
12f7a505 PNA |
209 | .pf = NFPROTO_IPV6, |
210 | .hooknum = NF_INET_POST_ROUTING, | |
211 | .priority = NF_IP6_PRI_CONNTRACK_HELPER, | |
212 | }, | |
964ddaa1 PM |
213 | { |
214 | .hook = ipv6_confirm, | |
57750a22 | 215 | .pf = NFPROTO_IPV6, |
6e23ae2a | 216 | .hooknum = NF_INET_POST_ROUTING, |
964ddaa1 PM |
217 | .priority = NF_IP6_PRI_LAST, |
218 | }, | |
12f7a505 PNA |
219 | { |
220 | .hook = ipv6_helper, | |
12f7a505 PNA |
221 | .pf = NFPROTO_IPV6, |
222 | .hooknum = NF_INET_LOCAL_IN, | |
223 | .priority = NF_IP6_PRI_CONNTRACK_HELPER, | |
224 | }, | |
964ddaa1 PM |
225 | { |
226 | .hook = ipv6_confirm, | |
57750a22 | 227 | .pf = NFPROTO_IPV6, |
6e23ae2a | 228 | .hooknum = NF_INET_LOCAL_IN, |
964ddaa1 PM |
229 | .priority = NF_IP6_PRI_LAST-1, |
230 | }, | |
9fb9cbb1 YK |
231 | }; |
232 | ||
121d1e09 FW |
233 | static int |
234 | ipv6_getorigdst(struct sock *sk, int optval, void __user *user, int *len) | |
235 | { | |
236 | const struct inet_sock *inet = inet_sk(sk); | |
237 | const struct ipv6_pinfo *inet6 = inet6_sk(sk); | |
238 | const struct nf_conntrack_tuple_hash *h; | |
239 | struct sockaddr_in6 sin6; | |
240 | struct nf_conntrack_tuple tuple = { .src.l3num = NFPROTO_IPV6 }; | |
241 | struct nf_conn *ct; | |
242 | ||
efe4208f | 243 | tuple.src.u3.in6 = sk->sk_v6_rcv_saddr; |
121d1e09 | 244 | tuple.src.u.tcp.port = inet->inet_sport; |
efe4208f | 245 | tuple.dst.u3.in6 = sk->sk_v6_daddr; |
121d1e09 FW |
246 | tuple.dst.u.tcp.port = inet->inet_dport; |
247 | tuple.dst.protonum = sk->sk_protocol; | |
248 | ||
249 | if (sk->sk_protocol != IPPROTO_TCP && sk->sk_protocol != IPPROTO_SCTP) | |
250 | return -ENOPROTOOPT; | |
251 | ||
252 | if (*len < 0 || (unsigned int) *len < sizeof(sin6)) | |
253 | return -EINVAL; | |
254 | ||
308ac914 | 255 | h = nf_conntrack_find_get(sock_net(sk), &nf_ct_zone_dflt, &tuple); |
121d1e09 FW |
256 | if (!h) { |
257 | pr_debug("IP6T_SO_ORIGINAL_DST: Can't find %pI6c/%u-%pI6c/%u.\n", | |
258 | &tuple.src.u3.ip6, ntohs(tuple.src.u.tcp.port), | |
259 | &tuple.dst.u3.ip6, ntohs(tuple.dst.u.tcp.port)); | |
260 | return -ENOENT; | |
261 | } | |
262 | ||
263 | ct = nf_ct_tuplehash_to_ctrack(h); | |
264 | ||
265 | sin6.sin6_family = AF_INET6; | |
266 | sin6.sin6_port = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.tcp.port; | |
267 | sin6.sin6_flowinfo = inet6->flow_label & IPV6_FLOWINFO_MASK; | |
268 | memcpy(&sin6.sin6_addr, | |
269 | &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.in6, | |
270 | sizeof(sin6.sin6_addr)); | |
121d1e09 FW |
271 | |
272 | nf_ct_put(ct); | |
d00bd3d4 HFS |
273 | sin6.sin6_scope_id = ipv6_iface_scope_id(&sin6.sin6_addr, |
274 | sk->sk_bound_dev_if); | |
121d1e09 FW |
275 | return copy_to_user(user, &sin6, sizeof(sin6)) ? -EFAULT : 0; |
276 | } | |
277 | ||
07a93626 | 278 | #if IS_ENABLED(CONFIG_NF_CT_NETLINK) |
c1d10adb PNA |
279 | |
280 | #include <linux/netfilter/nfnetlink.h> | |
281 | #include <linux/netfilter/nfnetlink_conntrack.h> | |
282 | ||
fdf70832 | 283 | static int ipv6_tuple_to_nlattr(struct sk_buff *skb, |
c1d10adb PNA |
284 | const struct nf_conntrack_tuple *tuple) |
285 | { | |
930345ea JB |
286 | if (nla_put_in6_addr(skb, CTA_IP_V6_SRC, &tuple->src.u3.in6) || |
287 | nla_put_in6_addr(skb, CTA_IP_V6_DST, &tuple->dst.u3.in6)) | |
e549a6b3 | 288 | goto nla_put_failure; |
c1d10adb PNA |
289 | return 0; |
290 | ||
df6fb868 | 291 | nla_put_failure: |
c1d10adb PNA |
292 | return -1; |
293 | } | |
294 | ||
f73e924c PM |
295 | static const struct nla_policy ipv6_nla_policy[CTA_IP_MAX+1] = { |
296 | [CTA_IP_V6_SRC] = { .len = sizeof(u_int32_t)*4 }, | |
297 | [CTA_IP_V6_DST] = { .len = sizeof(u_int32_t)*4 }, | |
c1d10adb PNA |
298 | }; |
299 | ||
fdf70832 | 300 | static int ipv6_nlattr_to_tuple(struct nlattr *tb[], |
c1d10adb PNA |
301 | struct nf_conntrack_tuple *t) |
302 | { | |
df6fb868 | 303 | if (!tb[CTA_IP_V6_SRC] || !tb[CTA_IP_V6_DST]) |
c1d10adb PNA |
304 | return -EINVAL; |
305 | ||
67b61f6c JB |
306 | t->src.u3.in6 = nla_get_in6_addr(tb[CTA_IP_V6_SRC]); |
307 | t->dst.u3.in6 = nla_get_in6_addr(tb[CTA_IP_V6_DST]); | |
c1d10adb PNA |
308 | |
309 | return 0; | |
310 | } | |
a400c30e HE |
311 | |
312 | static int ipv6_nlattr_tuple_size(void) | |
313 | { | |
314 | return nla_policy_len(ipv6_nla_policy, CTA_IP_MAX + 1); | |
315 | } | |
c1d10adb PNA |
316 | #endif |
317 | ||
0c66dc1e FW |
318 | static int ipv6_hooks_register(struct net *net) |
319 | { | |
320 | struct conntrack6_net *cnet = net_generic(net, conntrack6_net_id); | |
321 | int err = 0; | |
322 | ||
323 | mutex_lock(®ister_ipv6_hooks); | |
324 | cnet->users++; | |
325 | if (cnet->users > 1) | |
326 | goto out_unlock; | |
327 | ||
834184b1 FW |
328 | err = nf_defrag_ipv6_enable(net); |
329 | if (err < 0) { | |
330 | cnet->users = 0; | |
331 | goto out_unlock; | |
332 | } | |
333 | ||
0c66dc1e FW |
334 | err = nf_register_net_hooks(net, ipv6_conntrack_ops, |
335 | ARRAY_SIZE(ipv6_conntrack_ops)); | |
336 | if (err) | |
337 | cnet->users = 0; | |
338 | out_unlock: | |
339 | mutex_unlock(®ister_ipv6_hooks); | |
340 | return err; | |
341 | } | |
342 | ||
343 | static void ipv6_hooks_unregister(struct net *net) | |
344 | { | |
345 | struct conntrack6_net *cnet = net_generic(net, conntrack6_net_id); | |
346 | ||
347 | mutex_lock(®ister_ipv6_hooks); | |
348 | if (cnet->users && (--cnet->users == 0)) | |
349 | nf_unregister_net_hooks(net, ipv6_conntrack_ops, | |
350 | ARRAY_SIZE(ipv6_conntrack_ops)); | |
351 | mutex_unlock(®ister_ipv6_hooks); | |
352 | } | |
353 | ||
61075af5 | 354 | struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv6 __read_mostly = { |
9fb9cbb1 YK |
355 | .l3proto = PF_INET6, |
356 | .name = "ipv6", | |
357 | .pkt_to_tuple = ipv6_pkt_to_tuple, | |
358 | .invert_tuple = ipv6_invert_tuple, | |
359 | .print_tuple = ipv6_print_tuple, | |
ffc30690 | 360 | .get_l4proto = ipv6_get_l4proto, |
07a93626 | 361 | #if IS_ENABLED(CONFIG_NF_CT_NETLINK) |
fdf70832 | 362 | .tuple_to_nlattr = ipv6_tuple_to_nlattr, |
a400c30e | 363 | .nlattr_tuple_size = ipv6_nlattr_tuple_size, |
fdf70832 | 364 | .nlattr_to_tuple = ipv6_nlattr_to_tuple, |
f73e924c | 365 | .nla_policy = ipv6_nla_policy, |
c1d10adb | 366 | #endif |
0c66dc1e FW |
367 | .net_ns_get = ipv6_hooks_register, |
368 | .net_ns_put = ipv6_hooks_unregister, | |
9fb9cbb1 YK |
369 | .me = THIS_MODULE, |
370 | }; | |
371 | ||
32292a7f PM |
372 | MODULE_ALIAS("nf_conntrack-" __stringify(AF_INET6)); |
373 | MODULE_LICENSE("GPL"); | |
374 | MODULE_AUTHOR("Yasuyuki KOZAKAI @USAGI <yasuyuki.kozakai@toshiba.co.jp>"); | |
375 | ||
121d1e09 FW |
376 | static struct nf_sockopt_ops so_getorigdst6 = { |
377 | .pf = NFPROTO_IPV6, | |
378 | .get_optmin = IP6T_SO_ORIGINAL_DST, | |
379 | .get_optmax = IP6T_SO_ORIGINAL_DST + 1, | |
380 | .get = ipv6_getorigdst, | |
381 | .owner = THIS_MODULE, | |
382 | }; | |
383 | ||
0e54d217 DC |
384 | static struct nf_conntrack_l4proto *builtin_l4proto6[] = { |
385 | &nf_conntrack_l4proto_tcp6, | |
386 | &nf_conntrack_l4proto_udp6, | |
387 | &nf_conntrack_l4proto_icmpv6, | |
c51d3901 DC |
388 | #ifdef CONFIG_NF_CT_PROTO_DCCP |
389 | &nf_conntrack_l4proto_dccp6, | |
390 | #endif | |
a85406af DC |
391 | #ifdef CONFIG_NF_CT_PROTO_SCTP |
392 | &nf_conntrack_l4proto_sctp6, | |
393 | #endif | |
9b91c96c DC |
394 | #ifdef CONFIG_NF_CT_PROTO_UDPLITE |
395 | &nf_conntrack_l4proto_udplite6, | |
396 | #endif | |
0e54d217 DC |
397 | }; |
398 | ||
a7c439d3 | 399 | static int ipv6_net_init(struct net *net) |
9fb9cbb1 YK |
400 | { |
401 | int ret = 0; | |
402 | ||
0e54d217 DC |
403 | ret = nf_ct_l4proto_pernet_register(net, builtin_l4proto6, |
404 | ARRAY_SIZE(builtin_l4proto6)); | |
405 | if (ret < 0) | |
406 | return ret; | |
407 | ||
6330750d | 408 | ret = nf_ct_l3proto_pernet_register(net, &nf_conntrack_l3proto_ipv6); |
9fb9cbb1 | 409 | if (ret < 0) { |
6330750d | 410 | pr_err("nf_conntrack_ipv6: pernet registration failed.\n"); |
0e54d217 DC |
411 | nf_ct_l4proto_pernet_unregister(net, builtin_l4proto6, |
412 | ARRAY_SIZE(builtin_l4proto6)); | |
9fb9cbb1 | 413 | } |
a7c439d3 G |
414 | return ret; |
415 | } | |
9fb9cbb1 | 416 | |
a7c439d3 G |
417 | static void ipv6_net_exit(struct net *net) |
418 | { | |
6330750d | 419 | nf_ct_l3proto_pernet_unregister(net, &nf_conntrack_l3proto_ipv6); |
0e54d217 DC |
420 | nf_ct_l4proto_pernet_unregister(net, builtin_l4proto6, |
421 | ARRAY_SIZE(builtin_l4proto6)); | |
a7c439d3 G |
422 | } |
423 | ||
424 | static struct pernet_operations ipv6_net_ops = { | |
425 | .init = ipv6_net_init, | |
426 | .exit = ipv6_net_exit, | |
0c66dc1e FW |
427 | .id = &conntrack6_net_id, |
428 | .size = sizeof(struct conntrack6_net), | |
a7c439d3 G |
429 | }; |
430 | ||
431 | static int __init nf_conntrack_l3proto_ipv6_init(void) | |
432 | { | |
433 | int ret = 0; | |
434 | ||
435 | need_conntrack(); | |
a7c439d3 | 436 | |
121d1e09 FW |
437 | ret = nf_register_sockopt(&so_getorigdst6); |
438 | if (ret < 0) { | |
439 | pr_err("Unable to register netfilter socket option\n"); | |
440 | return ret; | |
441 | } | |
442 | ||
a7c439d3 G |
443 | ret = register_pernet_subsys(&ipv6_net_ops); |
444 | if (ret < 0) | |
6330750d G |
445 | goto cleanup_sockopt; |
446 | ||
0e54d217 DC |
447 | ret = nf_ct_l4proto_register(builtin_l4proto6, |
448 | ARRAY_SIZE(builtin_l4proto6)); | |
449 | if (ret < 0) | |
0c66dc1e | 450 | goto cleanup_pernet; |
c296bb4d | 451 | |
6330750d G |
452 | ret = nf_ct_l3proto_register(&nf_conntrack_l3proto_ipv6); |
453 | if (ret < 0) { | |
454 | pr_err("nf_conntrack_ipv6: can't register ipv6 proto.\n"); | |
0e54d217 | 455 | goto cleanup_l4proto; |
9fb9cbb1 | 456 | } |
9fb9cbb1 | 457 | return ret; |
0e54d217 DC |
458 | cleanup_l4proto: |
459 | nf_ct_l4proto_unregister(builtin_l4proto6, | |
460 | ARRAY_SIZE(builtin_l4proto6)); | |
a7c439d3 | 461 | cleanup_pernet: |
6330750d G |
462 | unregister_pernet_subsys(&ipv6_net_ops); |
463 | cleanup_sockopt: | |
121d1e09 | 464 | nf_unregister_sockopt(&so_getorigdst6); |
9fb9cbb1 YK |
465 | return ret; |
466 | } | |
467 | ||
65b4b4e8 | 468 | static void __exit nf_conntrack_l3proto_ipv6_fini(void) |
9fb9cbb1 | 469 | { |
32292a7f | 470 | synchronize_net(); |
6330750d | 471 | nf_ct_l3proto_unregister(&nf_conntrack_l3proto_ipv6); |
0e54d217 DC |
472 | nf_ct_l4proto_unregister(builtin_l4proto6, |
473 | ARRAY_SIZE(builtin_l4proto6)); | |
a7c439d3 | 474 | unregister_pernet_subsys(&ipv6_net_ops); |
121d1e09 | 475 | nf_unregister_sockopt(&so_getorigdst6); |
9fb9cbb1 YK |
476 | } |
477 | ||
65b4b4e8 AM |
478 | module_init(nf_conntrack_l3proto_ipv6_init); |
479 | module_exit(nf_conntrack_l3proto_ipv6_fini); |