Merge tag 'for-linus-20180210' of git://git.kernel.dk/linux-block
[sfrench/cifs-2.6.git] / net / ipv6 / netfilter / nf_flow_table_ipv6.c
1 #include <linux/kernel.h>
2 #include <linux/init.h>
3 #include <linux/module.h>
4 #include <linux/netfilter.h>
5 #include <linux/rhashtable.h>
6 #include <linux/ipv6.h>
7 #include <linux/netdevice.h>
8 #include <net/ipv6.h>
9 #include <net/ip6_route.h>
10 #include <net/neighbour.h>
11 #include <net/netfilter/nf_flow_table.h>
12 #include <net/netfilter/nf_tables.h>
13 /* For layer 4 checksum field offset. */
14 #include <linux/tcp.h>
15 #include <linux/udp.h>
16
17 static int nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff,
18                                 struct in6_addr *addr,
19                                 struct in6_addr *new_addr)
20 {
21         struct tcphdr *tcph;
22
23         if (!pskb_may_pull(skb, thoff + sizeof(*tcph)) ||
24             skb_try_make_writable(skb, thoff + sizeof(*tcph)))
25                 return -1;
26
27         tcph = (void *)(skb_network_header(skb) + thoff);
28         inet_proto_csum_replace16(&tcph->check, skb, addr->s6_addr32,
29                                   new_addr->s6_addr32, true);
30
31         return 0;
32 }
33
34 static int nf_flow_nat_ipv6_udp(struct sk_buff *skb, unsigned int thoff,
35                                 struct in6_addr *addr,
36                                 struct in6_addr *new_addr)
37 {
38         struct udphdr *udph;
39
40         if (!pskb_may_pull(skb, thoff + sizeof(*udph)) ||
41             skb_try_make_writable(skb, thoff + sizeof(*udph)))
42                 return -1;
43
44         udph = (void *)(skb_network_header(skb) + thoff);
45         if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) {
46                 inet_proto_csum_replace16(&udph->check, skb, addr->s6_addr32,
47                                           new_addr->s6_addr32, true);
48                 if (!udph->check)
49                         udph->check = CSUM_MANGLED_0;
50         }
51
52         return 0;
53 }
54
55 static int nf_flow_nat_ipv6_l4proto(struct sk_buff *skb, struct ipv6hdr *ip6h,
56                                     unsigned int thoff, struct in6_addr *addr,
57                                     struct in6_addr *new_addr)
58 {
59         switch (ip6h->nexthdr) {
60         case IPPROTO_TCP:
61                 if (nf_flow_nat_ipv6_tcp(skb, thoff, addr, new_addr) < 0)
62                         return NF_DROP;
63                 break;
64         case IPPROTO_UDP:
65                 if (nf_flow_nat_ipv6_udp(skb, thoff, addr, new_addr) < 0)
66                         return NF_DROP;
67                 break;
68         }
69
70         return 0;
71 }
72
73 static int nf_flow_snat_ipv6(const struct flow_offload *flow,
74                              struct sk_buff *skb, struct ipv6hdr *ip6h,
75                              unsigned int thoff,
76                              enum flow_offload_tuple_dir dir)
77 {
78         struct in6_addr addr, new_addr;
79
80         switch (dir) {
81         case FLOW_OFFLOAD_DIR_ORIGINAL:
82                 addr = ip6h->saddr;
83                 new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v6;
84                 ip6h->saddr = new_addr;
85                 break;
86         case FLOW_OFFLOAD_DIR_REPLY:
87                 addr = ip6h->daddr;
88                 new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v6;
89                 ip6h->daddr = new_addr;
90                 break;
91         default:
92                 return -1;
93         }
94
95         return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
96 }
97
98 static int nf_flow_dnat_ipv6(const struct flow_offload *flow,
99                              struct sk_buff *skb, struct ipv6hdr *ip6h,
100                              unsigned int thoff,
101                              enum flow_offload_tuple_dir dir)
102 {
103         struct in6_addr addr, new_addr;
104
105         switch (dir) {
106         case FLOW_OFFLOAD_DIR_ORIGINAL:
107                 addr = ip6h->daddr;
108                 new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v6;
109                 ip6h->daddr = new_addr;
110                 break;
111         case FLOW_OFFLOAD_DIR_REPLY:
112                 addr = ip6h->saddr;
113                 new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v6;
114                 ip6h->saddr = new_addr;
115                 break;
116         default:
117                 return -1;
118         }
119
120         return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
121 }
122
123 static int nf_flow_nat_ipv6(const struct flow_offload *flow,
124                             struct sk_buff *skb,
125                             enum flow_offload_tuple_dir dir)
126 {
127         struct ipv6hdr *ip6h = ipv6_hdr(skb);
128         unsigned int thoff = sizeof(*ip6h);
129
130         if (flow->flags & FLOW_OFFLOAD_SNAT &&
131             (nf_flow_snat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
132              nf_flow_snat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
133                 return -1;
134         if (flow->flags & FLOW_OFFLOAD_DNAT &&
135             (nf_flow_dnat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
136              nf_flow_dnat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
137                 return -1;
138
139         return 0;
140 }
141
142 static int nf_flow_tuple_ipv6(struct sk_buff *skb, const struct net_device *dev,
143                               struct flow_offload_tuple *tuple)
144 {
145         struct flow_ports *ports;
146         struct ipv6hdr *ip6h;
147         unsigned int thoff;
148
149         if (!pskb_may_pull(skb, sizeof(*ip6h)))
150                 return -1;
151
152         ip6h = ipv6_hdr(skb);
153
154         if (ip6h->nexthdr != IPPROTO_TCP &&
155             ip6h->nexthdr != IPPROTO_UDP)
156                 return -1;
157
158         thoff = sizeof(*ip6h);
159         if (!pskb_may_pull(skb, thoff + sizeof(*ports)))
160                 return -1;
161
162         ports = (struct flow_ports *)(skb_network_header(skb) + thoff);
163
164         tuple->src_v6           = ip6h->saddr;
165         tuple->dst_v6           = ip6h->daddr;
166         tuple->src_port         = ports->source;
167         tuple->dst_port         = ports->dest;
168         tuple->l3proto          = AF_INET6;
169         tuple->l4proto          = ip6h->nexthdr;
170         tuple->iifidx           = dev->ifindex;
171
172         return 0;
173 }
174
175 /* Based on ip_exceeds_mtu(). */
176 static bool __nf_flow_exceeds_mtu(const struct sk_buff *skb, unsigned int mtu)
177 {
178         if (skb->len <= mtu)
179                 return false;
180
181         if (skb_is_gso(skb) && skb_gso_validate_mtu(skb, mtu))
182                 return false;
183
184         return true;
185 }
186
187 static bool nf_flow_exceeds_mtu(struct sk_buff *skb, const struct rt6_info *rt)
188 {
189         u32 mtu;
190
191         mtu = ip6_dst_mtu_forward(&rt->dst);
192         if (__nf_flow_exceeds_mtu(skb, mtu))
193                 return true;
194
195         return false;
196 }
197
198 unsigned int
199 nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
200                           const struct nf_hook_state *state)
201 {
202         struct flow_offload_tuple_rhash *tuplehash;
203         struct nf_flowtable *flow_table = priv;
204         struct flow_offload_tuple tuple = {};
205         enum flow_offload_tuple_dir dir;
206         struct flow_offload *flow;
207         struct net_device *outdev;
208         struct in6_addr *nexthop;
209         struct ipv6hdr *ip6h;
210         struct rt6_info *rt;
211
212         if (skb->protocol != htons(ETH_P_IPV6))
213                 return NF_ACCEPT;
214
215         if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
216                 return NF_ACCEPT;
217
218         tuplehash = flow_offload_lookup(flow_table, &tuple);
219         if (tuplehash == NULL)
220                 return NF_ACCEPT;
221
222         outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx);
223         if (!outdev)
224                 return NF_ACCEPT;
225
226         dir = tuplehash->tuple.dir;
227         flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
228
229         rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache;
230         if (unlikely(nf_flow_exceeds_mtu(skb, rt)))
231                 return NF_ACCEPT;
232
233         if (skb_try_make_writable(skb, sizeof(*ip6h)))
234                 return NF_DROP;
235
236         if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) &&
237             nf_flow_nat_ipv6(flow, skb, dir) < 0)
238                 return NF_DROP;
239
240         flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT;
241         ip6h = ipv6_hdr(skb);
242         ip6h->hop_limit--;
243
244         skb->dev = outdev;
245         nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
246         neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
247
248         return NF_STOLEN;
249 }
250 EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook);
251
252 static struct nf_flowtable_type flowtable_ipv6 = {
253         .family         = NFPROTO_IPV6,
254         .params         = &nf_flow_offload_rhash_params,
255         .gc             = nf_flow_offload_work_gc,
256         .free           = nf_flow_table_free,
257         .hook           = nf_flow_offload_ipv6_hook,
258         .owner          = THIS_MODULE,
259 };
260
261 static int __init nf_flow_ipv6_module_init(void)
262 {
263         nft_register_flowtable_type(&flowtable_ipv6);
264
265         return 0;
266 }
267
268 static void __exit nf_flow_ipv6_module_exit(void)
269 {
270         nft_unregister_flowtable_type(&flowtable_ipv6);
271 }
272
273 module_init(nf_flow_ipv6_module_init);
274 module_exit(nf_flow_ipv6_module_exit);
275
276 MODULE_LICENSE("GPL");
277 MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
278 MODULE_ALIAS_NF_FLOWTABLE(AF_INET6);