blob: c1c74491a10baf675b7e7064d9080eb078cf790a [file] [log] [blame]
Tomasz Bursztykaeb316282013-10-10 13:39:19 +02001/*
2 * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
3 * Copyright (c) 2012 Intel Corporation
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms and conditions of the GNU General Public License,
7 * version 2, as published by the Free Software Foundation.
8 *
9 */
10
11#include <linux/module.h>
12#include <linux/init.h>
13#include <linux/list.h>
14#include <linux/skbuff.h>
15#include <linux/ip.h>
16#include <linux/netfilter.h>
17#include <linux/netfilter_ipv6.h>
18#include <linux/netfilter/nf_tables.h>
19#include <net/netfilter/nf_conntrack.h>
20#include <net/netfilter/nf_nat.h>
21#include <net/netfilter/nf_nat_core.h>
22#include <net/netfilter/nf_tables.h>
23#include <net/netfilter/nf_tables_ipv6.h>
24#include <net/netfilter/nf_nat_l3proto.h>
25#include <net/ipv6.h>
26
27/*
28 * IPv6 NAT chains
29 */
30
Pablo Neira Ayuso2a5538e2014-08-25 12:05:27 +020031static unsigned int nft_nat_ipv6_fn(const struct nf_hook_ops *ops,
Tomasz Bursztykaeb316282013-10-10 13:39:19 +020032 struct sk_buff *skb,
33 const struct net_device *in,
34 const struct net_device *out,
35 int (*okfn)(struct sk_buff *))
36{
37 enum ip_conntrack_info ctinfo;
38 struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
39 struct nf_conn_nat *nat;
40 enum nf_nat_manip_type maniptype = HOOK2MANIP(ops->hooknum);
41 __be16 frag_off;
42 int hdrlen;
43 u8 nexthdr;
44 struct nft_pktinfo pkt;
45 unsigned int ret;
46
47 if (ct == NULL || nf_ct_is_untracked(ct))
48 return NF_ACCEPT;
49
Florian Westphalf768e5b2014-04-28 21:09:50 +020050 nat = nf_ct_nat_ext_add(ct);
51 if (nat == NULL)
52 return NF_ACCEPT;
Tomasz Bursztykaeb316282013-10-10 13:39:19 +020053
54 switch (ctinfo) {
55 case IP_CT_RELATED:
56 case IP_CT_RELATED + IP_CT_IS_REPLY:
57 nexthdr = ipv6_hdr(skb)->nexthdr;
58 hdrlen = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr),
59 &nexthdr, &frag_off);
60
61 if (hdrlen >= 0 && nexthdr == IPPROTO_ICMPV6) {
62 if (!nf_nat_icmpv6_reply_translation(skb, ct, ctinfo,
63 ops->hooknum,
64 hdrlen))
65 return NF_DROP;
66 else
67 return NF_ACCEPT;
68 }
69 /* Fall through */
70 case IP_CT_NEW:
71 if (nf_nat_initialized(ct, maniptype))
72 break;
73
74 nft_set_pktinfo_ipv6(&pkt, ops, skb, in, out);
75
Patrick McHardy3876d222014-01-09 18:42:43 +000076 ret = nft_do_chain(&pkt, ops);
Tomasz Bursztykaeb316282013-10-10 13:39:19 +020077 if (ret != NF_ACCEPT)
78 return ret;
79 if (!nf_nat_initialized(ct, maniptype)) {
80 ret = nf_nat_alloc_null_binding(ct, ops->hooknum);
81 if (ret != NF_ACCEPT)
82 return ret;
83 }
84 default:
85 break;
86 }
87
88 return nf_nat_packet(ct, ctinfo, ops->hooknum, skb);
89}
90
91static unsigned int nf_nat_ipv6_prerouting(const struct nf_hook_ops *ops,
92 struct sk_buff *skb,
93 const struct net_device *in,
94 const struct net_device *out,
95 int (*okfn)(struct sk_buff *))
96{
97 struct in6_addr daddr = ipv6_hdr(skb)->daddr;
98 unsigned int ret;
99
Pablo Neira Ayuso2a5538e2014-08-25 12:05:27 +0200100 ret = nft_nat_ipv6_fn(ops, skb, in, out, okfn);
Tomasz Bursztykaeb316282013-10-10 13:39:19 +0200101 if (ret != NF_DROP && ret != NF_STOLEN &&
102 ipv6_addr_cmp(&daddr, &ipv6_hdr(skb)->daddr))
103 skb_dst_drop(skb);
104
105 return ret;
106}
107
108static unsigned int nf_nat_ipv6_postrouting(const struct nf_hook_ops *ops,
109 struct sk_buff *skb,
110 const struct net_device *in,
111 const struct net_device *out,
112 int (*okfn)(struct sk_buff *))
113{
114 enum ip_conntrack_info ctinfo __maybe_unused;
115 const struct nf_conn *ct __maybe_unused;
116 unsigned int ret;
117
Pablo Neira Ayuso2a5538e2014-08-25 12:05:27 +0200118 ret = nft_nat_ipv6_fn(ops, skb, in, out, okfn);
Tomasz Bursztykaeb316282013-10-10 13:39:19 +0200119#ifdef CONFIG_XFRM
120 if (ret != NF_DROP && ret != NF_STOLEN &&
121 !(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) &&
122 (ct = nf_ct_get(skb, &ctinfo)) != NULL) {
123 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
124
125 if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3,
126 &ct->tuplehash[!dir].tuple.dst.u3) ||
127 (ct->tuplehash[dir].tuple.src.u.all !=
128 ct->tuplehash[!dir].tuple.dst.u.all))
129 if (nf_xfrm_me_harder(skb, AF_INET6) < 0)
130 ret = NF_DROP;
131 }
132#endif
133 return ret;
134}
135
136static unsigned int nf_nat_ipv6_output(const struct nf_hook_ops *ops,
137 struct sk_buff *skb,
138 const struct net_device *in,
139 const struct net_device *out,
140 int (*okfn)(struct sk_buff *))
141{
142 enum ip_conntrack_info ctinfo;
143 const struct nf_conn *ct;
144 unsigned int ret;
145
Pablo Neira Ayuso2a5538e2014-08-25 12:05:27 +0200146 ret = nft_nat_ipv6_fn(ops, skb, in, out, okfn);
Tomasz Bursztykaeb316282013-10-10 13:39:19 +0200147 if (ret != NF_DROP && ret != NF_STOLEN &&
148 (ct = nf_ct_get(skb, &ctinfo)) != NULL) {
149 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
150
151 if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.dst.u3,
152 &ct->tuplehash[!dir].tuple.src.u3)) {
153 if (ip6_route_me_harder(skb))
154 ret = NF_DROP;
155 }
156#ifdef CONFIG_XFRM
157 else if (!(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) &&
158 ct->tuplehash[dir].tuple.dst.u.all !=
159 ct->tuplehash[!dir].tuple.src.u.all)
160 if (nf_xfrm_me_harder(skb, AF_INET6))
161 ret = NF_DROP;
162#endif
163 }
164 return ret;
165}
166
Patrick McHardy2a37d752014-01-09 18:42:37 +0000167static const struct nf_chain_type nft_chain_nat_ipv6 = {
Tomasz Bursztykaeb316282013-10-10 13:39:19 +0200168 .name = "nat",
169 .type = NFT_CHAIN_T_NAT,
Patrick McHardyfa2c1de2014-01-09 18:42:38 +0000170 .family = NFPROTO_IPV6,
171 .owner = THIS_MODULE,
Tomasz Bursztykaeb316282013-10-10 13:39:19 +0200172 .hook_mask = (1 << NF_INET_PRE_ROUTING) |
173 (1 << NF_INET_POST_ROUTING) |
174 (1 << NF_INET_LOCAL_OUT) |
175 (1 << NF_INET_LOCAL_IN),
Patrick McHardyfa2c1de2014-01-09 18:42:38 +0000176 .hooks = {
Tomasz Bursztykaeb316282013-10-10 13:39:19 +0200177 [NF_INET_PRE_ROUTING] = nf_nat_ipv6_prerouting,
178 [NF_INET_POST_ROUTING] = nf_nat_ipv6_postrouting,
179 [NF_INET_LOCAL_OUT] = nf_nat_ipv6_output,
Pablo Neira Ayuso2a5538e2014-08-25 12:05:27 +0200180 [NF_INET_LOCAL_IN] = nft_nat_ipv6_fn,
Tomasz Bursztykaeb316282013-10-10 13:39:19 +0200181 },
Tomasz Bursztykaeb316282013-10-10 13:39:19 +0200182};
183
184static int __init nft_chain_nat_ipv6_init(void)
185{
186 int err;
187
188 err = nft_register_chain_type(&nft_chain_nat_ipv6);
189 if (err < 0)
190 return err;
191
192 return 0;
193}
194
195static void __exit nft_chain_nat_ipv6_exit(void)
196{
197 nft_unregister_chain_type(&nft_chain_nat_ipv6);
198}
199
200module_init(nft_chain_nat_ipv6_init);
201module_exit(nft_chain_nat_ipv6_exit);
202
203MODULE_LICENSE("GPL");
204MODULE_AUTHOR("Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>");
205MODULE_ALIAS_NFT_CHAIN(AF_INET6, "nat");