]>
Commit | Line | Data |
---|---|---|
20ab58c7 SH |
1 | /* |
2 | * | |
3 | * Copyright © 2013 Serge Hallyn <serge.hallyn@ubuntu.com>. | |
4 | * Copyright © 2013 Canonical Ltd. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2, as | |
8 | * published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License along | |
16 | * with this program; if not, write to the Free Software Foundation, Inc., | |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
18 | */ | |
19 | ||
811ef482 | 20 | #define _GNU_SOURCE |
1f109d47 CB |
21 | #include <alloca.h> |
22 | #include <ctype.h> | |
23 | #include <errno.h> | |
24 | #include <fcntl.h> | |
25 | #include <grp.h> | |
26 | #include <pwd.h> | |
27 | #include <sched.h> | |
28 | #include <stdbool.h> | |
20ab58c7 SH |
29 | #include <stdio.h> |
30 | #include <stdlib.h> | |
20ab58c7 | 31 | #include <string.h> |
1f109d47 | 32 | #include <unistd.h> |
20ab58c7 | 33 | #include <arpa/inet.h> |
4119204e | 34 | #include <linux/netlink.h> |
20ab58c7 SH |
35 | #include <linux/rtnetlink.h> |
36 | #include <linux/sockios.h> | |
1f109d47 CB |
37 | #include <net/if.h> |
38 | #include <net/if_arp.h> | |
39 | #include <netinet/in.h> | |
40 | #include <sys/file.h> | |
41 | #include <sys/ioctl.h> | |
42 | #include <sys/mman.h> | |
4119204e | 43 | #include <sys/param.h> |
1f109d47 CB |
44 | #include <sys/socket.h> |
45 | #include <sys/stat.h> | |
46 | #include <sys/types.h> | |
f2363e38 | 47 | |
37fc7b9e | 48 | #include "config.h" |
0059379f | 49 | #include "namespace.h" |
8befa924 | 50 | #include "network.h" |
1f109d47 | 51 | #include "utils.h" |
20ab58c7 | 52 | |
16af2380 CB |
53 | #define usernic_debug_stream(stream, format, ...) \ |
54 | do { \ | |
55 | fprintf(stream, "%s: %d: %s: " format, __FILE__, __LINE__, \ | |
56 | __func__, __VA_ARGS__); \ | |
57 | } while (false) | |
58 | ||
59 | #define usernic_error(format, ...) usernic_debug_stream(stderr, format, __VA_ARGS__) | |
60 | ||
74a3920a | 61 | static void usage(char *me, bool fail) |
20ab58c7 | 62 | { |
900e5f94 CB |
63 | fprintf(stderr, "Usage: %s create {lxcpath} {name} {pid} {type} " |
64 | "{bridge} {nicname}\n", me); | |
cb5659e1 CB |
65 | fprintf(stderr, "Usage: %s delete {lxcpath} {name} " |
66 | "{/proc/<pid>/ns/net} {type} {bridge} {nicname}\n", me); | |
900e5f94 CB |
67 | fprintf(stderr, "{nicname} is the name to use inside the container\n"); |
68 | ||
69 | if (fail) | |
70 | exit(EXIT_FAILURE); | |
20ab58c7 | 71 | |
900e5f94 CB |
72 | exit(EXIT_SUCCESS); |
73 | } | |
c43cbc04 | 74 | |
552a7d05 | 75 | static int open_and_lock(char *path) |
20ab58c7 | 76 | { |
af256970 | 77 | int fd, ret; |
20ab58c7 SH |
78 | struct flock lk; |
79 | ||
af256970 | 80 | fd = open(path, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR); |
20ab58c7 | 81 | if (fd < 0) { |
af256970 CB |
82 | usernic_error("Failed to open \"%s\": %s\n", path, |
83 | strerror(errno)); | |
2b333aee | 84 | return -1; |
20ab58c7 SH |
85 | } |
86 | ||
87 | lk.l_type = F_WRLCK; | |
88 | lk.l_whence = SEEK_SET; | |
89 | lk.l_start = 0; | |
90 | lk.l_len = 0; | |
af256970 CB |
91 | |
92 | ret = fcntl(fd, F_SETLKW, &lk); | |
93 | if (ret < 0) { | |
94 | usernic_error("Failed to lock \"%s\": %s\n", path, | |
95 | strerror(errno)); | |
49aba4d3 | 96 | close(fd); |
78f97d4c | 97 | return -1; |
20ab58c7 SH |
98 | } |
99 | ||
100 | return fd; | |
101 | } | |
102 | ||
552a7d05 | 103 | static char *get_username(void) |
20ab58c7 | 104 | { |
2b333aee | 105 | struct passwd *pwd; |
20ab58c7 | 106 | |
2b333aee CB |
107 | pwd = getpwuid(getuid()); |
108 | if (!pwd) { | |
af256970 | 109 | usernic_error("Failed to get username: %s\n", strerror(errno)); |
20ab58c7 SH |
110 | return NULL; |
111 | } | |
112 | ||
37fc7b9e | 113 | return pwd->pw_name; |
20ab58c7 SH |
114 | } |
115 | ||
21002b39 HK |
116 | static void free_groupnames(char **groupnames) |
117 | { | |
d791668b | 118 | int i; |
2b333aee | 119 | |
d791668b SH |
120 | if (!groupnames) |
121 | return; | |
2b333aee | 122 | |
d791668b SH |
123 | for (i = 0; groupnames[i]; i++) |
124 | free(groupnames[i]); | |
2b333aee | 125 | |
21002b39 HK |
126 | free(groupnames); |
127 | } | |
128 | ||
3043883c HK |
129 | static char **get_groupnames(void) |
130 | { | |
131 | int ngroups; | |
132 | gid_t *group_ids; | |
21002b39 | 133 | int ret, i; |
af59ff2e | 134 | char **groupnames; |
3043883c HK |
135 | struct group *gr; |
136 | ||
137 | ngroups = getgroups(0, NULL); | |
2b333aee | 138 | if (ngroups < 0) { |
af256970 CB |
139 | usernic_error("Failed to get number of groups the user " |
140 | "belongs to: %s\n", strerror(errno)); | |
af59ff2e HK |
141 | return NULL; |
142 | } | |
21002b39 HK |
143 | if (ngroups == 0) |
144 | return NULL; | |
1b7eaf07 | 145 | |
2b333aee CB |
146 | group_ids = malloc(sizeof(gid_t) * ngroups); |
147 | if (!group_ids) { | |
148 | usernic_error("Failed to allocate memory while getting groups " | |
e771c51d | 149 | "the user belongs to: %s\n", |
2b333aee | 150 | strerror(errno)); |
21002b39 HK |
151 | return NULL; |
152 | } | |
153 | ||
3043883c | 154 | ret = getgroups(ngroups, group_ids); |
3043883c HK |
155 | if (ret < 0) { |
156 | free(group_ids); | |
e771c51d | 157 | usernic_error("Failed to get process groups: %s\n", |
2b333aee | 158 | strerror(errno)); |
3043883c HK |
159 | return NULL; |
160 | } | |
161 | ||
2b333aee CB |
162 | groupnames = malloc(sizeof(char *) * (ngroups + 1)); |
163 | if (!groupnames) { | |
21002b39 | 164 | free(group_ids); |
2b333aee | 165 | usernic_error("Failed to allocate memory while getting group " |
e771c51d | 166 | "names: %s\n", |
2b333aee | 167 | strerror(errno)); |
21002b39 HK |
168 | return NULL; |
169 | } | |
170 | ||
2b333aee | 171 | memset(groupnames, 0, sizeof(char *) * (ngroups + 1)); |
21002b39 | 172 | |
2b333aee | 173 | for (i = 0; i < ngroups; i++) { |
3043883c | 174 | gr = getgrgid(group_ids[i]); |
2b333aee | 175 | if (!gr) { |
e771c51d | 176 | usernic_error("Failed to get group name: %s\n", |
2b333aee | 177 | strerror(errno)); |
3043883c | 178 | free(group_ids); |
21002b39 | 179 | free_groupnames(groupnames); |
3043883c HK |
180 | return NULL; |
181 | } | |
182 | ||
af59ff2e | 183 | groupnames[i] = strdup(gr->gr_name); |
2b333aee | 184 | if (!groupnames[i]) { |
e771c51d | 185 | usernic_error("Failed to copy group name \"%s\"", |
2b333aee | 186 | gr->gr_name); |
3043883c | 187 | free(group_ids); |
21002b39 | 188 | free_groupnames(groupnames); |
1b7eaf07 | 189 | return NULL; |
3043883c HK |
190 | } |
191 | } | |
192 | ||
3043883c HK |
193 | free(group_ids); |
194 | ||
af59ff2e HK |
195 | return groupnames; |
196 | } | |
197 | ||
af59ff2e HK |
198 | static bool name_is_in_groupnames(char *name, char **groupnames) |
199 | { | |
2b333aee CB |
200 | while (groupnames) { |
201 | if (!strcmp(name, *groupnames)) | |
af59ff2e HK |
202 | return true; |
203 | groupnames++; | |
204 | } | |
205 | return false; | |
3043883c HK |
206 | } |
207 | ||
af59ff2e HK |
208 | struct alloted_s { |
209 | char *name; | |
210 | int allowed; | |
211 | struct alloted_s *next; | |
212 | }; | |
3043883c | 213 | |
af256970 CB |
214 | static struct alloted_s *append_alloted(struct alloted_s **head, char *name, |
215 | int n) | |
3043883c HK |
216 | { |
217 | struct alloted_s *cur, *al; | |
218 | ||
2b333aee | 219 | if (!head || !name) { |
af256970 | 220 | /* Sanity check. Parameters should not be null. */ |
e771c51d | 221 | usernic_error("%s\n", "Unexpected NULL argument"); |
3043883c HK |
222 | return NULL; |
223 | } | |
224 | ||
2b333aee CB |
225 | al = malloc(sizeof(struct alloted_s)); |
226 | if (!al) { | |
af256970 CB |
227 | usernic_error("Failed to allocate memory: %s\n", |
228 | strerror(errno)); | |
3043883c HK |
229 | return NULL; |
230 | } | |
231 | ||
232 | al->name = strdup(name); | |
2b333aee | 233 | if (!al->name) { |
21002b39 HK |
234 | free(al); |
235 | return NULL; | |
236 | } | |
237 | ||
3043883c HK |
238 | al->allowed = n; |
239 | al->next = NULL; | |
240 | ||
2b333aee | 241 | if (!*head) { |
3043883c HK |
242 | *head = al; |
243 | return al; | |
244 | } | |
245 | ||
246 | cur = *head; | |
2b333aee | 247 | while (cur->next) |
3043883c | 248 | cur = cur->next; |
3043883c | 249 | cur->next = al; |
2b333aee | 250 | |
3043883c HK |
251 | return al; |
252 | } | |
253 | ||
254 | static void free_alloted(struct alloted_s **head) | |
255 | { | |
256 | struct alloted_s *cur; | |
257 | ||
2b333aee | 258 | if (!head) |
3043883c | 259 | return; |
3043883c HK |
260 | |
261 | cur = *head; | |
2b333aee | 262 | while (cur) { |
3043883c HK |
263 | cur = cur->next; |
264 | free((*head)->name); | |
265 | free(*head); | |
266 | *head = cur; | |
267 | } | |
268 | } | |
269 | ||
20ab58c7 SH |
270 | /* The configuration file consists of lines of the form: |
271 | * | |
8c81de19 | 272 | * user type bridge count |
af59ff2e HK |
273 | * or |
274 | * @group type bridge count | |
20ab58c7 | 275 | * |
8c81de19 SH |
276 | * Return the count entry for the calling user if there is one. Else |
277 | * return -1. | |
20ab58c7 | 278 | */ |
af256970 CB |
279 | static int get_alloted(char *me, char *intype, char *link, |
280 | struct alloted_s **alloted) | |
20ab58c7 | 281 | { |
2b333aee | 282 | int n, ret; |
af59ff2e | 283 | char name[100], type[100], br[100]; |
3043883c | 284 | char **groups; |
2b333aee | 285 | FILE *fin; |
20ab58c7 | 286 | |
2b333aee CB |
287 | int count = 0; |
288 | size_t len = 0; | |
289 | char *line = NULL; | |
290 | ||
291 | fin = fopen(LXC_USERNIC_CONF, "r"); | |
8c81de19 | 292 | if (!fin) { |
af256970 CB |
293 | usernic_error("Failed to open \"%s\": %s\n", LXC_USERNIC_CONF, |
294 | strerror(errno)); | |
20ab58c7 | 295 | return -1; |
8c81de19 | 296 | } |
20ab58c7 | 297 | |
af59ff2e | 298 | groups = get_groupnames(); |
20ab58c7 | 299 | while ((getline(&line, &len, fin)) != -1) { |
af256970 CB |
300 | ret = sscanf(line, "%99[^ \t] %99[^ \t] %99[^ \t] %d", name, |
301 | type, br, &n); | |
20ab58c7 SH |
302 | if (ret != 4) |
303 | continue; | |
af59ff2e HK |
304 | |
305 | if (strlen(name) == 0) | |
20ab58c7 | 306 | continue; |
af59ff2e | 307 | |
2b333aee | 308 | if (strcmp(name, me)) { |
af59ff2e HK |
309 | if (name[0] != '@') |
310 | continue; | |
2b333aee CB |
311 | |
312 | if (!name_is_in_groupnames(name + 1, groups)) | |
af59ff2e HK |
313 | continue; |
314 | } | |
2b333aee CB |
315 | |
316 | if (strcmp(type, intype)) | |
20ab58c7 | 317 | continue; |
2b333aee CB |
318 | |
319 | if (strcmp(link, br)) | |
20ab58c7 | 320 | continue; |
3043883c | 321 | |
2b333aee CB |
322 | /* Found the user or group with the appropriate settings, |
323 | * therefore finish the search. What to do if there are more | |
324 | * than one applicable lines? not specified in the docs. Since | |
325 | * getline is implemented with realloc, we don't need to free | |
326 | * line until exiting func. | |
680836fa | 327 | * |
2b333aee CB |
328 | * If append_alloted returns NULL, e.g. due to a malloc error, |
329 | * we set count to 0 and break the loop, allowing cleanup and | |
330 | * then exiting from main(). | |
af59ff2e | 331 | */ |
2b333aee | 332 | if (!append_alloted(alloted, name, n)) { |
680836fa HK |
333 | count = 0; |
334 | break; | |
335 | } | |
3043883c | 336 | count += n; |
3043883c HK |
337 | } |
338 | ||
af59ff2e | 339 | free_groupnames(groups); |
20ab58c7 | 340 | fclose(fin); |
f10fad2f | 341 | free(line); |
21002b39 | 342 | |
2b333aee | 343 | /* Now return the total number of nics that this user can create. */ |
3043883c | 344 | return count; |
20ab58c7 SH |
345 | } |
346 | ||
8f7b58d6 | 347 | static char *get_eol(char *s, char *e) |
20ab58c7 | 348 | { |
2b333aee | 349 | while ((s < e) && *s && (*s != '\n')) |
20ab58c7 SH |
350 | s++; |
351 | return s; | |
352 | } | |
353 | ||
8f7b58d6 | 354 | static char *get_eow(char *s, char *e) |
20ab58c7 | 355 | { |
2b333aee | 356 | while ((s < e) && *s && !isblank(*s) && (*s != '\n')) |
20ab58c7 SH |
357 | s++; |
358 | return s; | |
359 | } | |
360 | ||
6d8c2779 CB |
361 | static char *find_line(char *buf_start, char *buf_end, char *name, |
362 | char *net_type, char *net_link, char *net_dev, | |
363 | bool *owner, bool *found, bool *keep) | |
20ab58c7 | 364 | { |
6d8c2779 | 365 | char *end_of_line, *end_of_word, *line; |
1b7eaf07 | 366 | |
6d8c2779 CB |
367 | while (buf_start < buf_end) { |
368 | size_t len; | |
369 | char netdev_name[IFNAMSIZ]; | |
370 | ||
371 | *found = false; | |
372 | *keep = true; | |
373 | *owner = false; | |
374 | ||
375 | end_of_line = get_eol(buf_start, buf_end); | |
376 | if (end_of_line >= buf_end) | |
377 | return NULL; | |
378 | ||
379 | line = buf_start; | |
380 | if (*buf_start == '#') | |
20ab58c7 | 381 | goto next; |
2b333aee | 382 | |
6d8c2779 CB |
383 | while ((buf_start < buf_end) && isblank(*buf_start)) |
384 | buf_start++; | |
385 | ||
386 | /* Check whether the line contains the caller's name. */ | |
387 | end_of_word = get_eow(buf_start, buf_end); | |
388 | /* corrupt db */ | |
389 | if (!end_of_word) | |
390 | return NULL; | |
391 | ||
392 | if (strncmp(buf_start, name, strlen(name))) | |
393 | *found = false; | |
394 | ||
395 | *owner = true; | |
2b333aee | 396 | |
6d8c2779 CB |
397 | buf_start = end_of_word + 1; |
398 | while ((buf_start < buf_end) && isblank(*buf_start)) | |
399 | buf_start++; | |
400 | ||
401 | /* Check whether line is of the right network type. */ | |
402 | end_of_word = get_eow(buf_start, buf_end); | |
403 | /* corrupt db */ | |
404 | if (!end_of_word) | |
405 | return NULL; | |
2b333aee | 406 | |
6d8c2779 CB |
407 | if (strncmp(buf_start, net_type, strlen(net_type))) |
408 | *found = false; | |
409 | ||
410 | buf_start = end_of_word + 1; | |
411 | while ((buf_start < buf_end) && isblank(*buf_start)) | |
412 | buf_start++; | |
413 | ||
414 | /* Check whether line is contains the right link. */ | |
415 | end_of_word = get_eow(buf_start, buf_end); | |
416 | /* corrupt db */ | |
417 | if (!end_of_word) | |
418 | return NULL; | |
419 | ||
420 | if (strncmp(buf_start, net_link, strlen(net_link))) | |
421 | *found = false; | |
422 | ||
423 | buf_start = end_of_word + 1; | |
424 | while ((buf_start < buf_end) && isblank(*buf_start)) | |
425 | buf_start++; | |
426 | ||
427 | /* Check whether line contains the right network device. */ | |
428 | end_of_word = get_eow(buf_start, buf_end); | |
429 | /* corrupt db */ | |
430 | if (!end_of_word) | |
431 | return NULL; | |
432 | ||
433 | len = end_of_word - buf_start; | |
434 | /* corrupt db */ | |
435 | if (len >= IFNAMSIZ) | |
436 | return NULL; | |
2b333aee | 437 | |
6d8c2779 CB |
438 | memcpy(netdev_name, buf_start, len); |
439 | netdev_name[len] = '\0'; | |
440 | *keep = lxc_nic_exists(netdev_name); | |
2b333aee | 441 | |
6d8c2779 CB |
442 | if (net_dev && !strcmp(netdev_name, net_dev)) |
443 | *found = true; | |
2b333aee | 444 | |
6d8c2779 | 445 | return line; |
2b333aee | 446 | |
af256970 | 447 | next: |
6d8c2779 | 448 | buf_start = end_of_line + 1; |
20ab58c7 SH |
449 | } |
450 | ||
451 | return NULL; | |
452 | } | |
453 | ||
cb5659e1 | 454 | static int instantiate_veth(char *veth1, char *veth2) |
20ab58c7 | 455 | { |
cb5659e1 | 456 | int ret; |
20ab58c7 | 457 | |
cb5659e1 CB |
458 | ret = lxc_veth_create(veth1, veth2); |
459 | if (ret < 0) { | |
460 | usernic_error("Failed to create %s-%s : %s.\n", veth1, veth2, | |
461 | strerror(-ret)); | |
78f97d4c | 462 | return -1; |
20ab58c7 SH |
463 | } |
464 | ||
2b333aee CB |
465 | /* Changing the high byte of the mac address to 0xfe, the bridge |
466 | * interface will always keep the host's mac address and not take the | |
467 | * mac address of a container. */ | |
cb5659e1 CB |
468 | ret = setup_private_host_hw_addr(veth1); |
469 | if (ret < 0) | |
2b333aee | 470 | usernic_error("Failed to change mac address of host interface " |
cb5659e1 | 471 | "%s : %s\n", veth1, strerror(-ret)); |
20ab58c7 | 472 | |
cb5659e1 | 473 | return netdev_set_flag(veth1, IFF_UP); |
20ab58c7 SH |
474 | } |
475 | ||
0130df54 SH |
476 | static int get_mtu(char *name) |
477 | { | |
2b333aee CB |
478 | int idx; |
479 | ||
480 | idx = if_nametoindex(name); | |
8424b4e1 CB |
481 | if (idx < 0) |
482 | return -1; | |
0130df54 SH |
483 | return netdev_get_mtu(idx); |
484 | } | |
485 | ||
cb5659e1 | 486 | static int create_nic(char *nic, char *br, int pid, char **cnic) |
20ab58c7 | 487 | { |
cb5659e1 | 488 | char veth1buf[IFNAMSIZ], veth2buf[IFNAMSIZ]; |
2b333aee CB |
489 | int mtu, ret; |
490 | ||
20ab58c7 SH |
491 | ret = snprintf(veth1buf, IFNAMSIZ, "%s", nic); |
492 | if (ret < 0 || ret >= IFNAMSIZ) { | |
e771c51d | 493 | usernic_error("%s", "Could not create nic name\n"); |
cb5659e1 | 494 | return -1; |
20ab58c7 SH |
495 | } |
496 | ||
cb5659e1 CB |
497 | ret = snprintf(veth2buf, IFNAMSIZ, "%sp", veth1buf); |
498 | if (ret < 0 || ret >= IFNAMSIZ) { | |
499 | usernic_error("%s\n", "Could not create nic name"); | |
500 | return -1; | |
501 | } | |
20ab58c7 | 502 | /* create the nics */ |
cb5659e1 CB |
503 | ret = instantiate_veth(veth1buf, veth2buf); |
504 | if (ret < 0) { | |
e771c51d | 505 | usernic_error("%s", "Error creating veth tunnel\n"); |
cb5659e1 | 506 | return -1; |
20ab58c7 SH |
507 | } |
508 | ||
2b333aee | 509 | if (strcmp(br, "none")) { |
cff7b5eb FN |
510 | /* copy the bridge's mtu to both ends */ |
511 | mtu = get_mtu(br); | |
2b333aee CB |
512 | if (mtu > 0) { |
513 | ret = lxc_netdev_set_mtu(veth1buf, mtu); | |
514 | if (ret < 0) { | |
af256970 CB |
515 | usernic_error("Failed to set mtu to %d on %s\n", |
516 | mtu, veth1buf); | |
2b333aee CB |
517 | goto out_del; |
518 | } | |
519 | ||
520 | ret = lxc_netdev_set_mtu(veth2buf, mtu); | |
521 | if (ret < 0) { | |
af256970 CB |
522 | usernic_error("Failed to set mtu to %d on %s\n", |
523 | mtu, veth2buf); | |
cff7b5eb FN |
524 | goto out_del; |
525 | } | |
0130df54 | 526 | } |
0130df54 | 527 | |
cff7b5eb | 528 | /* attach veth1 to bridge */ |
581c75e7 | 529 | ret = lxc_bridge_attach(br, veth1buf); |
2b333aee | 530 | if (ret < 0) { |
e771c51d | 531 | usernic_error("Error attaching %s to %s\n", veth1buf, br); |
cff7b5eb FN |
532 | goto out_del; |
533 | } | |
20ab58c7 SH |
534 | } |
535 | ||
536 | /* pass veth2 to target netns */ | |
8d357196 | 537 | ret = lxc_netdev_move_by_name(veth2buf, pid, NULL); |
20ab58c7 | 538 | if (ret < 0) { |
af256970 CB |
539 | usernic_error("Error moving %s to network namespace of %d\n", |
540 | veth2buf, pid); | |
20ab58c7 SH |
541 | goto out_del; |
542 | } | |
2b333aee | 543 | |
4119204e | 544 | *cnic = strdup(veth2buf); |
2b333aee | 545 | if (!*cnic) { |
e771c51d | 546 | usernic_error("Failed to copy string \"%s\"\n", veth2buf); |
cb5659e1 | 547 | return -1; |
2b333aee CB |
548 | } |
549 | ||
cb5659e1 | 550 | return 0; |
20ab58c7 SH |
551 | |
552 | out_del: | |
553 | lxc_netdev_delete_by_name(veth1buf); | |
cb5659e1 | 554 | return -1; |
20ab58c7 SH |
555 | } |
556 | ||
03c2eb15 SH |
557 | struct entry_line { |
558 | char *start; | |
559 | int len; | |
560 | bool keep; | |
561 | }; | |
562 | ||
6d8c2779 CB |
563 | static bool cull_entries(int fd, char *name, char *net_type, char *net_link, |
564 | char *net_dev, bool *found_nicname) | |
20ab58c7 | 565 | { |
8b8e00a2 | 566 | int i, ret; |
6d8c2779 | 567 | char *buf, *buf_end, *buf_start; |
2b333aee | 568 | struct stat sb; |
8b8e00a2 | 569 | int n = 0; |
6d8c2779 | 570 | bool found, keep; |
03c2eb15 | 571 | struct entry_line *entry_lines = NULL; |
20ab58c7 | 572 | |
8b8e00a2 CB |
573 | ret = fstat(fd, &sb); |
574 | if (ret < 0) { | |
e771c51d | 575 | usernic_error("Failed to fstat: %s\n", strerror(errno)); |
1e985428 SG |
576 | return false; |
577 | } | |
2b333aee | 578 | |
6d8c2779 CB |
579 | if (!sb.st_size) |
580 | return false; | |
2b333aee | 581 | |
8b8e00a2 | 582 | buf = lxc_strmmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); |
20ab58c7 | 583 | if (buf == MAP_FAILED) { |
af256970 CB |
584 | usernic_error("Failed to establish shared memory mapping: %s\n", |
585 | strerror(errno)); | |
20ab58c7 SH |
586 | return false; |
587 | } | |
588 | ||
6d8c2779 CB |
589 | buf_start = buf; |
590 | buf_end = buf + sb.st_size; | |
591 | while ((buf_start = find_line(buf_start, buf_end, name, net_type, | |
592 | net_link, net_dev, &(bool){true}, &found, | |
593 | &keep))) { | |
2b333aee CB |
594 | struct entry_line *newe; |
595 | ||
596 | newe = realloc(entry_lines, sizeof(*entry_lines) * (n + 1)); | |
03c2eb15 SH |
597 | if (!newe) { |
598 | free(entry_lines); | |
6d8c2779 | 599 | lxc_strmunmap(buf, sb.st_size); |
03c2eb15 SH |
600 | return false; |
601 | } | |
2b333aee | 602 | |
6d8c2779 CB |
603 | if (found) |
604 | *found_nicname = true; | |
605 | ||
03c2eb15 | 606 | entry_lines = newe; |
6d8c2779 CB |
607 | entry_lines[n].start = buf_start; |
608 | entry_lines[n].len = get_eol(buf_start, buf_end) - entry_lines[n].start; | |
609 | entry_lines[n].keep = keep; | |
03c2eb15 | 610 | n++; |
2b333aee | 611 | |
6d8c2779 CB |
612 | buf_start += entry_lines[n - 1].len + 1; |
613 | if (buf_start >= buf_end) | |
20ab58c7 | 614 | break; |
2b333aee CB |
615 | } |
616 | ||
6d8c2779 | 617 | buf_start = buf; |
2b333aee | 618 | for (i = 0; i < n; i++) { |
03c2eb15 SH |
619 | if (!entry_lines[i].keep) |
620 | continue; | |
2b333aee | 621 | |
6d8c2779 CB |
622 | memcpy(buf_start, entry_lines[i].start, entry_lines[i].len); |
623 | buf_start += entry_lines[i].len; | |
624 | *buf_start = '\n'; | |
625 | buf_start++; | |
03c2eb15 SH |
626 | } |
627 | free(entry_lines); | |
2b333aee | 628 | |
6d8c2779 | 629 | ret = ftruncate(fd, buf_start - buf); |
3ca10814 | 630 | lxc_strmunmap(buf, sb.st_size); |
8b8e00a2 | 631 | if (ret < 0) |
af256970 CB |
632 | usernic_error("Failed to set new file size: %s\n", |
633 | strerror(errno)); | |
2b333aee | 634 | |
20ab58c7 SH |
635 | return true; |
636 | } | |
637 | ||
6d8c2779 | 638 | static int count_entries(char *buf, off_t len, char *name, char *net_type, char *net_link) |
20ab58c7 | 639 | { |
20ab58c7 | 640 | int count = 0; |
6d8c2779 | 641 | bool owner = false;; |
cef588fc | 642 | char *buf_end; |
6d8c2779 CB |
643 | |
644 | buf_end = &buf[len]; | |
645 | while ((buf = find_line(buf, buf_end, name, net_type, net_link, NULL, | |
646 | &owner, &(bool){true}, &(bool){true}))) { | |
647 | if (owner) | |
648 | count++; | |
649 | buf = get_eol(buf, buf_end) + 1; | |
650 | if (buf >= buf_end) | |
20ab58c7 SH |
651 | break; |
652 | } | |
653 | ||
654 | return count; | |
655 | } | |
656 | ||
af256970 | 657 | /* The dbfile has lines of the format: user type bridge nicname. */ |
8285dcfb CB |
658 | static char *get_nic_if_avail(int fd, struct alloted_s *names, int pid, |
659 | char *intype, char *br, int allowed, char **cnic) | |
20ab58c7 | 660 | { |
2b333aee | 661 | int ret; |
a92028b2 | 662 | size_t slen; |
cb5659e1 CB |
663 | char *newline, *owner; |
664 | char nicname[IFNAMSIZ]; | |
20ab58c7 | 665 | struct stat sb; |
3043883c | 666 | struct alloted_s *n; |
2b333aee CB |
667 | int count = 0; |
668 | char *buf = NULL; | |
20ab58c7 | 669 | |
2b333aee | 670 | for (n = names; n != NULL; n = n->next) |
8b8e00a2 | 671 | cull_entries(fd, n->name, intype, br, NULL, NULL); |
20ab58c7 | 672 | |
c073c147 | 673 | if (allowed == 0) |
8285dcfb | 674 | return NULL; |
c073c147 | 675 | |
3043883c HK |
676 | owner = names->name; |
677 | ||
a92028b2 CB |
678 | ret = fstat(fd, &sb); |
679 | if (ret < 0) { | |
e771c51d | 680 | usernic_error("Failed to fstat: %s\n", strerror(errno)); |
8285dcfb | 681 | return NULL; |
1e985428 | 682 | } |
2b333aee | 683 | |
a92028b2 CB |
684 | if (sb.st_size > 0) { |
685 | buf = lxc_strmmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, | |
686 | MAP_SHARED, fd, 0); | |
20ab58c7 | 687 | if (buf == MAP_FAILED) { |
a92028b2 CB |
688 | usernic_error("Failed to establish shared memory " |
689 | "mapping: %s\n", strerror(errno)); | |
8285dcfb | 690 | return NULL; |
20ab58c7 SH |
691 | } |
692 | ||
3043883c | 693 | owner = NULL; |
2b333aee | 694 | for (n = names; n != NULL; n = n->next) { |
a92028b2 | 695 | count = count_entries(buf, sb.st_size, n->name, intype, br); |
3043883c HK |
696 | if (count >= n->allowed) |
697 | continue; | |
698 | ||
699 | owner = n->name; | |
700 | break; | |
701 | } | |
a92028b2 CB |
702 | |
703 | lxc_strmunmap(buf, sb.st_size); | |
20ab58c7 SH |
704 | } |
705 | ||
3043883c | 706 | if (owner == NULL) |
8285dcfb | 707 | return NULL; |
3043883c | 708 | |
cb5659e1 CB |
709 | ret = snprintf(nicname, sizeof(nicname), "vethXXXXXX"); |
710 | if (ret < 0 || (size_t)ret >= sizeof(nicname)) | |
711 | return NULL; | |
712 | ||
713 | if (!lxc_mkifname(nicname)) | |
714 | return NULL; | |
715 | ||
716 | ret = create_nic(nicname, br, pid, cnic); | |
717 | if (ret < 0) { | |
718 | usernic_error("%s", "Failed to create new nic\n"); | |
8285dcfb CB |
719 | return NULL; |
720 | } | |
2b333aee | 721 | |
a92028b2 CB |
722 | /* strlen(owner) |
723 | * + | |
724 | * " " | |
725 | * + | |
726 | * strlen(intype) | |
727 | * + | |
728 | * " " | |
729 | * + | |
730 | * strlen(br) | |
731 | * + | |
732 | * " " | |
733 | * + | |
734 | * strlen(nicname) | |
735 | * + | |
736 | * \n | |
737 | * + | |
738 | * \0 | |
739 | */ | |
740 | slen = strlen(owner) + strlen(intype) + strlen(br) + strlen(nicname) + 4; | |
741 | newline = malloc(slen + 1); | |
2b333aee | 742 | if (!newline) { |
a92028b2 | 743 | free(newline); |
e771c51d | 744 | usernic_error("Failed allocate memory: %s\n", strerror(errno)); |
8285dcfb | 745 | return NULL; |
2b333aee CB |
746 | } |
747 | ||
a92028b2 CB |
748 | ret = snprintf(newline, slen + 1, "%s %s %s %s\n", owner, intype, br, nicname); |
749 | if (ret < 0 || (size_t)ret >= (slen + 1)) { | |
8285dcfb CB |
750 | if (lxc_netdev_delete_by_name(nicname) != 0) |
751 | usernic_error("Error unlinking %s\n", nicname); | |
a92028b2 | 752 | free(newline); |
8285dcfb | 753 | return NULL; |
20ab58c7 | 754 | } |
2b333aee | 755 | |
a92028b2 CB |
756 | /* Note that the file needs to be truncated to the size **without** the |
757 | * \0 byte! Files are not \0-terminated! | |
758 | */ | |
759 | ret = ftruncate(fd, sb.st_size + slen); | |
760 | if (ret < 0) | |
761 | usernic_error("Failed to truncate file: %s\n", strerror(errno)); | |
2b333aee | 762 | |
a92028b2 CB |
763 | buf = lxc_strmmap(NULL, sb.st_size + slen, PROT_READ | PROT_WRITE, |
764 | MAP_SHARED, fd, 0); | |
20ab58c7 | 765 | if (buf == MAP_FAILED) { |
af256970 CB |
766 | usernic_error("Failed to establish shared memory mapping: %s\n", |
767 | strerror(errno)); | |
8285dcfb CB |
768 | if (lxc_netdev_delete_by_name(nicname) != 0) |
769 | usernic_error("Error unlinking %s\n", nicname); | |
a92028b2 | 770 | free(newline); |
8285dcfb | 771 | return NULL; |
20ab58c7 | 772 | } |
2b333aee | 773 | |
a92028b2 CB |
774 | /* Note that the memory needs to be moved in the buffer **without** the |
775 | * \0 byte! Files are not \0-terminated! | |
776 | */ | |
777 | memmove(buf + sb.st_size, newline, slen); | |
778 | free(newline); | |
779 | lxc_strmunmap(buf, sb.st_size + slen); | |
2b333aee | 780 | |
cb5659e1 | 781 | return strdup(nicname); |
20ab58c7 SH |
782 | } |
783 | ||
552a7d05 | 784 | static bool create_db_dir(char *fnam) |
070a4b8e | 785 | { |
af256970 | 786 | int ret; |
2b333aee | 787 | char *p; |
070a4b8e | 788 | |
2b333aee | 789 | p = alloca(strlen(fnam) + 1); |
070a4b8e SH |
790 | strcpy(p, fnam); |
791 | fnam = p; | |
792 | p = p + 1; | |
2b333aee | 793 | |
070a4b8e | 794 | again: |
2b333aee CB |
795 | while (*p && *p != '/') |
796 | p++; | |
070a4b8e SH |
797 | if (!*p) |
798 | return true; | |
2b333aee | 799 | |
070a4b8e | 800 | *p = '\0'; |
af256970 CB |
801 | |
802 | ret = mkdir(fnam, 0755); | |
803 | if (ret < 0 && errno != EEXIST) { | |
804 | usernic_error("Failed to create %s: %s\n", fnam, | |
805 | strerror(errno)); | |
070a4b8e SH |
806 | *p = '/'; |
807 | return false; | |
808 | } | |
809 | *(p++) = '/'; | |
2b333aee | 810 | |
070a4b8e SH |
811 | goto again; |
812 | } | |
813 | ||
0cffb676 | 814 | static char *lxc_secure_rename_in_ns(int pid, char *oldname, char *newname, |
74c6e2b0 | 815 | int *container_veth_ifidx) |
4119204e | 816 | { |
c92dfebd | 817 | int ret; |
0059379f | 818 | pid_t pid_self; |
16af2380 | 819 | uid_t ruid, suid, euid; |
c92dfebd CB |
820 | char ifname[IFNAMSIZ]; |
821 | char *string_ret = NULL, *name = NULL; | |
822 | int fd = -1, ifindex = -1, ofd = -1; | |
4119204e | 823 | |
0059379f CB |
824 | pid_self = lxc_raw_getpid(); |
825 | ofd = lxc_preserve_ns(pid_self, "net"); | |
5d04811e | 826 | if (ofd < 0) { |
0059379f | 827 | usernic_error("Failed opening network namespace path for %d", pid_self); |
c92dfebd | 828 | return NULL; |
4119204e | 829 | } |
4119204e | 830 | |
5d04811e CB |
831 | fd = lxc_preserve_ns(pid, "net"); |
832 | if (fd < 0) { | |
e771c51d | 833 | usernic_error("Failed opening network namespace path for %d", pid); |
16af2380 | 834 | goto do_partial_cleanup; |
4119204e | 835 | } |
5d04811e | 836 | |
16af2380 CB |
837 | ret = getresuid(&ruid, &euid, &suid); |
838 | if (ret < 0) { | |
839 | usernic_error("Failed to retrieve real, effective, and saved " | |
840 | "user IDs: %s\n", | |
841 | strerror(errno)); | |
842 | goto do_partial_cleanup; | |
4119204e | 843 | } |
16af2380 CB |
844 | |
845 | ret = setns(fd, CLONE_NEWNET); | |
846 | close(fd); | |
847 | fd = -1; | |
848 | if (ret < 0) { | |
849 | usernic_error("Failed to setns() to the network namespace of " | |
e771c51d | 850 | "the container with PID %d: %s\n", |
16af2380 CB |
851 | pid, strerror(errno)); |
852 | goto do_partial_cleanup; | |
853 | } | |
854 | ||
855 | ret = setresuid(ruid, ruid, 0); | |
856 | if (ret < 0) { | |
857 | usernic_error("Failed to drop privilege by setting effective " | |
858 | "user id and real user id to %d, and saved user " | |
e771c51d | 859 | "ID to 0: %s\n", |
16af2380 | 860 | ruid, strerror(errno)); |
1a0e70ac CB |
861 | /* It's ok to jump to do_full_cleanup here since setresuid() |
862 | * will succeed when trying to set real, effective, and saved to | |
863 | * values they currently have. | |
864 | */ | |
16af2380 CB |
865 | goto do_full_cleanup; |
866 | } | |
867 | ||
c92dfebd CB |
868 | /* Check if old interface exists. */ |
869 | ifindex = if_nametoindex(oldname); | |
870 | if (!ifindex) { | |
871 | usernic_error("Failed to get netdev index: %s\n", strerror(errno)); | |
872 | goto do_full_cleanup; | |
8aecd66b | 873 | } |
16af2380 | 874 | |
c92dfebd CB |
875 | /* When the IFLA_IFNAME attribute is passed something like "<prefix>%d" |
876 | * netlink will replace the format specifier with an appropriate index. | |
877 | * So we pass "eth%d". | |
878 | */ | |
879 | if (newname) | |
880 | name = newname; | |
881 | else | |
882 | name = "eth%d"; | |
883 | ||
884 | ret = lxc_netdev_rename_by_name(oldname, name); | |
a055595c | 885 | name = NULL; |
16af2380 | 886 | if (ret < 0) { |
c92dfebd | 887 | usernic_error("Error %d renaming netdev %s to %s in container\n", |
a055595c | 888 | ret, oldname, newname ? newname : "eth%d"); |
16af2380 | 889 | goto do_full_cleanup; |
4119204e | 890 | } |
16af2380 | 891 | |
c92dfebd CB |
892 | /* Retrieve new name for interface. */ |
893 | if (!if_indextoname(ifindex, ifname)) { | |
894 | usernic_error("Failed to get new netdev name: %s\n", strerror(errno)); | |
895 | goto do_full_cleanup; | |
8aecd66b | 896 | } |
16af2380 | 897 | |
c92dfebd CB |
898 | /* Allocation failure for strdup() is checked below. */ |
899 | name = strdup(ifname); | |
900 | string_ret = name; | |
74c6e2b0 | 901 | *container_veth_ifidx = ifindex; |
16af2380 CB |
902 | |
903 | do_full_cleanup: | |
904 | ret = setresuid(ruid, euid, suid); | |
905 | if (ret < 0) { | |
c92dfebd CB |
906 | usernic_error("Failed to restore privilege by setting " |
907 | "effective user id to %d, real user id to %d, " | |
908 | "and saved user ID to %d: %s\n", ruid, euid, suid, | |
909 | strerror(errno)); | |
910 | ||
911 | string_ret = NULL; | |
4119204e | 912 | } |
4119204e | 913 | |
16af2380 CB |
914 | ret = setns(ofd, CLONE_NEWNET); |
915 | if (ret < 0) { | |
916 | usernic_error("Failed to setns() to original network namespace " | |
c92dfebd CB |
917 | "of PID %d: %s\n", ofd, strerror(errno)); |
918 | ||
919 | string_ret = NULL; | |
16af2380 | 920 | } |
4119204e | 921 | |
16af2380 | 922 | do_partial_cleanup: |
4119204e SH |
923 | if (fd >= 0) |
924 | close(fd); | |
c92dfebd CB |
925 | |
926 | if (!string_ret && name) | |
927 | free(name); | |
928 | ||
16af2380 CB |
929 | close(ofd); |
930 | ||
c92dfebd | 931 | return string_ret; |
4119204e SH |
932 | } |
933 | ||
1a0e70ac CB |
934 | /* If the caller (real uid, not effective uid) may read the /proc/[pid]/ns/net, |
935 | * then it is either the caller's netns or one which it created. | |
8d9f636d SH |
936 | */ |
937 | static bool may_access_netns(int pid) | |
938 | { | |
939 | int ret; | |
940 | char s[200]; | |
941 | uid_t ruid, suid, euid; | |
942 | bool may_access = false; | |
943 | ||
944 | ret = getresuid(&ruid, &euid, &suid); | |
2b333aee CB |
945 | if (ret < 0) { |
946 | usernic_error("Failed to retrieve real, effective, and saved " | |
947 | "user IDs: %s\n", | |
948 | strerror(errno)); | |
8d9f636d SH |
949 | return false; |
950 | } | |
2b333aee | 951 | |
8d9f636d | 952 | ret = setresuid(ruid, ruid, euid); |
2b333aee CB |
953 | if (ret < 0) { |
954 | usernic_error("Failed to drop privilege by setting effective " | |
955 | "user id and real user id to %d, and saved user " | |
e771c51d | 956 | "ID to %d: %s\n", |
2b333aee | 957 | ruid, euid, strerror(errno)); |
8d9f636d SH |
958 | return false; |
959 | } | |
2b333aee | 960 | |
8d9f636d | 961 | ret = snprintf(s, 200, "/proc/%d/ns/net", pid); |
2b333aee | 962 | if (ret < 0 || ret >= 200) |
8d9f636d | 963 | return false; |
2b333aee | 964 | |
8d9f636d | 965 | ret = access(s, R_OK); |
2b333aee CB |
966 | may_access = true; |
967 | if (ret < 0) { | |
968 | may_access = false; | |
969 | usernic_error("Uid %d may not access %s: %s\n", (int)ruid, s, strerror(errno)); | |
8d9f636d | 970 | } |
2b333aee | 971 | |
8d9f636d | 972 | ret = setresuid(ruid, euid, suid); |
2b333aee CB |
973 | if (ret < 0) { |
974 | usernic_error("Failed to restore user id to %d, real user id " | |
e771c51d | 975 | "to %d, and saved user ID to %d: %s\n", |
2b333aee | 976 | ruid, euid, suid, strerror(errno)); |
8d9f636d SH |
977 | may_access = false; |
978 | } | |
2b333aee | 979 | |
8d9f636d SH |
980 | return may_access; |
981 | } | |
982 | ||
900e5f94 CB |
983 | struct user_nic_args { |
984 | char *cmd; | |
985 | char *lxc_path; | |
986 | char *lxc_name; | |
987 | char *pid; | |
988 | char *type; | |
989 | char *link; | |
990 | char *veth_name; | |
991 | }; | |
992 | ||
8b8e00a2 CB |
993 | #define LXC_USERNIC_CREATE 0 |
994 | #define LXC_USERNIC_DELETE 1 | |
995 | ||
1bd8d726 CB |
996 | static bool is_privileged_over_netns(int netns_fd) |
997 | { | |
998 | int ret; | |
0059379f | 999 | pid_t pid_self; |
1bd8d726 CB |
1000 | uid_t euid, ruid, suid; |
1001 | bool bret = false; | |
1002 | int ofd = -1; | |
1003 | ||
0059379f CB |
1004 | pid_self = lxc_raw_getpid(); |
1005 | ofd = lxc_preserve_ns(pid_self, "net"); | |
1bd8d726 | 1006 | if (ofd < 0) { |
0059379f | 1007 | usernic_error("Failed opening network namespace path for %d", pid_self); |
1bd8d726 CB |
1008 | return false; |
1009 | } | |
1010 | ||
1011 | ret = getresuid(&ruid, &euid, &suid); | |
1012 | if (ret < 0) { | |
1013 | usernic_error("Failed to retrieve real, effective, and saved " | |
1014 | "user IDs: %s\n", | |
1015 | strerror(errno)); | |
1016 | goto do_partial_cleanup; | |
1017 | } | |
1018 | ||
1019 | ret = setns(netns_fd, CLONE_NEWNET); | |
1020 | if (ret < 0) { | |
1021 | usernic_error("Failed to setns() to network namespace %s\n", | |
1022 | strerror(errno)); | |
1023 | goto do_partial_cleanup; | |
1024 | } | |
1025 | ||
1026 | ret = setresuid(ruid, ruid, 0); | |
1027 | if (ret < 0) { | |
1028 | usernic_error("Failed to drop privilege by setting effective " | |
1029 | "user id and real user id to %d, and saved user " | |
1030 | "ID to 0: %s\n", | |
1031 | ruid, strerror(errno)); | |
1032 | /* It's ok to jump to do_full_cleanup here since setresuid() | |
1033 | * will succeed when trying to set real, effective, and saved to | |
1034 | * values they currently have. | |
1035 | */ | |
1036 | goto do_full_cleanup; | |
1037 | } | |
1038 | ||
1039 | /* Test whether we are privileged over the network namespace. To do this | |
1040 | * we try to delete the loopback interface which is not possible. If we | |
1041 | * are privileged over the network namespace we will get ENOTSUP. If we | |
1042 | * are not privileged over the network namespace we will get EPERM. | |
1043 | */ | |
1044 | ret = lxc_netdev_delete_by_name("lo"); | |
1045 | if (ret == -ENOTSUP) | |
1046 | bret = true; | |
1047 | ||
1048 | do_full_cleanup: | |
1049 | ret = setresuid(ruid, euid, suid); | |
1050 | if (ret < 0) { | |
1051 | usernic_error("Failed to restore privilege by setting " | |
1052 | "effective user id to %d, real user id to %d, " | |
1053 | "and saved user ID to %d: %s\n", ruid, euid, suid, | |
1054 | strerror(errno)); | |
1055 | ||
1056 | bret = false; | |
1057 | } | |
1058 | ||
1059 | ret = setns(ofd, CLONE_NEWNET); | |
1060 | if (ret < 0) { | |
1061 | usernic_error("Failed to setns() to original network namespace " | |
1062 | "of PID %d: %s\n", ofd, strerror(errno)); | |
1063 | ||
1064 | bret = false; | |
1065 | } | |
1066 | ||
1067 | do_partial_cleanup: | |
1068 | ||
1069 | close(ofd); | |
1070 | return bret; | |
1071 | } | |
1072 | ||
20ab58c7 SH |
1073 | int main(int argc, char *argv[]) |
1074 | { | |
99573f4a | 1075 | int fd, n, pid, request, ret; |
c92dfebd | 1076 | char *me, *newname; |
1bd8d726 | 1077 | struct user_nic_args args; |
99573f4a | 1078 | int container_veth_ifidx = -1, host_veth_ifidx = -1, netns_fd = -1; |
25aead3f | 1079 | char *cnic = NULL, *nicname = NULL; |
3043883c | 1080 | struct alloted_s *alloted = NULL; |
900e5f94 CB |
1081 | |
1082 | if (argc < 7 || argc > 8) { | |
1083 | usage(argv[0], true); | |
1084 | exit(EXIT_FAILURE); | |
1085 | } | |
1086 | ||
1087 | memset(&args, 0, sizeof(struct user_nic_args)); | |
1088 | args.cmd = argv[1]; | |
1089 | args.lxc_path = argv[2]; | |
1090 | args.lxc_name = argv[3]; | |
1091 | args.pid = argv[4]; | |
1092 | args.type = argv[5]; | |
1093 | args.link = argv[6]; | |
1094 | if (argc >= 8) | |
1095 | args.veth_name = argv[7]; | |
20ab58c7 | 1096 | |
8b8e00a2 CB |
1097 | if (!strcmp(args.cmd, "create")) { |
1098 | request = LXC_USERNIC_CREATE; | |
1099 | } else if (!strcmp(args.cmd, "delete")) { | |
1100 | request = LXC_USERNIC_DELETE; | |
1101 | } else { | |
1102 | usage(argv[0], true); | |
1103 | exit(EXIT_FAILURE); | |
1104 | } | |
1105 | ||
d04e77c3 CB |
1106 | /* Set a sane env, because we are setuid-root. */ |
1107 | ret = clearenv(); | |
1108 | if (ret) { | |
e771c51d | 1109 | usernic_error("%s", "Failed to clear environment\n"); |
2b333aee | 1110 | exit(EXIT_FAILURE); |
361b4fe7 | 1111 | } |
d04e77c3 CB |
1112 | |
1113 | ret = setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1); | |
1114 | if (ret < 0) { | |
e771c51d | 1115 | usernic_error("%s", "Failed to set PATH, exiting\n"); |
2b333aee | 1116 | exit(EXIT_FAILURE); |
6ad22d06 | 1117 | } |
d04e77c3 CB |
1118 | |
1119 | me = get_username(); | |
1120 | if (!me) { | |
e771c51d | 1121 | usernic_error("%s", "Failed to get username\n"); |
2b333aee | 1122 | exit(EXIT_FAILURE); |
20ab58c7 SH |
1123 | } |
1124 | ||
1bd8d726 CB |
1125 | if (request == LXC_USERNIC_CREATE) { |
1126 | ret = lxc_safe_int(args.pid, &pid); | |
1127 | if (ret < 0) { | |
1128 | usernic_error("Could not read pid: %s\n", args.pid); | |
1129 | exit(EXIT_FAILURE); | |
1130 | } | |
1131 | } else if (request == LXC_USERNIC_DELETE) { | |
1132 | netns_fd = open(args.pid, O_RDONLY); | |
1133 | if (netns_fd < 0) { | |
1134 | usernic_error("Could not open \"%s\": %s\n", args.pid, | |
1135 | strerror(errno)); | |
1136 | exit(EXIT_FAILURE); | |
1137 | } | |
8d9f636d SH |
1138 | } |
1139 | ||
8befa924 | 1140 | if (!create_db_dir(LXC_USERNIC_DB)) { |
e771c51d | 1141 | usernic_error("%s", "Failed to create directory for db file\n"); |
1bd8d726 CB |
1142 | if (netns_fd >= 0) |
1143 | close(netns_fd); | |
2b333aee | 1144 | exit(EXIT_FAILURE); |
070a4b8e SH |
1145 | } |
1146 | ||
d04e77c3 CB |
1147 | fd = open_and_lock(LXC_USERNIC_DB); |
1148 | if (fd < 0) { | |
e771c51d | 1149 | usernic_error("Failed to lock %s\n", LXC_USERNIC_DB); |
1bd8d726 CB |
1150 | if (netns_fd >= 0) |
1151 | close(netns_fd); | |
2b333aee | 1152 | exit(EXIT_FAILURE); |
20ab58c7 SH |
1153 | } |
1154 | ||
1bd8d726 CB |
1155 | if (request == LXC_USERNIC_CREATE) { |
1156 | if (!may_access_netns(pid)) { | |
1157 | usernic_error("User %s may not modify netns for pid %d\n", me, pid); | |
1158 | exit(EXIT_FAILURE); | |
1159 | } | |
1160 | } else if (request == LXC_USERNIC_DELETE) { | |
1161 | bool has_priv; | |
1162 | has_priv = is_privileged_over_netns(netns_fd); | |
1163 | close(netns_fd); | |
1164 | if (!has_priv) { | |
1165 | usernic_error("%s", "Process is not privileged over " | |
1166 | "network namespace\n"); | |
1167 | exit(EXIT_FAILURE); | |
1168 | } | |
8d9f636d SH |
1169 | } |
1170 | ||
8b8e00a2 | 1171 | n = get_alloted(me, args.type, args.link, &alloted); |
a055595c | 1172 | |
8b8e00a2 CB |
1173 | if (request == LXC_USERNIC_DELETE) { |
1174 | int ret; | |
1175 | struct alloted_s *it; | |
1176 | bool found_nicname = false; | |
1177 | ||
1178 | if (!is_ovs_bridge(args.link)) { | |
a055595c | 1179 | usernic_error("%s", "Deletion of non ovs type network " |
8b8e00a2 CB |
1180 | "devices not implemented\n"); |
1181 | close(fd); | |
1182 | free_alloted(&alloted); | |
1183 | exit(EXIT_FAILURE); | |
1184 | } | |
1185 | ||
1186 | /* Check whether the network device we are supposed to delete | |
1187 | * exists in the db. If it doesn't we will not delete it as we | |
1188 | * need to assume the network device is not under our control. | |
1189 | * As a side effect we also clear any invalid entries from the | |
1190 | * database. | |
1191 | */ | |
1192 | for (it = alloted; it; it = it->next) | |
1193 | cull_entries(fd, it->name, args.type, args.link, | |
1194 | args.veth_name, &found_nicname); | |
1195 | close(fd); | |
1196 | free_alloted(&alloted); | |
1197 | ||
1198 | if (!found_nicname) { | |
32311345 CB |
1199 | usernic_error("Caller is not allowed to delete network " |
1200 | "device \"%s\"\n", args.veth_name); | |
a055595c CB |
1201 | exit(EXIT_FAILURE); |
1202 | } | |
1203 | ||
1204 | ret = lxc_ovs_delete_port(args.link, args.veth_name); | |
1205 | if (ret < 0) { | |
1206 | usernic_error("Failed to remove port \"%s\" from " | |
1207 | "openvswitch bridge \"%s\"", | |
1208 | args.veth_name, args.link); | |
1209 | exit(EXIT_FAILURE); | |
1210 | } | |
8b8e00a2 | 1211 | |
a055595c CB |
1212 | exit(EXIT_SUCCESS); |
1213 | } | |
20ab58c7 | 1214 | if (n > 0) |
900e5f94 CB |
1215 | nicname = get_nic_if_avail(fd, alloted, pid, args.type, |
1216 | args.link, n, &cnic); | |
3043883c | 1217 | |
20ab58c7 | 1218 | close(fd); |
3043883c | 1219 | free_alloted(&alloted); |
8285dcfb | 1220 | if (!nicname) { |
e771c51d | 1221 | usernic_error("%s", "Quota reached\n"); |
2b333aee | 1222 | exit(EXIT_FAILURE); |
20ab58c7 | 1223 | } |
20ab58c7 | 1224 | |
2b333aee | 1225 | /* Now rename the link. */ |
74c6e2b0 CB |
1226 | newname = lxc_secure_rename_in_ns(pid, cnic, args.veth_name, |
1227 | &container_veth_ifidx); | |
c92dfebd | 1228 | if (!newname) { |
e771c51d | 1229 | usernic_error("%s", "Failed to rename the link\n"); |
d04e77c3 CB |
1230 | ret = lxc_netdev_delete_by_name(cnic); |
1231 | if (ret < 0) | |
1232 | usernic_error("Failed to delete \"%s\"\n", cnic); | |
8285dcfb | 1233 | free(nicname); |
2b333aee | 1234 | exit(EXIT_FAILURE); |
4119204e | 1235 | } |
cb5659e1 | 1236 | |
74c6e2b0 | 1237 | host_veth_ifidx = if_nametoindex(nicname); |
8424b4e1 CB |
1238 | if (!host_veth_ifidx) { |
1239 | free(newname); | |
1240 | free(nicname); | |
1241 | usernic_error("Failed to get netdev index: %s\n", strerror(errno)); | |
1242 | exit(EXIT_FAILURE); | |
1243 | } | |
20ab58c7 | 1244 | |
74c6e2b0 CB |
1245 | /* Write names of veth pairs and their ifindeces to stout: |
1246 | * (e.g. eth0:731:veth9MT2L4:730) | |
1247 | */ | |
1248 | fprintf(stdout, "%s:%d:%s:%d\n", newname, container_veth_ifidx, nicname, | |
1249 | host_veth_ifidx); | |
c92dfebd | 1250 | free(newname); |
8285dcfb | 1251 | free(nicname); |
2b333aee | 1252 | exit(EXIT_SUCCESS); |
20ab58c7 | 1253 | } |