Commit | Line | Data |
---|---|---|
aa3463d6 TH |
1 | #include <linux/module.h> |
2 | #include <linux/errno.h> | |
3 | #include <linux/socket.h> | |
4 | #include <linux/skbuff.h> | |
5 | #include <linux/ip.h> | |
6 | #include <linux/udp.h> | |
b8a51b38 | 7 | #include <linux/icmpv6.h> |
aa3463d6 TH |
8 | #include <linux/types.h> |
9 | #include <linux/kernel.h> | |
10 | #include <net/fou.h> | |
11 | #include <net/ip.h> | |
12 | #include <net/ip6_tunnel.h> | |
13 | #include <net/ip6_checksum.h> | |
14 | #include <net/protocol.h> | |
15 | #include <net/udp.h> | |
16 | #include <net/udp_tunnel.h> | |
17 | ||
9dc621af | 18 | #if IS_ENABLED(CONFIG_IPV6_FOU_TUNNEL) |
19 | ||
aa3463d6 TH |
20 | static void fou6_build_udp(struct sk_buff *skb, struct ip_tunnel_encap *e, |
21 | struct flowi6 *fl6, u8 *protocol, __be16 sport) | |
22 | { | |
23 | struct udphdr *uh; | |
24 | ||
25 | skb_push(skb, sizeof(struct udphdr)); | |
26 | skb_reset_transport_header(skb); | |
27 | ||
28 | uh = udp_hdr(skb); | |
29 | ||
30 | uh->dest = e->dport; | |
31 | uh->source = sport; | |
32 | uh->len = htons(skb->len); | |
33 | udp6_set_csum(!(e->flags & TUNNEL_ENCAP_FLAG_CSUM6), skb, | |
34 | &fl6->saddr, &fl6->daddr, skb->len); | |
35 | ||
36 | *protocol = IPPROTO_UDP; | |
37 | } | |
38 | ||
9dc621af | 39 | static int fou6_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e, |
40 | u8 *protocol, struct flowi6 *fl6) | |
aa3463d6 TH |
41 | { |
42 | __be16 sport; | |
43 | int err; | |
44 | int type = e->flags & TUNNEL_ENCAP_FLAG_CSUM6 ? | |
45 | SKB_GSO_UDP_TUNNEL_CSUM : SKB_GSO_UDP_TUNNEL; | |
46 | ||
47 | err = __fou_build_header(skb, e, protocol, &sport, type); | |
48 | if (err) | |
49 | return err; | |
50 | ||
51 | fou6_build_udp(skb, e, fl6, protocol, sport); | |
52 | ||
53 | return 0; | |
54 | } | |
aa3463d6 | 55 | |
9dc621af | 56 | static int gue6_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e, |
57 | u8 *protocol, struct flowi6 *fl6) | |
aa3463d6 TH |
58 | { |
59 | __be16 sport; | |
60 | int err; | |
61 | int type = e->flags & TUNNEL_ENCAP_FLAG_CSUM6 ? | |
62 | SKB_GSO_UDP_TUNNEL_CSUM : SKB_GSO_UDP_TUNNEL; | |
63 | ||
64 | err = __gue_build_header(skb, e, protocol, &sport, type); | |
65 | if (err) | |
66 | return err; | |
67 | ||
68 | fou6_build_udp(skb, e, fl6, protocol, sport); | |
69 | ||
70 | return 0; | |
71 | } | |
aa3463d6 | 72 | |
b8a51b38 SB |
73 | static int gue6_err_proto_handler(int proto, struct sk_buff *skb, |
74 | struct inet6_skb_parm *opt, | |
5de362df | 75 | u8 type, u8 code, int offset, __be32 info) |
b8a51b38 SB |
76 | { |
77 | const struct inet6_protocol *ipprot; | |
78 | ||
79 | ipprot = rcu_dereference(inet6_protos[proto]); | |
80 | if (ipprot && ipprot->err_handler) { | |
81 | if (!ipprot->err_handler(skb, opt, type, code, offset, info)) | |
82 | return 0; | |
83 | } | |
84 | ||
85 | return -ENOENT; | |
86 | } | |
87 | ||
88 | static int gue6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, | |
89 | u8 type, u8 code, int offset, __be32 info) | |
90 | { | |
91 | int transport_offset = skb_transport_offset(skb); | |
92 | struct guehdr *guehdr; | |
26fc181e | 93 | size_t len, optlen; |
b8a51b38 SB |
94 | int ret; |
95 | ||
26fc181e ED |
96 | len = sizeof(struct udphdr) + sizeof(struct guehdr); |
97 | if (!pskb_may_pull(skb, len)) | |
b8a51b38 SB |
98 | return -EINVAL; |
99 | ||
100 | guehdr = (struct guehdr *)&udp_hdr(skb)[1]; | |
101 | ||
102 | switch (guehdr->version) { | |
103 | case 0: /* Full GUE header present */ | |
104 | break; | |
105 | case 1: { | |
106 | /* Direct encasulation of IPv4 or IPv6 */ | |
107 | skb_set_transport_header(skb, -(int)sizeof(struct icmp6hdr)); | |
108 | ||
109 | switch (((struct iphdr *)guehdr)->version) { | |
110 | case 4: | |
111 | ret = gue6_err_proto_handler(IPPROTO_IPIP, skb, opt, | |
112 | type, code, offset, info); | |
113 | goto out; | |
114 | case 6: | |
115 | ret = gue6_err_proto_handler(IPPROTO_IPV6, skb, opt, | |
116 | type, code, offset, info); | |
117 | goto out; | |
118 | default: | |
119 | ret = -EOPNOTSUPP; | |
120 | goto out; | |
121 | } | |
122 | } | |
123 | default: /* Undefined version */ | |
124 | return -EOPNOTSUPP; | |
125 | } | |
126 | ||
127 | if (guehdr->control) | |
128 | return -ENOENT; | |
129 | ||
130 | optlen = guehdr->hlen << 2; | |
131 | ||
26fc181e ED |
132 | if (!pskb_may_pull(skb, len + optlen)) |
133 | return -EINVAL; | |
134 | ||
135 | guehdr = (struct guehdr *)&udp_hdr(skb)[1]; | |
b8a51b38 SB |
136 | if (validate_gue_flags(guehdr, optlen)) |
137 | return -EINVAL; | |
138 | ||
44039e00 SB |
139 | /* Handling exceptions for direct UDP encapsulation in GUE would lead to |
140 | * recursion. Besides, this kind of encapsulation can't even be | |
141 | * configured currently. Discard this. | |
142 | */ | |
143 | if (guehdr->proto_ctype == IPPROTO_UDP || | |
144 | guehdr->proto_ctype == IPPROTO_UDPLITE) | |
145 | return -EOPNOTSUPP; | |
146 | ||
b8a51b38 SB |
147 | skb_set_transport_header(skb, -(int)sizeof(struct icmp6hdr)); |
148 | ret = gue6_err_proto_handler(guehdr->proto_ctype, skb, | |
149 | opt, type, code, offset, info); | |
150 | ||
151 | out: | |
152 | skb_set_transport_header(skb, transport_offset); | |
153 | return ret; | |
154 | } | |
155 | ||
156 | ||
aa3463d6 TH |
157 | static const struct ip6_tnl_encap_ops fou_ip6tun_ops = { |
158 | .encap_hlen = fou_encap_hlen, | |
159 | .build_header = fou6_build_header, | |
b8a51b38 | 160 | .err_handler = gue6_err, |
aa3463d6 TH |
161 | }; |
162 | ||
163 | static const struct ip6_tnl_encap_ops gue_ip6tun_ops = { | |
164 | .encap_hlen = gue_encap_hlen, | |
165 | .build_header = gue6_build_header, | |
b8a51b38 | 166 | .err_handler = gue6_err, |
aa3463d6 TH |
167 | }; |
168 | ||
169 | static int ip6_tnl_encap_add_fou_ops(void) | |
170 | { | |
171 | int ret; | |
172 | ||
173 | ret = ip6_tnl_encap_add_ops(&fou_ip6tun_ops, TUNNEL_ENCAP_FOU); | |
174 | if (ret < 0) { | |
175 | pr_err("can't add fou6 ops\n"); | |
176 | return ret; | |
177 | } | |
178 | ||
179 | ret = ip6_tnl_encap_add_ops(&gue_ip6tun_ops, TUNNEL_ENCAP_GUE); | |
180 | if (ret < 0) { | |
181 | pr_err("can't add gue6 ops\n"); | |
182 | ip6_tnl_encap_del_ops(&fou_ip6tun_ops, TUNNEL_ENCAP_FOU); | |
183 | return ret; | |
184 | } | |
185 | ||
186 | return 0; | |
187 | } | |
188 | ||
189 | static void ip6_tnl_encap_del_fou_ops(void) | |
190 | { | |
191 | ip6_tnl_encap_del_ops(&fou_ip6tun_ops, TUNNEL_ENCAP_FOU); | |
192 | ip6_tnl_encap_del_ops(&gue_ip6tun_ops, TUNNEL_ENCAP_GUE); | |
193 | } | |
194 | ||
195 | #else | |
196 | ||
197 | static int ip6_tnl_encap_add_fou_ops(void) | |
198 | { | |
199 | return 0; | |
200 | } | |
201 | ||
202 | static void ip6_tnl_encap_del_fou_ops(void) | |
203 | { | |
204 | } | |
205 | ||
206 | #endif | |
207 | ||
208 | static int __init fou6_init(void) | |
209 | { | |
210 | int ret; | |
211 | ||
212 | ret = ip6_tnl_encap_add_fou_ops(); | |
213 | ||
214 | return ret; | |
215 | } | |
216 | ||
217 | static void __exit fou6_fini(void) | |
218 | { | |
219 | ip6_tnl_encap_del_fou_ops(); | |
220 | } | |
221 | ||
222 | module_init(fou6_init); | |
223 | module_exit(fou6_fini); | |
224 | MODULE_AUTHOR("Tom Herbert <therbert@google.com>"); | |
225 | MODULE_LICENSE("GPL"); |