blob: 678d2df4b8d93915185bd14ca7fa8cf9931472e4 [file] [log] [blame]
Tom Herbert65d7ab82015-08-17 13:42:27 -07001#include <linux/errno.h>
2#include <linux/ip.h>
3#include <linux/kernel.h>
4#include <linux/module.h>
5#include <linux/skbuff.h>
6#include <linux/socket.h>
7#include <linux/types.h>
8#include <net/checksum.h>
9#include <net/ip.h>
10#include <net/ip6_fib.h>
11#include <net/lwtunnel.h>
12#include <net/protocol.h>
13#include <uapi/linux/ila.h>
14
15struct ila_params {
16 __be64 locator;
Tom Herbert92b78af2015-08-24 09:45:42 -070017 __be64 locator_match;
18 __wsum csum_diff;
Tom Herbert65d7ab82015-08-17 13:42:27 -070019};
20
21static inline struct ila_params *ila_params_lwtunnel(
22 struct lwtunnel_state *lwstate)
23{
24 return (struct ila_params *)lwstate->data;
25}
26
27static inline __wsum compute_csum_diff8(const __be32 *from, const __be32 *to)
28{
29 __be32 diff[] = {
30 ~from[0], ~from[1], to[0], to[1],
31 };
32
33 return csum_partial(diff, sizeof(diff), 0);
34}
35
36static inline __wsum get_csum_diff(struct ipv6hdr *ip6h, struct ila_params *p)
37{
Tom Herbert92b78af2015-08-24 09:45:42 -070038 if (*(__be64 *)&ip6h->daddr == p->locator_match)
39 return p->csum_diff;
40 else
Tom Herbert65d7ab82015-08-17 13:42:27 -070041 return compute_csum_diff8((__be32 *)&ip6h->daddr,
42 (__be32 *)&p->locator);
43}
44
45static void update_ipv6_locator(struct sk_buff *skb, struct ila_params *p)
46{
47 __wsum diff;
48 struct ipv6hdr *ip6h = ipv6_hdr(skb);
49 size_t nhoff = sizeof(struct ipv6hdr);
50
51 /* First update checksum */
52 switch (ip6h->nexthdr) {
53 case NEXTHDR_TCP:
54 if (likely(pskb_may_pull(skb, nhoff + sizeof(struct tcphdr)))) {
55 struct tcphdr *th = (struct tcphdr *)
56 (skb_network_header(skb) + nhoff);
57
58 diff = get_csum_diff(ip6h, p);
59 inet_proto_csum_replace_by_diff(&th->check, skb,
60 diff, true);
61 }
62 break;
63 case NEXTHDR_UDP:
64 if (likely(pskb_may_pull(skb, nhoff + sizeof(struct udphdr)))) {
65 struct udphdr *uh = (struct udphdr *)
66 (skb_network_header(skb) + nhoff);
67
68 if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) {
69 diff = get_csum_diff(ip6h, p);
70 inet_proto_csum_replace_by_diff(&uh->check, skb,
71 diff, true);
72 if (!uh->check)
73 uh->check = CSUM_MANGLED_0;
74 }
75 }
76 break;
77 case NEXTHDR_ICMP:
78 if (likely(pskb_may_pull(skb,
79 nhoff + sizeof(struct icmp6hdr)))) {
80 struct icmp6hdr *ih = (struct icmp6hdr *)
81 (skb_network_header(skb) + nhoff);
82
83 diff = get_csum_diff(ip6h, p);
84 inet_proto_csum_replace_by_diff(&ih->icmp6_cksum, skb,
85 diff, true);
86 }
87 break;
88 }
89
90 /* Now change destination address */
91 *(__be64 *)&ip6h->daddr = p->locator;
92}
93
94static int ila_output(struct sock *sk, struct sk_buff *skb)
95{
96 struct dst_entry *dst = skb_dst(skb);
Tom Herbert65d7ab82015-08-17 13:42:27 -070097
98 if (skb->protocol != htons(ETH_P_IPV6))
99 goto drop;
100
Jiri Benc61adedf2015-08-20 13:56:25 +0200101 update_ipv6_locator(skb, ila_params_lwtunnel(dst->lwtstate));
Tom Herbert65d7ab82015-08-17 13:42:27 -0700102
Jiri Benc61adedf2015-08-20 13:56:25 +0200103 return dst->lwtstate->orig_output(sk, skb);
Tom Herbert65d7ab82015-08-17 13:42:27 -0700104
105drop:
106 kfree_skb(skb);
107 return -EINVAL;
108}
109
110static int ila_input(struct sk_buff *skb)
111{
112 struct dst_entry *dst = skb_dst(skb);
Tom Herbert65d7ab82015-08-17 13:42:27 -0700113
114 if (skb->protocol != htons(ETH_P_IPV6))
115 goto drop;
116
Jiri Benc61adedf2015-08-20 13:56:25 +0200117 update_ipv6_locator(skb, ila_params_lwtunnel(dst->lwtstate));
Tom Herbert65d7ab82015-08-17 13:42:27 -0700118
Jiri Benc61adedf2015-08-20 13:56:25 +0200119 return dst->lwtstate->orig_input(skb);
Tom Herbert65d7ab82015-08-17 13:42:27 -0700120
121drop:
122 kfree_skb(skb);
123 return -EINVAL;
124}
125
126static struct nla_policy ila_nl_policy[ILA_ATTR_MAX + 1] = {
127 [ILA_ATTR_LOCATOR] = { .type = NLA_U64, },
128};
129
130static int ila_build_state(struct net_device *dev, struct nlattr *nla,
Tom Herbert127eb7c2015-08-24 09:45:41 -0700131 unsigned int family, const void *cfg,
Tom Herbert65d7ab82015-08-17 13:42:27 -0700132 struct lwtunnel_state **ts)
133{
134 struct ila_params *p;
135 struct nlattr *tb[ILA_ATTR_MAX + 1];
136 size_t encap_len = sizeof(*p);
137 struct lwtunnel_state *newts;
Tom Herbert92b78af2015-08-24 09:45:42 -0700138 const struct fib6_config *cfg6 = cfg;
Tom Herbert65d7ab82015-08-17 13:42:27 -0700139 int ret;
140
Tom Herbert92b78af2015-08-24 09:45:42 -0700141 if (family != AF_INET6)
142 return -EINVAL;
143
Tom Herbert65d7ab82015-08-17 13:42:27 -0700144 ret = nla_parse_nested(tb, ILA_ATTR_MAX, nla,
145 ila_nl_policy);
146 if (ret < 0)
147 return ret;
148
149 if (!tb[ILA_ATTR_LOCATOR])
150 return -EINVAL;
151
152 newts = lwtunnel_state_alloc(encap_len);
153 if (!newts)
154 return -ENOMEM;
155
156 newts->len = encap_len;
157 p = ila_params_lwtunnel(newts);
158
159 p->locator = (__force __be64)nla_get_u64(tb[ILA_ATTR_LOCATOR]);
160
Tom Herbert92b78af2015-08-24 09:45:42 -0700161 if (cfg6->fc_dst_len > sizeof(__be64)) {
162 /* Precompute checksum difference for translation since we
163 * know both the old locator and the new one.
164 */
165 p->locator_match = *(__be64 *)&cfg6->fc_dst;
166 p->csum_diff = compute_csum_diff8(
167 (__be32 *)&p->locator_match, (__be32 *)&p->locator);
168 }
169
Tom Herbert65d7ab82015-08-17 13:42:27 -0700170 newts->type = LWTUNNEL_ENCAP_ILA;
171 newts->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT |
172 LWTUNNEL_STATE_INPUT_REDIRECT;
173
174 *ts = newts;
175
176 return 0;
177}
178
179static int ila_fill_encap_info(struct sk_buff *skb,
180 struct lwtunnel_state *lwtstate)
181{
182 struct ila_params *p = ila_params_lwtunnel(lwtstate);
183
184 if (nla_put_u64(skb, ILA_ATTR_LOCATOR, (__force u64)p->locator))
185 goto nla_put_failure;
186
187 return 0;
188
189nla_put_failure:
190 return -EMSGSIZE;
191}
192
193static int ila_encap_nlsize(struct lwtunnel_state *lwtstate)
194{
195 /* No encapsulation overhead */
196 return 0;
197}
198
199static int ila_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b)
200{
201 struct ila_params *a_p = ila_params_lwtunnel(a);
202 struct ila_params *b_p = ila_params_lwtunnel(b);
203
204 return (a_p->locator != b_p->locator);
205}
206
207static const struct lwtunnel_encap_ops ila_encap_ops = {
208 .build_state = ila_build_state,
209 .output = ila_output,
210 .input = ila_input,
211 .fill_encap = ila_fill_encap_info,
212 .get_encap_size = ila_encap_nlsize,
213 .cmp_encap = ila_encap_cmp,
214};
215
216static int __init ila_init(void)
217{
218 return lwtunnel_encap_add_ops(&ila_encap_ops, LWTUNNEL_ENCAP_ILA);
219}
220
221static void __exit ila_fini(void)
222{
223 lwtunnel_encap_del_ops(&ila_encap_ops, LWTUNNEL_ENCAP_ILA);
224}
225
226module_init(ila_init);
227module_exit(ila_fini);
228MODULE_AUTHOR("Tom Herbert <tom@herbertland.com>");
229MODULE_LICENSE("GPL");