net: Identifier Locator Addressing module
[deliverable/linux.git] / net / ipv6 / ila.c
1 #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
15 struct ila_params {
16 __be64 locator;
17 };
18
19 static inline struct ila_params *ila_params_lwtunnel(
20 struct lwtunnel_state *lwstate)
21 {
22 return (struct ila_params *)lwstate->data;
23 }
24
25 static inline __wsum compute_csum_diff8(const __be32 *from, const __be32 *to)
26 {
27 __be32 diff[] = {
28 ~from[0], ~from[1], to[0], to[1],
29 };
30
31 return csum_partial(diff, sizeof(diff), 0);
32 }
33
34 static inline __wsum get_csum_diff(struct ipv6hdr *ip6h, struct ila_params *p)
35 {
36 return compute_csum_diff8((__be32 *)&ip6h->daddr,
37 (__be32 *)&p->locator);
38 }
39
40 static void update_ipv6_locator(struct sk_buff *skb, struct ila_params *p)
41 {
42 __wsum diff;
43 struct ipv6hdr *ip6h = ipv6_hdr(skb);
44 size_t nhoff = sizeof(struct ipv6hdr);
45
46 /* First update checksum */
47 switch (ip6h->nexthdr) {
48 case NEXTHDR_TCP:
49 if (likely(pskb_may_pull(skb, nhoff + sizeof(struct tcphdr)))) {
50 struct tcphdr *th = (struct tcphdr *)
51 (skb_network_header(skb) + nhoff);
52
53 diff = get_csum_diff(ip6h, p);
54 inet_proto_csum_replace_by_diff(&th->check, skb,
55 diff, true);
56 }
57 break;
58 case NEXTHDR_UDP:
59 if (likely(pskb_may_pull(skb, nhoff + sizeof(struct udphdr)))) {
60 struct udphdr *uh = (struct udphdr *)
61 (skb_network_header(skb) + nhoff);
62
63 if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) {
64 diff = get_csum_diff(ip6h, p);
65 inet_proto_csum_replace_by_diff(&uh->check, skb,
66 diff, true);
67 if (!uh->check)
68 uh->check = CSUM_MANGLED_0;
69 }
70 }
71 break;
72 case NEXTHDR_ICMP:
73 if (likely(pskb_may_pull(skb,
74 nhoff + sizeof(struct icmp6hdr)))) {
75 struct icmp6hdr *ih = (struct icmp6hdr *)
76 (skb_network_header(skb) + nhoff);
77
78 diff = get_csum_diff(ip6h, p);
79 inet_proto_csum_replace_by_diff(&ih->icmp6_cksum, skb,
80 diff, true);
81 }
82 break;
83 }
84
85 /* Now change destination address */
86 *(__be64 *)&ip6h->daddr = p->locator;
87 }
88
89 static int ila_output(struct sock *sk, struct sk_buff *skb)
90 {
91 struct dst_entry *dst = skb_dst(skb);
92 struct rt6_info *rt6 = NULL;
93
94 if (skb->protocol != htons(ETH_P_IPV6))
95 goto drop;
96
97 rt6 = (struct rt6_info *)dst;
98
99 update_ipv6_locator(skb, ila_params_lwtunnel(rt6->rt6i_lwtstate));
100
101 return rt6->rt6i_lwtstate->orig_output(sk, skb);
102
103 drop:
104 kfree_skb(skb);
105 return -EINVAL;
106 }
107
108 static int ila_input(struct sk_buff *skb)
109 {
110 struct dst_entry *dst = skb_dst(skb);
111 struct rt6_info *rt6 = NULL;
112
113 if (skb->protocol != htons(ETH_P_IPV6))
114 goto drop;
115
116 rt6 = (struct rt6_info *)dst;
117
118 update_ipv6_locator(skb, ila_params_lwtunnel(rt6->rt6i_lwtstate));
119
120 return rt6->rt6i_lwtstate->orig_input(skb);
121
122 drop:
123 kfree_skb(skb);
124 return -EINVAL;
125 }
126
127 static struct nla_policy ila_nl_policy[ILA_ATTR_MAX + 1] = {
128 [ILA_ATTR_LOCATOR] = { .type = NLA_U64, },
129 };
130
131 static int ila_build_state(struct net_device *dev, struct nlattr *nla,
132 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;
138 int ret;
139
140 ret = nla_parse_nested(tb, ILA_ATTR_MAX, nla,
141 ila_nl_policy);
142 if (ret < 0)
143 return ret;
144
145 if (!tb[ILA_ATTR_LOCATOR])
146 return -EINVAL;
147
148 newts = lwtunnel_state_alloc(encap_len);
149 if (!newts)
150 return -ENOMEM;
151
152 newts->len = encap_len;
153 p = ila_params_lwtunnel(newts);
154
155 p->locator = (__force __be64)nla_get_u64(tb[ILA_ATTR_LOCATOR]);
156
157 newts->type = LWTUNNEL_ENCAP_ILA;
158 newts->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT |
159 LWTUNNEL_STATE_INPUT_REDIRECT;
160
161 *ts = newts;
162
163 return 0;
164 }
165
166 static int ila_fill_encap_info(struct sk_buff *skb,
167 struct lwtunnel_state *lwtstate)
168 {
169 struct ila_params *p = ila_params_lwtunnel(lwtstate);
170
171 if (nla_put_u64(skb, ILA_ATTR_LOCATOR, (__force u64)p->locator))
172 goto nla_put_failure;
173
174 return 0;
175
176 nla_put_failure:
177 return -EMSGSIZE;
178 }
179
180 static int ila_encap_nlsize(struct lwtunnel_state *lwtstate)
181 {
182 /* No encapsulation overhead */
183 return 0;
184 }
185
186 static int ila_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b)
187 {
188 struct ila_params *a_p = ila_params_lwtunnel(a);
189 struct ila_params *b_p = ila_params_lwtunnel(b);
190
191 return (a_p->locator != b_p->locator);
192 }
193
194 static const struct lwtunnel_encap_ops ila_encap_ops = {
195 .build_state = ila_build_state,
196 .output = ila_output,
197 .input = ila_input,
198 .fill_encap = ila_fill_encap_info,
199 .get_encap_size = ila_encap_nlsize,
200 .cmp_encap = ila_encap_cmp,
201 };
202
203 static int __init ila_init(void)
204 {
205 return lwtunnel_encap_add_ops(&ila_encap_ops, LWTUNNEL_ENCAP_ILA);
206 }
207
208 static void __exit ila_fini(void)
209 {
210 lwtunnel_encap_del_ops(&ila_encap_ops, LWTUNNEL_ENCAP_ILA);
211 }
212
213 module_init(ila_init);
214 module_exit(ila_fini);
215 MODULE_AUTHOR("Tom Herbert <tom@herbertland.com>");
216 MODULE_LICENSE("GPL");
This page took 0.035398 seconds and 6 git commands to generate.