1d619b284a0202a0668643d5b526c10cf7cf7b93
[strongswan.git] / src / libcharon / plugins / ha / ha_kernel.c
1 /*
2 * Copyright (C) 2009-2011 Martin Willi
3 * Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "ha_kernel.h"
17
18 typedef u_int32_t u32;
19 typedef u_int8_t u8;
20
21 #include <sys/utsname.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <unistd.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28
29 #define CLUSTERIP_DIR "/proc/net/ipt_CLUSTERIP"
30
31 /**
32 * Versions of jhash used in the Linux kernel
33 */
34 typedef enum {
35 /* old variant, http://burtleburtle.net/bob/c/lookup2.c */
36 JHASH_LOOKUP2,
37 /* new variant, http://burtleburtle.net/bob/c/lookup3.c, since 2.6.37 */
38 JHASH_LOOKUP3,
39 } jhash_version_t;
40
41 typedef struct private_ha_kernel_t private_ha_kernel_t;
42
43 /**
44 * Private data of an ha_kernel_t object.
45 */
46 struct private_ha_kernel_t {
47
48 /**
49 * Public ha_kernel_t interface.
50 */
51 ha_kernel_t public;
52
53 /**
54 * Total number of ClusterIP segments
55 */
56 u_int count;
57
58 /**
59 * jhash version the kernel uses
60 */
61 jhash_version_t version;
62 };
63
64 /**
65 * Get the jhash version based on the uname().release
66 */
67 static jhash_version_t get_jhash_version()
68 {
69 struct utsname utsname;
70 int a, b, c;
71
72 if (uname(&utsname) == 0)
73 {
74 switch (sscanf(utsname.release, "%d.%d.%d", &a, &b, &c))
75 {
76 case 3:
77 if (a == 2 && b == 6)
78 {
79 if (c < 37)
80 {
81 DBG1(DBG_CFG, "detected Linux %d.%d.%d, using old "
82 "jhash", a, b, c);
83 return JHASH_LOOKUP2;
84 }
85 DBG1(DBG_CFG, "detected Linux %d.%d.%d, using new "
86 "jhash", a, b, c);
87 return JHASH_LOOKUP3;
88 }
89 /* FALL */
90 case 2:
91 DBG1(DBG_CFG, "detected Linux %d.%d, using new jhash", a, b);
92 return JHASH_LOOKUP3;
93 default:
94 break;
95 }
96 }
97 DBG1(DBG_CFG, "detecting Linux version failed, using new jhash");
98 return JHASH_LOOKUP3;
99 }
100
101 /**
102 * Rotate 32 bit word x by k bits
103 */
104 #define jhash_rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
105
106 /**
107 * jhash algorithm of two words, as used in kernel (using 0 as initval)
108 */
109 static u_int32_t jhash(jhash_version_t version, u_int32_t a, u_int32_t b)
110 {
111 u_int32_t c = 0;
112
113 switch (version)
114 {
115 case JHASH_LOOKUP2:
116 a += 0x9e3779b9;
117 b += 0x9e3779b9;
118
119 a -= b; a -= c; a ^= (c >> 13);
120 b -= c; b -= a; b ^= (a << 8);
121 c -= a; c -= b; c ^= (b >> 13);
122 a -= b; a -= c; a ^= (c >> 12);
123 b -= c; b -= a; b ^= (a << 16);
124 c -= a; c -= b; c ^= (b >> 5);
125 a -= b; a -= c; a ^= (c >> 3);
126 b -= c; b -= a; b ^= (a << 10);
127 c -= a; c -= b; c ^= (b >> 15);
128 break;
129 case JHASH_LOOKUP3:
130 a += 0xdeadbeef;
131 b += 0xdeadbeef;
132
133 c ^= b; c -= jhash_rot(b, 14);
134 a ^= c; a -= jhash_rot(c, 11);
135 b ^= a; b -= jhash_rot(a, 25);
136 c ^= b; c -= jhash_rot(b, 16);
137 a ^= c; a -= jhash_rot(c, 4);
138 b ^= a; b -= jhash_rot(a, 14);
139 c ^= b; c -= jhash_rot(b, 24);
140 break;
141 }
142 return c;
143 }
144
145 /**
146 * Segmentate a calculated hash
147 */
148 static u_int hash2segment(private_ha_kernel_t *this, u_int64_t hash)
149 {
150 return ((hash * this->count) >> 32) + 1;
151 }
152
153 /**
154 * Get a host as an integer for hashing
155 */
156 static u_int32_t host2int(host_t *host)
157 {
158 if (host->get_family(host) == AF_INET)
159 {
160 return *(u_int32_t*)host->get_address(host).ptr;
161 }
162 return 0;
163 }
164
165 METHOD(ha_kernel_t, get_segment, u_int,
166 private_ha_kernel_t *this, host_t *host)
167 {
168 unsigned long hash;
169 u_int32_t addr;
170
171 addr = host2int(host);
172 hash = jhash(this->version, ntohl(addr), 0);
173
174 return hash2segment(this, hash);
175 }
176
177 METHOD(ha_kernel_t, get_segment_spi, u_int,
178 private_ha_kernel_t *this, host_t *host, u_int32_t spi)
179 {
180 unsigned long hash;
181 u_int32_t addr;
182
183 addr = host2int(host);
184 hash = jhash(this->version, ntohl(addr), ntohl(spi));
185
186 return hash2segment(this, hash);
187 }
188
189 METHOD(ha_kernel_t, get_segment_int, u_int,
190 private_ha_kernel_t *this, int n)
191 {
192 unsigned long hash;
193
194 hash = jhash(this->version, ntohl(n), 0);
195
196 return hash2segment(this, hash);
197 }
198
199 /**
200 * Activate/Deactivate a segment for a given clusterip file
201 */
202 static void enable_disable(private_ha_kernel_t *this, u_int segment,
203 char *file, bool enable)
204 {
205 char cmd[8];
206 int fd;
207
208 snprintf(cmd, sizeof(cmd), "%c%d\n", enable ? '+' : '-', segment);
209
210 fd = open(file, O_WRONLY);
211 if (fd == -1)
212 {
213 DBG1(DBG_CFG, "opening CLUSTERIP file '%s' failed: %s",
214 file, strerror(errno));
215 return;
216 }
217 if (write(fd, cmd, strlen(cmd)) == -1)
218 {
219 DBG1(DBG_CFG, "writing to CLUSTERIP file '%s' failed: %s",
220 file, strerror(errno));
221 }
222 close(fd);
223 }
224
225 /**
226 * Get the currenlty active segments in the kernel for a clusterip file
227 */
228 static segment_mask_t get_active(private_ha_kernel_t *this, char *file)
229 {
230 char buf[256];
231 segment_mask_t mask = 0;
232 ssize_t len;
233 int fd;
234
235 fd = open(file, O_RDONLY);
236 if (fd == -1)
237 {
238 DBG1(DBG_CFG, "opening CLUSTERIP file '%s' failed: %s",
239 file, strerror(errno));
240 return 0;
241 }
242 len = read(fd, buf, sizeof(buf)-1);
243 if (len == -1)
244 {
245 DBG1(DBG_CFG, "reading from CLUSTERIP file '%s' failed: %s",
246 file, strerror(errno));
247 }
248 else
249 {
250 enumerator_t *enumerator;
251 u_int segment;
252 char *token;
253
254 buf[len] = '\0';
255 enumerator = enumerator_create_token(buf, ",", " ");
256 while (enumerator->enumerate(enumerator, &token))
257 {
258 segment = atoi(token);
259 if (segment)
260 {
261 mask |= SEGMENTS_BIT(segment);
262 }
263 }
264 enumerator->destroy(enumerator);
265 }
266 return mask;
267 }
268
269 METHOD(ha_kernel_t, activate, void,
270 private_ha_kernel_t *this, u_int segment)
271 {
272 enumerator_t *enumerator;
273 char *file;
274
275 enumerator = enumerator_create_directory(CLUSTERIP_DIR);
276 while (enumerator->enumerate(enumerator, NULL, &file, NULL))
277 {
278 enable_disable(this, segment, file, TRUE);
279 }
280 enumerator->destroy(enumerator);
281 }
282
283 METHOD(ha_kernel_t, deactivate, void,
284 private_ha_kernel_t *this, u_int segment)
285 {
286 enumerator_t *enumerator;
287 char *file;
288
289 enumerator = enumerator_create_directory(CLUSTERIP_DIR);
290 while (enumerator->enumerate(enumerator, NULL, &file, NULL))
291 {
292 enable_disable(this, segment, file, FALSE);
293 }
294 enumerator->destroy(enumerator);
295 }
296
297 /**
298 * Disable all not-yet disabled segments on all clusterip addresses
299 */
300 static void disable_all(private_ha_kernel_t *this)
301 {
302 enumerator_t *enumerator;
303 segment_mask_t active;
304 char *file;
305 int i;
306
307 enumerator = enumerator_create_directory(CLUSTERIP_DIR);
308 while (enumerator->enumerate(enumerator, NULL, &file, NULL))
309 {
310 if (chown(file, charon->uid, charon->gid) != 0)
311 {
312 DBG1(DBG_CFG, "changing ClusterIP permissions failed: %s",
313 strerror(errno));
314 }
315 active = get_active(this, file);
316 for (i = 1; i <= this->count; i++)
317 {
318 if (active & SEGMENTS_BIT(i))
319 {
320 enable_disable(this, i, file, FALSE);
321 }
322 }
323 }
324 enumerator->destroy(enumerator);
325 }
326
327 METHOD(ha_kernel_t, destroy, void,
328 private_ha_kernel_t *this)
329 {
330 free(this);
331 }
332
333 /**
334 * See header
335 */
336 ha_kernel_t *ha_kernel_create(u_int count)
337 {
338 private_ha_kernel_t *this;
339
340 INIT(this,
341 .public = {
342 .get_segment = _get_segment,
343 .get_segment_spi = _get_segment_spi,
344 .get_segment_int = _get_segment_int,
345 .activate = _activate,
346 .deactivate = _deactivate,
347 .destroy = _destroy,
348 },
349 .version = get_jhash_version(),
350 .count = count,
351 );
352
353 disable_all(this);
354
355 return &this->public;
356 }
357