Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * ip_vs_proto.c: transport protocol load balancing support for IPVS | |
3 | * | |
1da177e4 LT |
4 | * Authors: Wensong Zhang <wensong@linuxvirtualserver.org> |
5 | * Julian Anastasov <ja@ssi.bg> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License | |
9 | * as published by the Free Software Foundation; either version | |
10 | * 2 of the License, or (at your option) any later version. | |
11 | * | |
12 | * Changes: | |
13 | * | |
14 | */ | |
15 | ||
9aada7ac HE |
16 | #define KMSG_COMPONENT "IPVS" |
17 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | |
18 | ||
1da177e4 LT |
19 | #include <linux/module.h> |
20 | #include <linux/kernel.h> | |
21 | #include <linux/skbuff.h> | |
5a0e3ad6 | 22 | #include <linux/gfp.h> |
1da177e4 LT |
23 | #include <linux/in.h> |
24 | #include <linux/ip.h> | |
25 | #include <net/protocol.h> | |
26 | #include <net/tcp.h> | |
27 | #include <net/udp.h> | |
28 | #include <asm/system.h> | |
29 | #include <linux/stat.h> | |
30 | #include <linux/proc_fs.h> | |
31 | ||
32 | #include <net/ip_vs.h> | |
33 | ||
34 | ||
35 | /* | |
36 | * IPVS protocols can only be registered/unregistered when the ipvs | |
37 | * module is loaded/unloaded, so no lock is needed in accessing the | |
38 | * ipvs protocol table. | |
39 | */ | |
40 | ||
41 | #define IP_VS_PROTO_TAB_SIZE 32 /* must be power of 2 */ | |
42 | #define IP_VS_PROTO_HASH(proto) ((proto) & (IP_VS_PROTO_TAB_SIZE-1)) | |
43 | ||
44 | static struct ip_vs_protocol *ip_vs_proto_table[IP_VS_PROTO_TAB_SIZE]; | |
45 | ||
46 | ||
47 | /* | |
48 | * register an ipvs protocol | |
49 | */ | |
048cf48b | 50 | static int __used __init register_ip_vs_protocol(struct ip_vs_protocol *pp) |
1da177e4 LT |
51 | { |
52 | unsigned hash = IP_VS_PROTO_HASH(pp->protocol); | |
53 | ||
54 | pp->next = ip_vs_proto_table[hash]; | |
55 | ip_vs_proto_table[hash] = pp; | |
56 | ||
57 | if (pp->init != NULL) | |
58 | pp->init(pp); | |
59 | ||
60 | return 0; | |
61 | } | |
62 | ||
252c6410 HS |
63 | /* |
64 | * register an ipvs protocols netns related data | |
65 | */ | |
66 | static int | |
67 | register_ip_vs_proto_netns(struct net *net, struct ip_vs_protocol *pp) | |
68 | { | |
69 | struct netns_ipvs *ipvs = net_ipvs(net); | |
70 | unsigned hash = IP_VS_PROTO_HASH(pp->protocol); | |
71 | struct ip_vs_proto_data *pd = | |
72 | kzalloc(sizeof(struct ip_vs_proto_data), GFP_ATOMIC); | |
73 | ||
74 | if (!pd) { | |
75 | pr_err("%s(): no memory.\n", __func__); | |
76 | return -ENOMEM; | |
77 | } | |
78 | pd->pp = pp; /* For speed issues */ | |
79 | pd->next = ipvs->proto_data_table[hash]; | |
80 | ipvs->proto_data_table[hash] = pd; | |
81 | atomic_set(&pd->appcnt, 0); /* Init app counter */ | |
82 | ||
83 | if (pp->init_netns != NULL) | |
84 | pp->init_netns(net, pd); | |
85 | ||
86 | return 0; | |
87 | } | |
1da177e4 LT |
88 | |
89 | /* | |
90 | * unregister an ipvs protocol | |
91 | */ | |
92 | static int unregister_ip_vs_protocol(struct ip_vs_protocol *pp) | |
93 | { | |
94 | struct ip_vs_protocol **pp_p; | |
95 | unsigned hash = IP_VS_PROTO_HASH(pp->protocol); | |
96 | ||
97 | pp_p = &ip_vs_proto_table[hash]; | |
98 | for (; *pp_p; pp_p = &(*pp_p)->next) { | |
99 | if (*pp_p == pp) { | |
100 | *pp_p = pp->next; | |
101 | if (pp->exit != NULL) | |
102 | pp->exit(pp); | |
103 | return 0; | |
104 | } | |
105 | } | |
106 | ||
107 | return -ESRCH; | |
108 | } | |
109 | ||
252c6410 HS |
110 | /* |
111 | * unregister an ipvs protocols netns data | |
112 | */ | |
113 | static int | |
114 | unregister_ip_vs_proto_netns(struct net *net, struct ip_vs_proto_data *pd) | |
115 | { | |
116 | struct netns_ipvs *ipvs = net_ipvs(net); | |
117 | struct ip_vs_proto_data **pd_p; | |
118 | unsigned hash = IP_VS_PROTO_HASH(pd->pp->protocol); | |
119 | ||
120 | pd_p = &ipvs->proto_data_table[hash]; | |
121 | for (; *pd_p; pd_p = &(*pd_p)->next) { | |
122 | if (*pd_p == pd) { | |
123 | *pd_p = pd->next; | |
124 | if (pd->pp->exit_netns != NULL) | |
125 | pd->pp->exit_netns(net, pd); | |
126 | kfree(pd); | |
127 | return 0; | |
128 | } | |
129 | } | |
130 | ||
131 | return -ESRCH; | |
132 | } | |
1da177e4 LT |
133 | |
134 | /* | |
135 | * get ip_vs_protocol object by its proto. | |
136 | */ | |
137 | struct ip_vs_protocol * ip_vs_proto_get(unsigned short proto) | |
138 | { | |
139 | struct ip_vs_protocol *pp; | |
140 | unsigned hash = IP_VS_PROTO_HASH(proto); | |
141 | ||
142 | for (pp = ip_vs_proto_table[hash]; pp; pp = pp->next) { | |
143 | if (pp->protocol == proto) | |
144 | return pp; | |
145 | } | |
146 | ||
147 | return NULL; | |
148 | } | |
9c3e1c39 | 149 | EXPORT_SYMBOL(ip_vs_proto_get); |
1da177e4 | 150 | |
252c6410 HS |
151 | /* |
152 | * get ip_vs_protocol object data by netns and proto | |
153 | */ | |
154 | struct ip_vs_proto_data * | |
155 | ip_vs_proto_data_get(struct net *net, unsigned short proto) | |
156 | { | |
157 | struct netns_ipvs *ipvs = net_ipvs(net); | |
158 | struct ip_vs_proto_data *pd; | |
159 | unsigned hash = IP_VS_PROTO_HASH(proto); | |
160 | ||
161 | for (pd = ipvs->proto_data_table[hash]; pd; pd = pd->next) { | |
162 | if (pd->pp->protocol == proto) | |
163 | return pd; | |
164 | } | |
165 | ||
166 | return NULL; | |
167 | } | |
168 | EXPORT_SYMBOL(ip_vs_proto_data_get); | |
1da177e4 LT |
169 | |
170 | /* | |
171 | * Propagate event for state change to all protocols | |
172 | */ | |
173 | void ip_vs_protocol_timeout_change(int flags) | |
174 | { | |
175 | struct ip_vs_protocol *pp; | |
176 | int i; | |
177 | ||
178 | for (i = 0; i < IP_VS_PROTO_TAB_SIZE; i++) { | |
179 | for (pp = ip_vs_proto_table[i]; pp; pp = pp->next) { | |
180 | if (pp->timeout_change) | |
181 | pp->timeout_change(pp, flags); | |
182 | } | |
183 | } | |
184 | } | |
185 | ||
186 | ||
187 | int * | |
188 | ip_vs_create_timeout_table(int *table, int size) | |
189 | { | |
8b2ed4bb | 190 | return kmemdup(table, size, GFP_ATOMIC); |
1da177e4 LT |
191 | } |
192 | ||
193 | ||
194 | /* | |
195 | * Set timeout value for state specified by name | |
196 | */ | |
197 | int | |
36cbd3dc JE |
198 | ip_vs_set_state_timeout(int *table, int num, const char *const *names, |
199 | const char *name, int to) | |
1da177e4 LT |
200 | { |
201 | int i; | |
202 | ||
203 | if (!table || !name || !to) | |
204 | return -EINVAL; | |
205 | ||
206 | for (i = 0; i < num; i++) { | |
207 | if (strcmp(names[i], name)) | |
208 | continue; | |
209 | table[i] = to * HZ; | |
210 | return 0; | |
211 | } | |
212 | return -ENOENT; | |
213 | } | |
214 | ||
215 | ||
216 | const char * ip_vs_state_name(__u16 proto, int state) | |
217 | { | |
218 | struct ip_vs_protocol *pp = ip_vs_proto_get(proto); | |
219 | ||
220 | if (pp == NULL || pp->state_name == NULL) | |
2ad17def | 221 | return (IPPROTO_IP == proto) ? "NONE" : "ERR!"; |
1da177e4 LT |
222 | return pp->state_name(state); |
223 | } | |
224 | ||
225 | ||
77eb8516 | 226 | static void |
3b047d9d JV |
227 | ip_vs_tcpudp_debug_packet_v4(struct ip_vs_protocol *pp, |
228 | const struct sk_buff *skb, | |
229 | int offset, | |
230 | const char *msg) | |
1da177e4 LT |
231 | { |
232 | char buf[128]; | |
233 | struct iphdr _iph, *ih; | |
234 | ||
235 | ih = skb_header_pointer(skb, offset, sizeof(_iph), &_iph); | |
236 | if (ih == NULL) | |
3d91c1a8 | 237 | sprintf(buf, "TRUNCATED"); |
5661df7b | 238 | else if (ih->frag_off & htons(IP_OFFSET)) |
3d91c1a8 | 239 | sprintf(buf, "%pI4->%pI4 frag", &ih->saddr, &ih->daddr); |
1da177e4 | 240 | else { |
0d79641a JA |
241 | __be16 _ports[2], *pptr; |
242 | ||
1da177e4 LT |
243 | pptr = skb_header_pointer(skb, offset + ih->ihl*4, |
244 | sizeof(_ports), _ports); | |
245 | if (pptr == NULL) | |
3d91c1a8 PM |
246 | sprintf(buf, "TRUNCATED %pI4->%pI4", |
247 | &ih->saddr, &ih->daddr); | |
1da177e4 | 248 | else |
3d91c1a8 | 249 | sprintf(buf, "%pI4:%u->%pI4:%u", |
14d5e834 HH |
250 | &ih->saddr, ntohs(pptr[0]), |
251 | &ih->daddr, ntohs(pptr[1])); | |
1da177e4 LT |
252 | } |
253 | ||
3d91c1a8 | 254 | pr_debug("%s: %s %s\n", msg, pp->name, buf); |
1da177e4 LT |
255 | } |
256 | ||
3b047d9d | 257 | #ifdef CONFIG_IP_VS_IPV6 |
77eb8516 | 258 | static void |
3b047d9d JV |
259 | ip_vs_tcpudp_debug_packet_v6(struct ip_vs_protocol *pp, |
260 | const struct sk_buff *skb, | |
261 | int offset, | |
262 | const char *msg) | |
263 | { | |
264 | char buf[192]; | |
265 | struct ipv6hdr _iph, *ih; | |
266 | ||
267 | ih = skb_header_pointer(skb, offset, sizeof(_iph), &_iph); | |
268 | if (ih == NULL) | |
3d91c1a8 | 269 | sprintf(buf, "TRUNCATED"); |
3b047d9d | 270 | else if (ih->nexthdr == IPPROTO_FRAGMENT) |
3d91c1a8 | 271 | sprintf(buf, "%pI6->%pI6 frag", &ih->saddr, &ih->daddr); |
3b047d9d JV |
272 | else { |
273 | __be16 _ports[2], *pptr; | |
274 | ||
275 | pptr = skb_header_pointer(skb, offset + sizeof(struct ipv6hdr), | |
276 | sizeof(_ports), _ports); | |
277 | if (pptr == NULL) | |
3d91c1a8 PM |
278 | sprintf(buf, "TRUNCATED %pI6->%pI6", |
279 | &ih->saddr, &ih->daddr); | |
3b047d9d | 280 | else |
3d91c1a8 | 281 | sprintf(buf, "%pI6:%u->%pI6:%u", |
38ff4fa4 HH |
282 | &ih->saddr, ntohs(pptr[0]), |
283 | &ih->daddr, ntohs(pptr[1])); | |
3b047d9d JV |
284 | } |
285 | ||
3d91c1a8 | 286 | pr_debug("%s: %s %s\n", msg, pp->name, buf); |
3b047d9d JV |
287 | } |
288 | #endif | |
289 | ||
290 | ||
291 | void | |
0d79641a | 292 | ip_vs_tcpudp_debug_packet(int af, struct ip_vs_protocol *pp, |
3b047d9d JV |
293 | const struct sk_buff *skb, |
294 | int offset, | |
295 | const char *msg) | |
296 | { | |
297 | #ifdef CONFIG_IP_VS_IPV6 | |
0d79641a | 298 | if (af == AF_INET6) |
3b047d9d JV |
299 | ip_vs_tcpudp_debug_packet_v6(pp, skb, offset, msg); |
300 | else | |
301 | #endif | |
302 | ip_vs_tcpudp_debug_packet_v4(pp, skb, offset, msg); | |
303 | } | |
304 | ||
61b1ab45 HS |
305 | /* |
306 | * per network name-space init | |
307 | */ | |
308 | static int __net_init __ip_vs_protocol_init(struct net *net) | |
309 | { | |
4a85b96c HS |
310 | #ifdef CONFIG_IP_VS_PROTO_TCP |
311 | register_ip_vs_proto_netns(net, &ip_vs_protocol_tcp); | |
312 | #endif | |
61b1ab45 HS |
313 | return 0; |
314 | } | |
315 | ||
316 | static void __net_exit __ip_vs_protocol_cleanup(struct net *net) | |
317 | { | |
4a85b96c HS |
318 | struct netns_ipvs *ipvs = net_ipvs(net); |
319 | struct ip_vs_proto_data *pd; | |
320 | int i; | |
321 | ||
322 | /* unregister all the ipvs proto data for this netns */ | |
323 | for (i = 0; i < IP_VS_PROTO_TAB_SIZE; i++) { | |
324 | while ((pd = ipvs->proto_data_table[i]) != NULL) | |
325 | unregister_ip_vs_proto_netns(net, pd); | |
326 | } | |
61b1ab45 HS |
327 | } |
328 | ||
329 | static struct pernet_operations ipvs_proto_ops = { | |
330 | .init = __ip_vs_protocol_init, | |
331 | .exit = __ip_vs_protocol_cleanup, | |
332 | }; | |
1da177e4 | 333 | |
048cf48b | 334 | int __init ip_vs_protocol_init(void) |
1da177e4 LT |
335 | { |
336 | char protocols[64]; | |
337 | #define REGISTER_PROTOCOL(p) \ | |
338 | do { \ | |
339 | register_ip_vs_protocol(p); \ | |
340 | strcat(protocols, ", "); \ | |
341 | strcat(protocols, (p)->name); \ | |
342 | } while (0) | |
343 | ||
344 | protocols[0] = '\0'; | |
345 | protocols[2] = '\0'; | |
346 | #ifdef CONFIG_IP_VS_PROTO_TCP | |
347 | REGISTER_PROTOCOL(&ip_vs_protocol_tcp); | |
348 | #endif | |
349 | #ifdef CONFIG_IP_VS_PROTO_UDP | |
350 | REGISTER_PROTOCOL(&ip_vs_protocol_udp); | |
351 | #endif | |
2906f66a VMR |
352 | #ifdef CONFIG_IP_VS_PROTO_SCTP |
353 | REGISTER_PROTOCOL(&ip_vs_protocol_sctp); | |
354 | #endif | |
1da177e4 LT |
355 | #ifdef CONFIG_IP_VS_PROTO_AH |
356 | REGISTER_PROTOCOL(&ip_vs_protocol_ah); | |
357 | #endif | |
358 | #ifdef CONFIG_IP_VS_PROTO_ESP | |
359 | REGISTER_PROTOCOL(&ip_vs_protocol_esp); | |
360 | #endif | |
1e3e238e | 361 | pr_info("Registered protocols (%s)\n", &protocols[2]); |
61b1ab45 | 362 | return register_pernet_subsys(&ipvs_proto_ops); |
1da177e4 LT |
363 | |
364 | return 0; | |
365 | } | |
366 | ||
367 | ||
368 | void ip_vs_protocol_cleanup(void) | |
369 | { | |
370 | struct ip_vs_protocol *pp; | |
371 | int i; | |
372 | ||
61b1ab45 | 373 | unregister_pernet_subsys(&ipvs_proto_ops); |
1da177e4 LT |
374 | /* unregister all the ipvs protocols */ |
375 | for (i = 0; i < IP_VS_PROTO_TAB_SIZE; i++) { | |
376 | while ((pp = ip_vs_proto_table[i]) != NULL) | |
377 | unregister_ip_vs_protocol(pp); | |
378 | } | |
379 | } |