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