1 | /* |
2 | * An implementation of key value pair (KVP) functionality for Linux. |
3 | * |
4 | * |
5 | * Copyright (C) 2010, Novell, Inc. |
6 | * Author : K. Y. Srinivasan <ksrinivasan@novell.com> |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify it |
9 | * under the terms of the GNU General Public License version 2 as published |
10 | * by the Free Software Foundation. |
11 | * |
12 | * This program is distributed in the hope that it will be useful, but |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or |
15 | * NON INFRINGEMENT. See the GNU General Public License for more |
16 | * details. |
17 | * |
18 | * You should have received a copy of the GNU General Public License |
19 | * along with this program; if not, write to the Free Software |
20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
21 | * |
22 | */ |
23 | |
24 | |
25 | #include <sys/poll.h> |
26 | #include <sys/utsname.h> |
27 | #include <stdio.h> |
28 | #include <stdlib.h> |
29 | #include <unistd.h> |
30 | #include <string.h> |
31 | #include <ctype.h> |
32 | #include <errno.h> |
33 | #include <arpa/inet.h> |
34 | #include <linux/hyperv.h> |
35 | #include <ifaddrs.h> |
36 | #include <netdb.h> |
37 | #include <syslog.h> |
38 | #include <sys/stat.h> |
39 | #include <fcntl.h> |
40 | #include <dirent.h> |
41 | #include <net/if.h> |
42 | #include <limits.h> |
43 | #include <getopt.h> |
44 | |
45 | /* |
46 | * KVP protocol: The user mode component first registers with the |
47 | * kernel component. Subsequently, the kernel component requests, data |
48 | * for the specified keys. In response to this message the user mode component |
49 | * fills in the value corresponding to the specified key. We overload the |
50 | * sequence field in the cn_msg header to define our KVP message types. |
51 | * |
52 | * We use this infrastructure for also supporting queries from user mode |
53 | * application for state that may be maintained in the KVP kernel component. |
54 | * |
55 | */ |
56 | |
57 | |
58 | enum key_index { |
59 | FullyQualifiedDomainName = 0, |
60 | IntegrationServicesVersion, /*This key is serviced in the kernel*/ |
61 | NetworkAddressIPv4, |
62 | NetworkAddressIPv6, |
63 | OSBuildNumber, |
64 | OSName, |
65 | OSMajorVersion, |
66 | OSMinorVersion, |
67 | OSVersion, |
68 | ProcessorArchitecture |
69 | }; |
70 | |
71 | |
72 | enum { |
73 | IPADDR = 0, |
74 | NETMASK, |
75 | GATEWAY, |
76 | DNS |
77 | }; |
78 | |
79 | enum { |
80 | IPV4 = 1, |
81 | IPV6, |
82 | IP_TYPE_MAX |
83 | }; |
84 | |
85 | static int in_hand_shake; |
86 | |
87 | static char *os_name = "" ; |
88 | static char *os_major = "" ; |
89 | static char *os_minor = "" ; |
90 | static char *processor_arch; |
91 | static char *os_build; |
92 | static char *os_version; |
93 | static char *lic_version = "Unknown version" ; |
94 | static char full_domain_name[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; |
95 | static struct utsname uts_buf; |
96 | |
97 | /* |
98 | * The location of the interface configuration file. |
99 | */ |
100 | |
101 | #define KVP_CONFIG_LOC "/var/lib/hyperv" |
102 | |
103 | #ifndef KVP_SCRIPTS_PATH |
104 | #define KVP_SCRIPTS_PATH "/usr/libexec/hypervkvpd/" |
105 | #endif |
106 | |
107 | #define KVP_NET_DIR "/sys/class/net/" |
108 | |
109 | #define MAX_FILE_NAME 100 |
110 | #define ENTRIES_PER_BLOCK 50 |
111 | /* |
112 | * Change this entry if the number of addresses increases in future |
113 | */ |
114 | #define MAX_IP_ENTRIES 64 |
115 | #define OUTSTR_BUF_SIZE ((INET6_ADDRSTRLEN + 1) * MAX_IP_ENTRIES) |
116 | |
117 | struct kvp_record { |
118 | char key[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; |
119 | char value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; |
120 | }; |
121 | |
122 | struct kvp_file_state { |
123 | int fd; |
124 | int num_blocks; |
125 | struct kvp_record *records; |
126 | int num_records; |
127 | char fname[MAX_FILE_NAME]; |
128 | }; |
129 | |
130 | static struct kvp_file_state kvp_file_info[KVP_POOL_COUNT]; |
131 | |
132 | static void kvp_acquire_lock(int pool) |
133 | { |
134 | struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0}; |
135 | fl.l_pid = getpid(); |
136 | |
137 | if (fcntl(kvp_file_info[pool].fd, F_SETLKW, &fl) == -1) { |
138 | syslog(LOG_ERR, "Failed to acquire the lock pool: %d; error: %d %s" , pool, |
139 | errno, strerror(errno)); |
140 | exit(EXIT_FAILURE); |
141 | } |
142 | } |
143 | |
144 | static void kvp_release_lock(int pool) |
145 | { |
146 | struct flock fl = {F_UNLCK, SEEK_SET, 0, 0, 0}; |
147 | fl.l_pid = getpid(); |
148 | |
149 | if (fcntl(kvp_file_info[pool].fd, F_SETLK, &fl) == -1) { |
150 | syslog(LOG_ERR, "Failed to release the lock pool: %d; error: %d %s" , pool, |
151 | errno, strerror(errno)); |
152 | exit(EXIT_FAILURE); |
153 | } |
154 | } |
155 | |
156 | static void kvp_update_file(int pool) |
157 | { |
158 | FILE *filep; |
159 | |
160 | /* |
161 | * We are going to write our in-memory registry out to |
162 | * disk; acquire the lock first. |
163 | */ |
164 | kvp_acquire_lock(pool); |
165 | |
166 | filep = fopen(kvp_file_info[pool].fname, "we" ); |
167 | if (!filep) { |
168 | syslog(LOG_ERR, "Failed to open file, pool: %d; error: %d %s" , pool, |
169 | errno, strerror(errno)); |
170 | kvp_release_lock(pool); |
171 | exit(EXIT_FAILURE); |
172 | } |
173 | |
174 | fwrite(kvp_file_info[pool].records, sizeof(struct kvp_record), |
175 | kvp_file_info[pool].num_records, filep); |
176 | |
177 | if (ferror(filep) || fclose(filep)) { |
178 | kvp_release_lock(pool); |
179 | syslog(LOG_ERR, "Failed to write file, pool: %d" , pool); |
180 | exit(EXIT_FAILURE); |
181 | } |
182 | |
183 | kvp_release_lock(pool); |
184 | } |
185 | |
186 | static void kvp_update_mem_state(int pool) |
187 | { |
188 | FILE *filep; |
189 | size_t records_read = 0; |
190 | struct kvp_record *record = kvp_file_info[pool].records; |
191 | struct kvp_record *readp; |
192 | int num_blocks = kvp_file_info[pool].num_blocks; |
193 | int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK; |
194 | |
195 | kvp_acquire_lock(pool); |
196 | |
197 | filep = fopen(kvp_file_info[pool].fname, "re" ); |
198 | if (!filep) { |
199 | syslog(LOG_ERR, "Failed to open file, pool: %d; error: %d %s" , pool, |
200 | errno, strerror(errno)); |
201 | kvp_release_lock(pool); |
202 | exit(EXIT_FAILURE); |
203 | } |
204 | for (;;) { |
205 | readp = &record[records_read]; |
206 | records_read += fread(readp, sizeof(struct kvp_record), |
207 | ENTRIES_PER_BLOCK * num_blocks - records_read, |
208 | filep); |
209 | |
210 | if (ferror(filep)) { |
211 | syslog(LOG_ERR, |
212 | "Failed to read file, pool: %d; error: %d %s" , |
213 | pool, errno, strerror(errno)); |
214 | kvp_release_lock(pool); |
215 | exit(EXIT_FAILURE); |
216 | } |
217 | |
218 | if (!feof(filep)) { |
219 | /* |
220 | * We have more data to read. |
221 | */ |
222 | num_blocks++; |
223 | record = realloc(record, alloc_unit * num_blocks); |
224 | |
225 | if (record == NULL) { |
226 | syslog(LOG_ERR, "malloc failed" ); |
227 | kvp_release_lock(pool); |
228 | exit(EXIT_FAILURE); |
229 | } |
230 | continue; |
231 | } |
232 | break; |
233 | } |
234 | |
235 | kvp_file_info[pool].num_blocks = num_blocks; |
236 | kvp_file_info[pool].records = record; |
237 | kvp_file_info[pool].num_records = records_read; |
238 | |
239 | fclose(filep); |
240 | kvp_release_lock(pool); |
241 | } |
242 | |
243 | static int kvp_file_init(void) |
244 | { |
245 | int fd; |
246 | char *fname; |
247 | int i; |
248 | int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK; |
249 | |
250 | if (access(KVP_CONFIG_LOC, F_OK)) { |
251 | if (mkdir(KVP_CONFIG_LOC, 0755 /* rwxr-xr-x */)) { |
252 | syslog(LOG_ERR, "Failed to create '%s'; error: %d %s" , KVP_CONFIG_LOC, |
253 | errno, strerror(errno)); |
254 | exit(EXIT_FAILURE); |
255 | } |
256 | } |
257 | |
258 | for (i = 0; i < KVP_POOL_COUNT; i++) { |
259 | fname = kvp_file_info[i].fname; |
260 | sprintf(buf: fname, fmt: "%s/.kvp_pool_%d" , KVP_CONFIG_LOC, i); |
261 | fd = open(fname, O_RDWR | O_CREAT | O_CLOEXEC, 0644 /* rw-r--r-- */); |
262 | |
263 | if (fd == -1) |
264 | return 1; |
265 | |
266 | kvp_file_info[i].fd = fd; |
267 | kvp_file_info[i].num_blocks = 1; |
268 | kvp_file_info[i].records = malloc(alloc_unit); |
269 | if (kvp_file_info[i].records == NULL) |
270 | return 1; |
271 | kvp_file_info[i].num_records = 0; |
272 | kvp_update_mem_state(pool: i); |
273 | } |
274 | |
275 | return 0; |
276 | } |
277 | |
278 | static int kvp_key_delete(int pool, const __u8 *key, int key_size) |
279 | { |
280 | int i; |
281 | int j, k; |
282 | int num_records; |
283 | struct kvp_record *record; |
284 | |
285 | /* |
286 | * First update the in-memory state. |
287 | */ |
288 | kvp_update_mem_state(pool); |
289 | |
290 | num_records = kvp_file_info[pool].num_records; |
291 | record = kvp_file_info[pool].records; |
292 | |
293 | for (i = 0; i < num_records; i++) { |
294 | if (memcmp(p: key, q: record[i].key, size: key_size)) |
295 | continue; |
296 | /* |
297 | * Found a match; just move the remaining |
298 | * entries up. |
299 | */ |
300 | if (i == (num_records - 1)) { |
301 | kvp_file_info[pool].num_records--; |
302 | kvp_update_file(pool); |
303 | return 0; |
304 | } |
305 | |
306 | j = i; |
307 | k = j + 1; |
308 | for (; k < num_records; k++) { |
309 | strcpy(p: record[j].key, q: record[k].key); |
310 | strcpy(p: record[j].value, q: record[k].value); |
311 | j++; |
312 | } |
313 | |
314 | kvp_file_info[pool].num_records--; |
315 | kvp_update_file(pool); |
316 | return 0; |
317 | } |
318 | return 1; |
319 | } |
320 | |
321 | static int kvp_key_add_or_modify(int pool, const __u8 *key, int key_size, |
322 | const __u8 *value, int value_size) |
323 | { |
324 | int i; |
325 | int num_records; |
326 | struct kvp_record *record; |
327 | int num_blocks; |
328 | |
329 | if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) || |
330 | (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) |
331 | return 1; |
332 | |
333 | /* |
334 | * First update the in-memory state. |
335 | */ |
336 | kvp_update_mem_state(pool); |
337 | |
338 | num_records = kvp_file_info[pool].num_records; |
339 | record = kvp_file_info[pool].records; |
340 | num_blocks = kvp_file_info[pool].num_blocks; |
341 | |
342 | for (i = 0; i < num_records; i++) { |
343 | if (memcmp(p: key, q: record[i].key, size: key_size)) |
344 | continue; |
345 | /* |
346 | * Found a match; just update the value - |
347 | * this is the modify case. |
348 | */ |
349 | memcpy(record[i].value, value, value_size); |
350 | kvp_update_file(pool); |
351 | return 0; |
352 | } |
353 | |
354 | /* |
355 | * Need to add a new entry; |
356 | */ |
357 | if (num_records == (ENTRIES_PER_BLOCK * num_blocks)) { |
358 | /* Need to allocate a larger array for reg entries. */ |
359 | record = realloc(record, sizeof(struct kvp_record) * |
360 | ENTRIES_PER_BLOCK * (num_blocks + 1)); |
361 | |
362 | if (record == NULL) |
363 | return 1; |
364 | kvp_file_info[pool].num_blocks++; |
365 | |
366 | } |
367 | memcpy(record[i].value, value, value_size); |
368 | memcpy(record[i].key, key, key_size); |
369 | kvp_file_info[pool].records = record; |
370 | kvp_file_info[pool].num_records++; |
371 | kvp_update_file(pool); |
372 | return 0; |
373 | } |
374 | |
375 | static int kvp_get_value(int pool, const __u8 *key, int key_size, __u8 *value, |
376 | int value_size) |
377 | { |
378 | int i; |
379 | int num_records; |
380 | struct kvp_record *record; |
381 | |
382 | if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) || |
383 | (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) |
384 | return 1; |
385 | |
386 | /* |
387 | * First update the in-memory state. |
388 | */ |
389 | kvp_update_mem_state(pool); |
390 | |
391 | num_records = kvp_file_info[pool].num_records; |
392 | record = kvp_file_info[pool].records; |
393 | |
394 | for (i = 0; i < num_records; i++) { |
395 | if (memcmp(p: key, q: record[i].key, size: key_size)) |
396 | continue; |
397 | /* |
398 | * Found a match; just copy the value out. |
399 | */ |
400 | memcpy(value, record[i].value, value_size); |
401 | return 0; |
402 | } |
403 | |
404 | return 1; |
405 | } |
406 | |
407 | static int kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, |
408 | __u8 *value, int value_size) |
409 | { |
410 | struct kvp_record *record; |
411 | |
412 | /* |
413 | * First update our in-memory database. |
414 | */ |
415 | kvp_update_mem_state(pool); |
416 | record = kvp_file_info[pool].records; |
417 | |
418 | if (index >= kvp_file_info[pool].num_records) { |
419 | return 1; |
420 | } |
421 | |
422 | memcpy(key, record[index].key, key_size); |
423 | memcpy(value, record[index].value, value_size); |
424 | return 0; |
425 | } |
426 | |
427 | |
428 | void kvp_get_os_info(void) |
429 | { |
430 | FILE *file; |
431 | char *p, buf[512]; |
432 | |
433 | uname(&uts_buf); |
434 | os_version = uts_buf.release; |
435 | os_build = strdup(uts_buf.release); |
436 | |
437 | os_name = uts_buf.sysname; |
438 | processor_arch = uts_buf.machine; |
439 | |
440 | /* |
441 | * The current windows host (win7) expects the build |
442 | * string to be of the form: x.y.z |
443 | * Strip additional information we may have. |
444 | */ |
445 | p = strchr(os_version, '-'); |
446 | if (p) |
447 | *p = '\0'; |
448 | |
449 | /* |
450 | * Parse the /etc/os-release file if present: |
451 | * https://www.freedesktop.org/software/systemd/man/os-release.html |
452 | */ |
453 | file = fopen("/etc/os-release" , "r" ); |
454 | if (file != NULL) { |
455 | while (fgets(buf, sizeof(buf), file)) { |
456 | char *value, *q; |
457 | |
458 | /* Ignore comments */ |
459 | if (buf[0] == '#') |
460 | continue; |
461 | |
462 | /* Split into name=value */ |
463 | p = strchr(buf, '='); |
464 | if (!p) |
465 | continue; |
466 | *p++ = 0; |
467 | |
468 | /* Remove quotes and newline; un-escape */ |
469 | value = p; |
470 | q = p; |
471 | while (*p) { |
472 | if (*p == '\\') { |
473 | ++p; |
474 | if (!*p) |
475 | break; |
476 | *q++ = *p++; |
477 | } else if (*p == '\'' || *p == '"' || |
478 | *p == '\n') { |
479 | ++p; |
480 | } else { |
481 | *q++ = *p++; |
482 | } |
483 | } |
484 | *q = 0; |
485 | |
486 | if (!strcmp(buf, "NAME" )) { |
487 | p = strdup(value); |
488 | if (!p) |
489 | break; |
490 | os_name = p; |
491 | } else if (!strcmp(buf, "VERSION_ID" )) { |
492 | p = strdup(value); |
493 | if (!p) |
494 | break; |
495 | os_major = p; |
496 | } |
497 | } |
498 | fclose(file); |
499 | return; |
500 | } |
501 | |
502 | /* Fallback for older RH/SUSE releases */ |
503 | file = fopen("/etc/SuSE-release" , "r" ); |
504 | if (file != NULL) |
505 | goto kvp_osinfo_found; |
506 | file = fopen("/etc/redhat-release" , "r" ); |
507 | if (file != NULL) |
508 | goto kvp_osinfo_found; |
509 | |
510 | /* |
511 | * We don't have information about the os. |
512 | */ |
513 | return; |
514 | |
515 | kvp_osinfo_found: |
516 | /* up to three lines */ |
517 | p = fgets(buf, sizeof(buf), file); |
518 | if (p) { |
519 | p = strchr(buf, '\n'); |
520 | if (p) |
521 | *p = '\0'; |
522 | p = strdup(buf); |
523 | if (!p) |
524 | goto done; |
525 | os_name = p; |
526 | |
527 | /* second line */ |
528 | p = fgets(buf, sizeof(buf), file); |
529 | if (p) { |
530 | p = strchr(buf, '\n'); |
531 | if (p) |
532 | *p = '\0'; |
533 | p = strdup(buf); |
534 | if (!p) |
535 | goto done; |
536 | os_major = p; |
537 | |
538 | /* third line */ |
539 | p = fgets(buf, sizeof(buf), file); |
540 | if (p) { |
541 | p = strchr(buf, '\n'); |
542 | if (p) |
543 | *p = '\0'; |
544 | p = strdup(buf); |
545 | if (p) |
546 | os_minor = p; |
547 | } |
548 | } |
549 | } |
550 | |
551 | done: |
552 | fclose(file); |
553 | return; |
554 | } |
555 | |
556 | |
557 | |
558 | /* |
559 | * Retrieve an interface name corresponding to the specified guid. |
560 | * If there is a match, the function returns a pointer |
561 | * to the interface name and if not, a NULL is returned. |
562 | * If a match is found, the caller is responsible for |
563 | * freeing the memory. |
564 | */ |
565 | |
566 | static char *kvp_get_if_name(char *guid) |
567 | { |
568 | DIR *dir; |
569 | struct dirent *entry; |
570 | FILE *file; |
571 | char *p, *x; |
572 | char *if_name = NULL; |
573 | char buf[256]; |
574 | char dev_id[PATH_MAX]; |
575 | |
576 | dir = opendir(KVP_NET_DIR); |
577 | if (dir == NULL) |
578 | return NULL; |
579 | |
580 | while ((entry = readdir(dir)) != NULL) { |
581 | /* |
582 | * Set the state for the next pass. |
583 | */ |
584 | snprintf(buf: dev_id, size: sizeof(dev_id), fmt: "%s%s/device/device_id" , |
585 | KVP_NET_DIR, entry->d_name); |
586 | |
587 | file = fopen(dev_id, "r" ); |
588 | if (file == NULL) |
589 | continue; |
590 | |
591 | p = fgets(buf, sizeof(buf), file); |
592 | if (p) { |
593 | x = strchr(p, '\n'); |
594 | if (x) |
595 | *x = '\0'; |
596 | |
597 | if (!strcmp(p, guid)) { |
598 | /* |
599 | * Found the guid match; return the interface |
600 | * name. The caller will free the memory. |
601 | */ |
602 | if_name = strdup(entry->d_name); |
603 | fclose(file); |
604 | break; |
605 | } |
606 | } |
607 | fclose(file); |
608 | } |
609 | |
610 | closedir(dir); |
611 | return if_name; |
612 | } |
613 | |
614 | /* |
615 | * Retrieve the MAC address given the interface name. |
616 | */ |
617 | |
618 | static char *kvp_if_name_to_mac(char *if_name) |
619 | { |
620 | FILE *file; |
621 | char *p, *x; |
622 | char buf[256]; |
623 | char addr_file[PATH_MAX]; |
624 | unsigned int i; |
625 | char *mac_addr = NULL; |
626 | |
627 | snprintf(buf: addr_file, size: sizeof(addr_file), fmt: "%s%s%s" , KVP_NET_DIR, |
628 | if_name, "/address" ); |
629 | |
630 | file = fopen(addr_file, "r" ); |
631 | if (file == NULL) |
632 | return NULL; |
633 | |
634 | p = fgets(buf, sizeof(buf), file); |
635 | if (p) { |
636 | x = strchr(p, '\n'); |
637 | if (x) |
638 | *x = '\0'; |
639 | for (i = 0; i < strlen(p); i++) |
640 | p[i] = toupper(p[i]); |
641 | mac_addr = strdup(p); |
642 | } |
643 | |
644 | fclose(file); |
645 | return mac_addr; |
646 | } |
647 | |
648 | static void kvp_process_ipconfig_file(char *cmd, |
649 | char *config_buf, unsigned int len, |
650 | int element_size, int offset) |
651 | { |
652 | char buf[256]; |
653 | char *p; |
654 | char *x; |
655 | FILE *file; |
656 | |
657 | /* |
658 | * First execute the command. |
659 | */ |
660 | file = popen(cmd, "r" ); |
661 | if (file == NULL) |
662 | return; |
663 | |
664 | if (offset == 0) |
665 | memset(config_buf, 0, len); |
666 | while ((p = fgets(buf, sizeof(buf), file)) != NULL) { |
667 | if (len < strlen(config_buf) + element_size + 1) |
668 | break; |
669 | |
670 | x = strchr(p, '\n'); |
671 | if (x) |
672 | *x = '\0'; |
673 | |
674 | strcat(p: config_buf, q: p); |
675 | strcat(p: config_buf, q: ";" ); |
676 | } |
677 | pclose(file); |
678 | } |
679 | |
680 | static void kvp_get_ipconfig_info(char *if_name, |
681 | struct hv_kvp_ipaddr_value *buffer) |
682 | { |
683 | char cmd[512]; |
684 | char dhcp_info[128]; |
685 | char *p; |
686 | FILE *file; |
687 | |
688 | /* |
689 | * Get the address of default gateway (ipv4). |
690 | */ |
691 | sprintf(buf: cmd, fmt: "%s %s" , "ip route show dev" , if_name); |
692 | strcat(p: cmd, q: " | awk '/default/ {print $3 }'" ); |
693 | |
694 | /* |
695 | * Execute the command to gather gateway info. |
696 | */ |
697 | kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, |
698 | (MAX_GATEWAY_SIZE * 2), INET_ADDRSTRLEN, 0); |
699 | |
700 | /* |
701 | * Get the address of default gateway (ipv6). |
702 | */ |
703 | sprintf(buf: cmd, fmt: "%s %s" , "ip -f inet6 route show dev" , if_name); |
704 | strcat(p: cmd, q: " | awk '/default/ {print $3 }'" ); |
705 | |
706 | /* |
707 | * Execute the command to gather gateway info (ipv6). |
708 | */ |
709 | kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, |
710 | (MAX_GATEWAY_SIZE * 2), INET6_ADDRSTRLEN, 1); |
711 | |
712 | |
713 | /* |
714 | * Gather the DNS state. |
715 | * Since there is no standard way to get this information |
716 | * across various distributions of interest; we just invoke |
717 | * an external script that needs to be ported across distros |
718 | * of interest. |
719 | * |
720 | * Following is the expected format of the information from the script: |
721 | * |
722 | * ipaddr1 (nameserver1) |
723 | * ipaddr2 (nameserver2) |
724 | * . |
725 | * . |
726 | */ |
727 | |
728 | sprintf(buf: cmd, KVP_SCRIPTS_PATH "%s" , "hv_get_dns_info" ); |
729 | |
730 | /* |
731 | * Execute the command to gather DNS info. |
732 | */ |
733 | kvp_process_ipconfig_file(cmd, (char *)buffer->dns_addr, |
734 | (MAX_IP_ADDR_SIZE * 2), INET_ADDRSTRLEN, 0); |
735 | |
736 | /* |
737 | * Gather the DHCP state. |
738 | * We will gather this state by invoking an external script. |
739 | * The parameter to the script is the interface name. |
740 | * Here is the expected output: |
741 | * |
742 | * Enabled: DHCP enabled. |
743 | */ |
744 | |
745 | sprintf(buf: cmd, KVP_SCRIPTS_PATH "%s %s" , "hv_get_dhcp_info" , if_name); |
746 | |
747 | file = popen(cmd, "r" ); |
748 | if (file == NULL) |
749 | return; |
750 | |
751 | p = fgets(dhcp_info, sizeof(dhcp_info), file); |
752 | if (p == NULL) { |
753 | pclose(file); |
754 | return; |
755 | } |
756 | |
757 | if (!strncmp(p, "Enabled" , 7)) |
758 | buffer->dhcp_enabled = 1; |
759 | else |
760 | buffer->dhcp_enabled = 0; |
761 | |
762 | pclose(file); |
763 | } |
764 | |
765 | |
766 | static unsigned int hweight32(unsigned int *w) |
767 | { |
768 | unsigned int res = *w - ((*w >> 1) & 0x55555555); |
769 | res = (res & 0x33333333) + ((res >> 2) & 0x33333333); |
770 | res = (res + (res >> 4)) & 0x0F0F0F0F; |
771 | res = res + (res >> 8); |
772 | return (res + (res >> 16)) & 0x000000FF; |
773 | } |
774 | |
775 | static int kvp_process_ip_address(void *addrp, |
776 | int family, char *buffer, |
777 | int length, int *offset) |
778 | { |
779 | struct sockaddr_in *addr; |
780 | struct sockaddr_in6 *addr6; |
781 | int addr_length; |
782 | char tmp[50]; |
783 | const char *str; |
784 | |
785 | if (family == AF_INET) { |
786 | addr = addrp; |
787 | str = inet_ntop(family, &addr->sin_addr, tmp, 50); |
788 | addr_length = INET_ADDRSTRLEN; |
789 | } else { |
790 | addr6 = addrp; |
791 | str = inet_ntop(family, &addr6->sin6_addr.s6_addr, tmp, 50); |
792 | addr_length = INET6_ADDRSTRLEN; |
793 | } |
794 | |
795 | if ((length - *offset) < addr_length + 2) |
796 | return HV_E_FAIL; |
797 | if (str == NULL) { |
798 | strcpy(p: buffer, q: "inet_ntop failed\n" ); |
799 | return HV_E_FAIL; |
800 | } |
801 | if (*offset == 0) |
802 | strcpy(p: buffer, q: tmp); |
803 | else { |
804 | strcat(p: buffer, q: ";" ); |
805 | strcat(p: buffer, q: tmp); |
806 | } |
807 | |
808 | *offset += strlen(str) + 1; |
809 | |
810 | return 0; |
811 | } |
812 | |
813 | static int |
814 | kvp_get_ip_info(int family, char *if_name, int op, |
815 | void *out_buffer, unsigned int length) |
816 | { |
817 | struct ifaddrs *ifap; |
818 | struct ifaddrs *curp; |
819 | int offset = 0; |
820 | int sn_offset = 0; |
821 | int error = 0; |
822 | char *buffer; |
823 | struct hv_kvp_ipaddr_value *ip_buffer = NULL; |
824 | char cidr_mask[5]; /* /xyz */ |
825 | int weight; |
826 | int i; |
827 | unsigned int *w; |
828 | char *sn_str; |
829 | struct sockaddr_in6 *addr6; |
830 | |
831 | if (op == KVP_OP_ENUMERATE) { |
832 | buffer = out_buffer; |
833 | } else { |
834 | ip_buffer = out_buffer; |
835 | buffer = (char *)ip_buffer->ip_addr; |
836 | ip_buffer->addr_family = 0; |
837 | } |
838 | /* |
839 | * On entry into this function, the buffer is capable of holding the |
840 | * maximum key value. |
841 | */ |
842 | |
843 | if (getifaddrs(&ifap)) { |
844 | strcpy(p: buffer, q: "getifaddrs failed\n" ); |
845 | return HV_E_FAIL; |
846 | } |
847 | |
848 | curp = ifap; |
849 | while (curp != NULL) { |
850 | if (curp->ifa_addr == NULL) { |
851 | curp = curp->ifa_next; |
852 | continue; |
853 | } |
854 | |
855 | if ((if_name != NULL) && |
856 | (strncmp(curp->ifa_name, if_name, strlen(if_name)))) { |
857 | /* |
858 | * We want info about a specific interface; |
859 | * just continue. |
860 | */ |
861 | curp = curp->ifa_next; |
862 | continue; |
863 | } |
864 | |
865 | /* |
866 | * We only support two address families: AF_INET and AF_INET6. |
867 | * If a family value of 0 is specified, we collect both |
868 | * supported address families; if not we gather info on |
869 | * the specified address family. |
870 | */ |
871 | if ((((family != 0) && |
872 | (curp->ifa_addr->sa_family != family))) || |
873 | (curp->ifa_flags & IFF_LOOPBACK)) { |
874 | curp = curp->ifa_next; |
875 | continue; |
876 | } |
877 | if ((curp->ifa_addr->sa_family != AF_INET) && |
878 | (curp->ifa_addr->sa_family != AF_INET6)) { |
879 | curp = curp->ifa_next; |
880 | continue; |
881 | } |
882 | |
883 | if (op == KVP_OP_GET_IP_INFO) { |
884 | /* |
885 | * Gather info other than the IP address. |
886 | * IP address info will be gathered later. |
887 | */ |
888 | if (curp->ifa_addr->sa_family == AF_INET) { |
889 | ip_buffer->addr_family |= ADDR_FAMILY_IPV4; |
890 | /* |
891 | * Get subnet info. |
892 | */ |
893 | error = kvp_process_ip_address( |
894 | addrp: curp->ifa_netmask, |
895 | AF_INET, |
896 | buffer: (char *) |
897 | ip_buffer->sub_net, |
898 | length, |
899 | offset: &sn_offset); |
900 | if (error) |
901 | goto gather_ipaddr; |
902 | } else { |
903 | ip_buffer->addr_family |= ADDR_FAMILY_IPV6; |
904 | |
905 | /* |
906 | * Get subnet info in CIDR format. |
907 | */ |
908 | weight = 0; |
909 | sn_str = (char *)ip_buffer->sub_net; |
910 | addr6 = (struct sockaddr_in6 *) |
911 | curp->ifa_netmask; |
912 | w = addr6->sin6_addr.s6_addr32; |
913 | |
914 | for (i = 0; i < 4; i++) |
915 | weight += hweight32(&w[i]); |
916 | |
917 | sprintf(buf: cidr_mask, fmt: "/%d" , weight); |
918 | if (length < sn_offset + strlen(cidr_mask) + 1) |
919 | goto gather_ipaddr; |
920 | |
921 | if (sn_offset == 0) |
922 | strcpy(p: sn_str, q: cidr_mask); |
923 | else { |
924 | strcat(p: (char *)ip_buffer->sub_net, q: ";" ); |
925 | strcat(p: sn_str, q: cidr_mask); |
926 | } |
927 | sn_offset += strlen(sn_str) + 1; |
928 | } |
929 | |
930 | /* |
931 | * Collect other ip related configuration info. |
932 | */ |
933 | |
934 | kvp_get_ipconfig_info(if_name, buffer: ip_buffer); |
935 | } |
936 | |
937 | gather_ipaddr: |
938 | error = kvp_process_ip_address(addrp: curp->ifa_addr, |
939 | family: curp->ifa_addr->sa_family, |
940 | buffer, |
941 | length, offset: &offset); |
942 | if (error) |
943 | goto getaddr_done; |
944 | |
945 | curp = curp->ifa_next; |
946 | } |
947 | |
948 | getaddr_done: |
949 | freeifaddrs(ifap); |
950 | return error; |
951 | } |
952 | |
953 | /* |
954 | * Retrieve the IP given the MAC address. |
955 | */ |
956 | static int kvp_mac_to_ip(struct hv_kvp_ipaddr_value *kvp_ip_val) |
957 | { |
958 | char *mac = (char *)kvp_ip_val->adapter_id; |
959 | DIR *dir; |
960 | struct dirent *entry; |
961 | FILE *file; |
962 | char *p, *x; |
963 | char *if_name = NULL; |
964 | char buf[256]; |
965 | char dev_id[PATH_MAX]; |
966 | unsigned int i; |
967 | int error = HV_E_FAIL; |
968 | |
969 | dir = opendir(KVP_NET_DIR); |
970 | if (dir == NULL) |
971 | return HV_E_FAIL; |
972 | |
973 | while ((entry = readdir(dir)) != NULL) { |
974 | /* |
975 | * Set the state for the next pass. |
976 | */ |
977 | snprintf(buf: dev_id, size: sizeof(dev_id), fmt: "%s%s/address" , KVP_NET_DIR, |
978 | entry->d_name); |
979 | |
980 | file = fopen(dev_id, "r" ); |
981 | if (file == NULL) |
982 | continue; |
983 | |
984 | p = fgets(buf, sizeof(buf), file); |
985 | fclose(file); |
986 | if (!p) |
987 | continue; |
988 | |
989 | x = strchr(p, '\n'); |
990 | if (x) |
991 | *x = '\0'; |
992 | |
993 | for (i = 0; i < strlen(p); i++) |
994 | p[i] = toupper(p[i]); |
995 | |
996 | if (strcmp(p, mac)) |
997 | continue; |
998 | |
999 | /* |
1000 | * Found the MAC match. |
1001 | * A NIC (e.g. VF) matching the MAC, but without IP, is skipped. |
1002 | */ |
1003 | if_name = entry->d_name; |
1004 | if (!if_name) |
1005 | continue; |
1006 | |
1007 | error = kvp_get_ip_info(family: 0, if_name, op: KVP_OP_GET_IP_INFO, |
1008 | out_buffer: kvp_ip_val, MAX_IP_ADDR_SIZE * 2); |
1009 | |
1010 | if (!error && strlen((char *)kvp_ip_val->ip_addr)) |
1011 | break; |
1012 | } |
1013 | |
1014 | closedir(dir); |
1015 | return error; |
1016 | } |
1017 | |
1018 | static int expand_ipv6(char *addr, int type) |
1019 | { |
1020 | int ret; |
1021 | struct in6_addr v6_addr; |
1022 | |
1023 | ret = inet_pton(AF_INET6, addr, &v6_addr); |
1024 | |
1025 | if (ret != 1) { |
1026 | if (type == NETMASK) |
1027 | return 1; |
1028 | return 0; |
1029 | } |
1030 | |
1031 | sprintf(buf: addr, fmt: "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:" |
1032 | "%02x%02x:%02x%02x:%02x%02x" , |
1033 | (int)v6_addr.s6_addr[0], (int)v6_addr.s6_addr[1], |
1034 | (int)v6_addr.s6_addr[2], (int)v6_addr.s6_addr[3], |
1035 | (int)v6_addr.s6_addr[4], (int)v6_addr.s6_addr[5], |
1036 | (int)v6_addr.s6_addr[6], (int)v6_addr.s6_addr[7], |
1037 | (int)v6_addr.s6_addr[8], (int)v6_addr.s6_addr[9], |
1038 | (int)v6_addr.s6_addr[10], (int)v6_addr.s6_addr[11], |
1039 | (int)v6_addr.s6_addr[12], (int)v6_addr.s6_addr[13], |
1040 | (int)v6_addr.s6_addr[14], (int)v6_addr.s6_addr[15]); |
1041 | |
1042 | return 1; |
1043 | |
1044 | } |
1045 | |
1046 | static int is_ipv4(char *addr) |
1047 | { |
1048 | int ret; |
1049 | struct in_addr ipv4_addr; |
1050 | |
1051 | ret = inet_pton(AF_INET, addr, &ipv4_addr); |
1052 | |
1053 | if (ret == 1) |
1054 | return 1; |
1055 | return 0; |
1056 | } |
1057 | |
1058 | static int parse_ip_val_buffer(char *in_buf, int *offset, |
1059 | char *out_buf, int out_len) |
1060 | { |
1061 | char *x; |
1062 | char *start; |
1063 | |
1064 | /* |
1065 | * in_buf has sequence of characters that are separated by |
1066 | * the character ';'. The last sequence does not have the |
1067 | * terminating ";" character. |
1068 | */ |
1069 | start = in_buf + *offset; |
1070 | |
1071 | x = strchr(start, ';'); |
1072 | if (x) |
1073 | *x = 0; |
1074 | else |
1075 | x = start + strlen(start); |
1076 | |
1077 | if (strlen(start) != 0) { |
1078 | int i = 0; |
1079 | /* |
1080 | * Get rid of leading spaces. |
1081 | */ |
1082 | while (start[i] == ' ') |
1083 | i++; |
1084 | |
1085 | if ((x - start) <= out_len) { |
1086 | strcpy(p: out_buf, q: (start + i)); |
1087 | *offset += (x - start) + 1; |
1088 | return 1; |
1089 | } |
1090 | } |
1091 | return 0; |
1092 | } |
1093 | |
1094 | static int kvp_write_file(FILE *f, char *s1, char *s2, char *s3) |
1095 | { |
1096 | int ret; |
1097 | |
1098 | ret = fprintf(f, "%s%s%s%s\n" , s1, s2, "=" , s3); |
1099 | |
1100 | if (ret < 0) |
1101 | return HV_E_FAIL; |
1102 | |
1103 | return 0; |
1104 | } |
1105 | |
1106 | |
1107 | static int process_ip_string(FILE *f, char *ip_string, int type) |
1108 | { |
1109 | int error = 0; |
1110 | char addr[INET6_ADDRSTRLEN]; |
1111 | int i = 0; |
1112 | int j = 0; |
1113 | char str[256]; |
1114 | char sub_str[13]; |
1115 | int offset = 0; |
1116 | |
1117 | memset(addr, 0, sizeof(addr)); |
1118 | |
1119 | while (parse_ip_val_buffer(in_buf: ip_string, offset: &offset, out_buf: addr, |
1120 | out_len: (MAX_IP_ADDR_SIZE * 2))) { |
1121 | |
1122 | sub_str[0] = 0; |
1123 | if (is_ipv4(addr: addr)) { |
1124 | switch (type) { |
1125 | case IPADDR: |
1126 | snprintf(buf: str, size: sizeof(str), fmt: "%s" , "IPADDR" ); |
1127 | break; |
1128 | case NETMASK: |
1129 | snprintf(buf: str, size: sizeof(str), fmt: "%s" , "NETMASK" ); |
1130 | break; |
1131 | case GATEWAY: |
1132 | snprintf(buf: str, size: sizeof(str), fmt: "%s" , "GATEWAY" ); |
1133 | break; |
1134 | case DNS: |
1135 | snprintf(buf: str, size: sizeof(str), fmt: "%s" , "DNS" ); |
1136 | break; |
1137 | } |
1138 | |
1139 | if (type == DNS) { |
1140 | snprintf(buf: sub_str, size: sizeof(sub_str), fmt: "%d" , ++i); |
1141 | } else if (type == GATEWAY && i == 0) { |
1142 | ++i; |
1143 | } else { |
1144 | snprintf(buf: sub_str, size: sizeof(sub_str), fmt: "%d" , i++); |
1145 | } |
1146 | |
1147 | |
1148 | } else if (expand_ipv6(addr: addr, type)) { |
1149 | switch (type) { |
1150 | case IPADDR: |
1151 | snprintf(buf: str, size: sizeof(str), fmt: "%s" , "IPV6ADDR" ); |
1152 | break; |
1153 | case NETMASK: |
1154 | snprintf(buf: str, size: sizeof(str), fmt: "%s" , "IPV6NETMASK" ); |
1155 | break; |
1156 | case GATEWAY: |
1157 | snprintf(buf: str, size: sizeof(str), fmt: "%s" , |
1158 | "IPV6_DEFAULTGW" ); |
1159 | break; |
1160 | case DNS: |
1161 | snprintf(buf: str, size: sizeof(str), fmt: "%s" , "DNS" ); |
1162 | break; |
1163 | } |
1164 | |
1165 | if (type == DNS) { |
1166 | snprintf(buf: sub_str, size: sizeof(sub_str), fmt: "%d" , ++i); |
1167 | } else if (j == 0) { |
1168 | ++j; |
1169 | } else { |
1170 | snprintf(buf: sub_str, size: sizeof(sub_str), fmt: "_%d" , j++); |
1171 | } |
1172 | } else { |
1173 | return HV_INVALIDARG; |
1174 | } |
1175 | |
1176 | error = kvp_write_file(f, str, sub_str, addr); |
1177 | if (error) |
1178 | return error; |
1179 | memset(addr, 0, sizeof(addr)); |
1180 | } |
1181 | |
1182 | return 0; |
1183 | } |
1184 | |
1185 | int ip_version_check(const char *input_addr) |
1186 | { |
1187 | struct in6_addr addr; |
1188 | |
1189 | if (inet_pton(AF_INET, input_addr, &addr)) |
1190 | return IPV4; |
1191 | else if (inet_pton(AF_INET6, input_addr, &addr)) |
1192 | return IPV6; |
1193 | |
1194 | return -EINVAL; |
1195 | } |
1196 | |
1197 | /* |
1198 | * Only IPv4 subnet strings needs to be converted to plen |
1199 | * For IPv6 the subnet is already privided in plen format |
1200 | */ |
1201 | static int kvp_subnet_to_plen(char *subnet_addr_str) |
1202 | { |
1203 | int plen = 0; |
1204 | struct in_addr subnet_addr4; |
1205 | |
1206 | /* |
1207 | * Convert subnet address to binary representation |
1208 | */ |
1209 | if (inet_pton(AF_INET, subnet_addr_str, &subnet_addr4) == 1) { |
1210 | uint32_t subnet_mask = ntohl(subnet_addr4.s_addr); |
1211 | |
1212 | while (subnet_mask & 0x80000000) { |
1213 | plen++; |
1214 | subnet_mask <<= 1; |
1215 | } |
1216 | } else { |
1217 | return -1; |
1218 | } |
1219 | |
1220 | return plen; |
1221 | } |
1222 | |
1223 | static int process_dns_gateway_nm(FILE *f, char *ip_string, int type, |
1224 | int ip_sec) |
1225 | { |
1226 | char addr[INET6_ADDRSTRLEN], *output_str; |
1227 | int ip_offset = 0, error = 0, ip_ver; |
1228 | char *param_name; |
1229 | |
1230 | if (type == DNS) |
1231 | param_name = "dns" ; |
1232 | else if (type == GATEWAY) |
1233 | param_name = "gateway" ; |
1234 | else |
1235 | return -EINVAL; |
1236 | |
1237 | output_str = (char *)calloc(OUTSTR_BUF_SIZE, sizeof(char)); |
1238 | if (!output_str) |
1239 | return -ENOMEM; |
1240 | |
1241 | while (1) { |
1242 | memset(addr, 0, sizeof(addr)); |
1243 | |
1244 | if (!parse_ip_val_buffer(in_buf: ip_string, offset: &ip_offset, out_buf: addr, |
1245 | out_len: (MAX_IP_ADDR_SIZE * 2))) |
1246 | break; |
1247 | |
1248 | ip_ver = ip_version_check(input_addr: addr); |
1249 | if (ip_ver < 0) |
1250 | continue; |
1251 | |
1252 | if ((ip_ver == IPV4 && ip_sec == IPV4) || |
1253 | (ip_ver == IPV6 && ip_sec == IPV6)) { |
1254 | /* |
1255 | * do a bound check to avoid out-of bound writes |
1256 | */ |
1257 | if ((OUTSTR_BUF_SIZE - strlen(output_str)) > |
1258 | (strlen(addr) + 1)) { |
1259 | strncat(output_str, addr, |
1260 | OUTSTR_BUF_SIZE - |
1261 | strlen(output_str) - 1); |
1262 | strncat(output_str, "," , |
1263 | OUTSTR_BUF_SIZE - |
1264 | strlen(output_str) - 1); |
1265 | } |
1266 | } else { |
1267 | continue; |
1268 | } |
1269 | } |
1270 | |
1271 | if (strlen(output_str)) { |
1272 | /* |
1273 | * This is to get rid of that extra comma character |
1274 | * in the end of the string |
1275 | */ |
1276 | output_str[strlen(output_str) - 1] = '\0'; |
1277 | error = fprintf(f, "%s=%s\n" , param_name, output_str); |
1278 | } |
1279 | |
1280 | free(output_str); |
1281 | return error; |
1282 | } |
1283 | |
1284 | static int process_ip_string_nm(FILE *f, char *ip_string, char *subnet, |
1285 | int ip_sec) |
1286 | { |
1287 | char addr[INET6_ADDRSTRLEN]; |
1288 | char subnet_addr[INET6_ADDRSTRLEN]; |
1289 | int error = 0, i = 0; |
1290 | int ip_offset = 0, subnet_offset = 0; |
1291 | int plen, ip_ver; |
1292 | |
1293 | memset(addr, 0, sizeof(addr)); |
1294 | memset(subnet_addr, 0, sizeof(subnet_addr)); |
1295 | |
1296 | while (parse_ip_val_buffer(in_buf: ip_string, offset: &ip_offset, out_buf: addr, |
1297 | out_len: (MAX_IP_ADDR_SIZE * 2)) && |
1298 | parse_ip_val_buffer(in_buf: subnet, |
1299 | offset: &subnet_offset, |
1300 | out_buf: subnet_addr, |
1301 | out_len: (MAX_IP_ADDR_SIZE * |
1302 | 2))) { |
1303 | ip_ver = ip_version_check(input_addr: addr); |
1304 | if (ip_ver < 0) |
1305 | continue; |
1306 | |
1307 | if (ip_ver == IPV4 && ip_sec == IPV4) |
1308 | plen = kvp_subnet_to_plen(subnet_addr_str: (char *)subnet_addr); |
1309 | else if (ip_ver == IPV6 && ip_sec == IPV6) |
1310 | plen = atoi(subnet_addr); |
1311 | else |
1312 | continue; |
1313 | |
1314 | if (plen < 0) |
1315 | return plen; |
1316 | |
1317 | error = fprintf(f, "address%d=%s/%d\n" , ++i, (char *)addr, |
1318 | plen); |
1319 | if (error < 0) |
1320 | return error; |
1321 | |
1322 | memset(addr, 0, sizeof(addr)); |
1323 | memset(subnet_addr, 0, sizeof(subnet_addr)); |
1324 | } |
1325 | |
1326 | return error; |
1327 | } |
1328 | |
1329 | static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) |
1330 | { |
1331 | int error = 0, ip_ver; |
1332 | char if_filename[PATH_MAX]; |
1333 | char nm_filename[PATH_MAX]; |
1334 | FILE *ifcfg_file, *nmfile; |
1335 | char cmd[PATH_MAX]; |
1336 | char *mac_addr; |
1337 | int str_len; |
1338 | |
1339 | /* |
1340 | * Set the configuration for the specified interface with |
1341 | * the information provided. Since there is no standard |
1342 | * way to configure an interface, we will have an external |
1343 | * script that does the job of configuring the interface and |
1344 | * flushing the configuration. |
1345 | * |
1346 | * The parameters passed to this external script are: |
1347 | * 1. A configuration file that has the specified configuration. |
1348 | * |
1349 | * We will embed the name of the interface in the configuration |
1350 | * file: ifcfg-ethx (where ethx is the interface name). |
1351 | * |
1352 | * The information provided here may be more than what is needed |
1353 | * in a given distro to configure the interface and so are free |
1354 | * ignore information that may not be relevant. |
1355 | * |
1356 | * Here is the ifcfg format of the ip configuration file: |
1357 | * |
1358 | * HWADDR=macaddr |
1359 | * DEVICE=interface name |
1360 | * BOOTPROTO=<protocol> (where <protocol> is "dhcp" if DHCP is configured |
1361 | * or "none" if no boot-time protocol should be used) |
1362 | * |
1363 | * IPADDR0=ipaddr1 |
1364 | * IPADDR1=ipaddr2 |
1365 | * IPADDRx=ipaddry (where y = x + 1) |
1366 | * |
1367 | * NETMASK0=netmask1 |
1368 | * NETMASKx=netmasky (where y = x + 1) |
1369 | * |
1370 | * GATEWAY=ipaddr1 |
1371 | * GATEWAYx=ipaddry (where y = x + 1) |
1372 | * |
1373 | * DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc) |
1374 | * |
1375 | * IPV6 addresses will be tagged as IPV6ADDR, IPV6 gateway will be |
1376 | * tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as |
1377 | * IPV6NETMASK. |
1378 | * |
1379 | * Here is the keyfile format of the ip configuration file: |
1380 | * |
1381 | * [ethernet] |
1382 | * mac-address=macaddr |
1383 | * [connection] |
1384 | * interface-name=interface name |
1385 | * |
1386 | * [ipv4] |
1387 | * method=<protocol> (where <protocol> is "auto" if DHCP is configured |
1388 | * or "manual" if no boot-time protocol should be used) |
1389 | * |
1390 | * address1=ipaddr1/plen |
1391 | * address2=ipaddr2/plen |
1392 | * |
1393 | * gateway=gateway1;gateway2 |
1394 | * |
1395 | * dns=dns1;dns2 |
1396 | * |
1397 | * [ipv6] |
1398 | * address1=ipaddr1/plen |
1399 | * address2=ipaddr2/plen |
1400 | * |
1401 | * gateway=gateway1;gateway2 |
1402 | * |
1403 | * dns=dns1;dns2 |
1404 | * |
1405 | * The host can specify multiple ipv4 and ipv6 addresses to be |
1406 | * configured for the interface. Furthermore, the configuration |
1407 | * needs to be persistent. A subsequent GET call on the interface |
1408 | * is expected to return the configuration that is set via the SET |
1409 | * call. |
1410 | */ |
1411 | |
1412 | /* |
1413 | * We are populating both ifcfg and nmconnection files |
1414 | */ |
1415 | snprintf(buf: if_filename, size: sizeof(if_filename), fmt: "%s%s%s" , KVP_CONFIG_LOC, |
1416 | "/ifcfg-" , if_name); |
1417 | |
1418 | ifcfg_file = fopen(if_filename, "w" ); |
1419 | |
1420 | if (!ifcfg_file) { |
1421 | syslog(LOG_ERR, "Failed to open config file; error: %d %s" , |
1422 | errno, strerror(errno)); |
1423 | return HV_E_FAIL; |
1424 | } |
1425 | |
1426 | snprintf(buf: nm_filename, size: sizeof(nm_filename), fmt: "%s%s%s%s" , KVP_CONFIG_LOC, |
1427 | "/" , if_name, ".nmconnection" ); |
1428 | |
1429 | nmfile = fopen(nm_filename, "w" ); |
1430 | |
1431 | if (!nmfile) { |
1432 | syslog(LOG_ERR, "Failed to open config file; error: %d %s" , |
1433 | errno, strerror(errno)); |
1434 | fclose(ifcfg_file); |
1435 | return HV_E_FAIL; |
1436 | } |
1437 | |
1438 | /* |
1439 | * First write out the MAC address. |
1440 | */ |
1441 | |
1442 | mac_addr = kvp_if_name_to_mac(if_name); |
1443 | if (mac_addr == NULL) { |
1444 | error = HV_E_FAIL; |
1445 | goto setval_error; |
1446 | } |
1447 | |
1448 | error = kvp_write_file(ifcfg_file, "HWADDR" , "" , mac_addr); |
1449 | if (error < 0) |
1450 | goto setmac_error; |
1451 | |
1452 | error = kvp_write_file(ifcfg_file, "DEVICE" , "" , if_name); |
1453 | if (error < 0) |
1454 | goto setmac_error; |
1455 | |
1456 | error = fprintf(nmfile, "\n[connection]\n" ); |
1457 | if (error < 0) |
1458 | goto setmac_error; |
1459 | |
1460 | error = kvp_write_file(nmfile, "interface-name" , "" , if_name); |
1461 | if (error) |
1462 | goto setmac_error; |
1463 | |
1464 | error = fprintf(nmfile, "\n[ethernet]\n" ); |
1465 | if (error < 0) |
1466 | goto setmac_error; |
1467 | |
1468 | error = kvp_write_file(nmfile, "mac-address" , "" , mac_addr); |
1469 | if (error) |
1470 | goto setmac_error; |
1471 | |
1472 | free(mac_addr); |
1473 | |
1474 | /* |
1475 | * The dhcp_enabled flag is only for IPv4. In the case the host only |
1476 | * injects an IPv6 address, the flag is true, but we still need to |
1477 | * proceed to parse and pass the IPv6 information to the |
1478 | * disto-specific script hv_set_ifconfig. |
1479 | */ |
1480 | |
1481 | /* |
1482 | * First populate the ifcfg file format |
1483 | */ |
1484 | if (new_val->dhcp_enabled) { |
1485 | error = kvp_write_file(ifcfg_file, "BOOTPROTO" , "" , "dhcp" ); |
1486 | if (error) |
1487 | goto setval_error; |
1488 | } else { |
1489 | error = kvp_write_file(ifcfg_file, "BOOTPROTO" , "" , "none" ); |
1490 | if (error) |
1491 | goto setval_error; |
1492 | } |
1493 | |
1494 | error = process_ip_string(ifcfg_file, (char *)new_val->ip_addr, |
1495 | IPADDR); |
1496 | if (error) |
1497 | goto setval_error; |
1498 | |
1499 | error = process_ip_string(ifcfg_file, (char *)new_val->sub_net, |
1500 | NETMASK); |
1501 | if (error) |
1502 | goto setval_error; |
1503 | |
1504 | error = process_ip_string(ifcfg_file, (char *)new_val->gate_way, |
1505 | GATEWAY); |
1506 | if (error) |
1507 | goto setval_error; |
1508 | |
1509 | error = process_ip_string(ifcfg_file, (char *)new_val->dns_addr, DNS); |
1510 | if (error) |
1511 | goto setval_error; |
1512 | |
1513 | /* |
1514 | * Now we populate the keyfile format |
1515 | * |
1516 | * The keyfile format expects the IPv6 and IPv4 configuration in |
1517 | * different sections. Therefore we iterate through the list twice, |
1518 | * once to populate the IPv4 section and the next time for IPv6 |
1519 | */ |
1520 | ip_ver = IPV4; |
1521 | do { |
1522 | if (ip_ver == IPV4) { |
1523 | error = fprintf(nmfile, "\n[ipv4]\n" ); |
1524 | if (error < 0) |
1525 | goto setval_error; |
1526 | } else { |
1527 | error = fprintf(nmfile, "\n[ipv6]\n" ); |
1528 | if (error < 0) |
1529 | goto setval_error; |
1530 | } |
1531 | |
1532 | /* |
1533 | * Write the configuration for ipaddress, netmask, gateway and |
1534 | * name services |
1535 | */ |
1536 | error = process_ip_string_nm(nmfile, (char *)new_val->ip_addr, |
1537 | (char *)new_val->sub_net, |
1538 | ip_ver); |
1539 | if (error < 0) |
1540 | goto setval_error; |
1541 | |
1542 | /* |
1543 | * As dhcp_enabled is only valid for ipv4, we do not set dhcp |
1544 | * methods for ipv6 based on dhcp_enabled flag. |
1545 | * |
1546 | * For ipv4, set method to manual only when dhcp_enabled is |
1547 | * false and specific ipv4 addresses are configured. If neither |
1548 | * dhcp_enabled is true and no ipv4 addresses are configured, |
1549 | * set method to 'disabled'. |
1550 | * |
1551 | * For ipv6, set method to manual when we configure ipv6 |
1552 | * addresses. Otherwise set method to 'auto' so that SLAAC from |
1553 | * RA may be used. |
1554 | */ |
1555 | if (ip_ver == IPV4) { |
1556 | if (new_val->dhcp_enabled) { |
1557 | error = kvp_write_file(nmfile, "method" , "" , |
1558 | "auto" ); |
1559 | if (error < 0) |
1560 | goto setval_error; |
1561 | } else if (error) { |
1562 | error = kvp_write_file(nmfile, "method" , "" , |
1563 | "manual" ); |
1564 | if (error < 0) |
1565 | goto setval_error; |
1566 | } else { |
1567 | error = kvp_write_file(nmfile, "method" , "" , |
1568 | "disabled" ); |
1569 | if (error < 0) |
1570 | goto setval_error; |
1571 | } |
1572 | } else if (ip_ver == IPV6) { |
1573 | if (error) { |
1574 | error = kvp_write_file(nmfile, "method" , "" , |
1575 | "manual" ); |
1576 | if (error < 0) |
1577 | goto setval_error; |
1578 | } else { |
1579 | error = kvp_write_file(nmfile, "method" , "" , |
1580 | "auto" ); |
1581 | if (error < 0) |
1582 | goto setval_error; |
1583 | } |
1584 | } |
1585 | |
1586 | error = process_dns_gateway_nm(nmfile, |
1587 | (char *)new_val->gate_way, |
1588 | GATEWAY, ip_ver); |
1589 | if (error < 0) |
1590 | goto setval_error; |
1591 | |
1592 | error = process_dns_gateway_nm(nmfile, |
1593 | (char *)new_val->dns_addr, DNS, |
1594 | ip_ver); |
1595 | if (error < 0) |
1596 | goto setval_error; |
1597 | |
1598 | ip_ver++; |
1599 | } while (ip_ver < IP_TYPE_MAX); |
1600 | |
1601 | fclose(nmfile); |
1602 | fclose(ifcfg_file); |
1603 | |
1604 | /* |
1605 | * Now that we have populated the configuration file, |
1606 | * invoke the external script to do its magic. |
1607 | */ |
1608 | |
1609 | str_len = snprintf(buf: cmd, size: sizeof(cmd), KVP_SCRIPTS_PATH "%s %s %s" , |
1610 | "hv_set_ifconfig" , if_filename, nm_filename); |
1611 | /* |
1612 | * This is a little overcautious, but it's necessary to suppress some |
1613 | * false warnings from gcc 8.0.1. |
1614 | */ |
1615 | if (str_len <= 0 || (unsigned int)str_len >= sizeof(cmd)) { |
1616 | syslog(LOG_ERR, "Cmd '%s' (len=%d) may be too long" , |
1617 | cmd, str_len); |
1618 | return HV_E_FAIL; |
1619 | } |
1620 | |
1621 | if (system(cmd)) { |
1622 | syslog(LOG_ERR, "Failed to execute cmd '%s'; error: %d %s" , |
1623 | cmd, errno, strerror(errno)); |
1624 | return HV_E_FAIL; |
1625 | } |
1626 | return 0; |
1627 | setmac_error: |
1628 | free(mac_addr); |
1629 | setval_error: |
1630 | syslog(LOG_ERR, "Failed to write config file" ); |
1631 | fclose(ifcfg_file); |
1632 | fclose(nmfile); |
1633 | return error; |
1634 | } |
1635 | |
1636 | |
1637 | static void |
1638 | kvp_get_domain_name(char *buffer, int length) |
1639 | { |
1640 | struct addrinfo hints, *info ; |
1641 | int error = 0; |
1642 | |
1643 | gethostname(buffer, length); |
1644 | memset(&hints, 0, sizeof(hints)); |
1645 | hints.ai_family = AF_INET; /*Get only ipv4 addrinfo. */ |
1646 | hints.ai_socktype = SOCK_STREAM; |
1647 | hints.ai_flags = AI_CANONNAME; |
1648 | |
1649 | error = getaddrinfo(buffer, NULL, &hints, &info); |
1650 | if (error != 0) { |
1651 | snprintf(buf: buffer, size: length, fmt: "getaddrinfo failed: 0x%x %s" , |
1652 | error, gai_strerror(error)); |
1653 | return; |
1654 | } |
1655 | snprintf(buf: buffer, size: length, fmt: "%s" , info->ai_canonname); |
1656 | freeaddrinfo(info); |
1657 | } |
1658 | |
1659 | void print_usage(char *argv[]) |
1660 | { |
1661 | fprintf(stderr, "Usage: %s [options]\n" |
1662 | "Options are:\n" |
1663 | " -n, --no-daemon stay in foreground, don't daemonize\n" |
1664 | " -h, --help print this help\n" , argv[0]); |
1665 | } |
1666 | |
1667 | int main(int argc, char *argv[]) |
1668 | { |
1669 | int kvp_fd = -1, len; |
1670 | int error; |
1671 | struct pollfd pfd; |
1672 | char *p; |
1673 | struct hv_kvp_msg hv_msg[1]; |
1674 | char *key_value; |
1675 | char *key_name; |
1676 | int op; |
1677 | int pool; |
1678 | char *if_name; |
1679 | struct hv_kvp_ipaddr_value *kvp_ip_val; |
1680 | int daemonize = 1, long_index = 0, opt; |
1681 | |
1682 | static struct option long_options[] = { |
1683 | {"help" , no_argument, 0, 'h' }, |
1684 | {"no-daemon" , no_argument, 0, 'n' }, |
1685 | {0, 0, 0, 0 } |
1686 | }; |
1687 | |
1688 | while ((opt = getopt_long(argc, argv, "hn" , long_options, |
1689 | &long_index)) != -1) { |
1690 | switch (opt) { |
1691 | case 'n': |
1692 | daemonize = 0; |
1693 | break; |
1694 | case 'h': |
1695 | print_usage(argv); |
1696 | exit(0); |
1697 | default: |
1698 | print_usage(argv); |
1699 | exit(EXIT_FAILURE); |
1700 | } |
1701 | } |
1702 | |
1703 | if (daemonize && daemon(1, 0)) |
1704 | return 1; |
1705 | |
1706 | openlog("KVP" , 0, LOG_USER); |
1707 | syslog(LOG_INFO, "KVP starting; pid is:%d" , getpid()); |
1708 | |
1709 | /* |
1710 | * Retrieve OS release information. |
1711 | */ |
1712 | kvp_get_os_info(); |
1713 | /* |
1714 | * Cache Fully Qualified Domain Name because getaddrinfo takes an |
1715 | * unpredictable amount of time to finish. |
1716 | */ |
1717 | kvp_get_domain_name(buffer: full_domain_name, length: sizeof(full_domain_name)); |
1718 | |
1719 | if (kvp_file_init()) { |
1720 | syslog(LOG_ERR, "Failed to initialize the pools" ); |
1721 | exit(EXIT_FAILURE); |
1722 | } |
1723 | |
1724 | reopen_kvp_fd: |
1725 | if (kvp_fd != -1) |
1726 | close(kvp_fd); |
1727 | in_hand_shake = 1; |
1728 | kvp_fd = open("/dev/vmbus/hv_kvp" , O_RDWR | O_CLOEXEC); |
1729 | |
1730 | if (kvp_fd < 0) { |
1731 | syslog(LOG_ERR, "open /dev/vmbus/hv_kvp failed; error: %d %s" , |
1732 | errno, strerror(errno)); |
1733 | exit(EXIT_FAILURE); |
1734 | } |
1735 | |
1736 | /* |
1737 | * Register ourselves with the kernel. |
1738 | */ |
1739 | hv_msg->kvp_hdr.operation = KVP_OP_REGISTER1; |
1740 | len = write(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg)); |
1741 | if (len != sizeof(struct hv_kvp_msg)) { |
1742 | syslog(LOG_ERR, "registration to kernel failed; error: %d %s" , |
1743 | errno, strerror(errno)); |
1744 | close(kvp_fd); |
1745 | exit(EXIT_FAILURE); |
1746 | } |
1747 | |
1748 | pfd.fd = kvp_fd; |
1749 | |
1750 | while (1) { |
1751 | pfd.events = POLLIN; |
1752 | pfd.revents = 0; |
1753 | |
1754 | if (poll(&pfd, 1, -1) < 0) { |
1755 | syslog(LOG_ERR, "poll failed; error: %d %s" , errno, strerror(errno)); |
1756 | if (errno == EINVAL) { |
1757 | close(kvp_fd); |
1758 | exit(EXIT_FAILURE); |
1759 | } |
1760 | else |
1761 | continue; |
1762 | } |
1763 | |
1764 | len = read(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg)); |
1765 | |
1766 | if (len != sizeof(struct hv_kvp_msg)) { |
1767 | syslog(LOG_ERR, "read failed; error:%d %s" , |
1768 | errno, strerror(errno)); |
1769 | goto reopen_kvp_fd; |
1770 | } |
1771 | |
1772 | /* |
1773 | * We will use the KVP header information to pass back |
1774 | * the error from this daemon. So, first copy the state |
1775 | * and set the error code to success. |
1776 | */ |
1777 | op = hv_msg->kvp_hdr.operation; |
1778 | pool = hv_msg->kvp_hdr.pool; |
1779 | hv_msg->error = HV_S_OK; |
1780 | |
1781 | if ((in_hand_shake) && (op == KVP_OP_REGISTER1)) { |
1782 | /* |
1783 | * Driver is registering with us; stash away the version |
1784 | * information. |
1785 | */ |
1786 | in_hand_shake = 0; |
1787 | p = (char *)hv_msg->body.kvp_register.version; |
1788 | lic_version = malloc(strlen(p) + 1); |
1789 | if (lic_version) { |
1790 | strcpy(p: lic_version, q: p); |
1791 | syslog(LOG_INFO, "KVP LIC Version: %s" , |
1792 | lic_version); |
1793 | } else { |
1794 | syslog(LOG_ERR, "malloc failed" ); |
1795 | } |
1796 | continue; |
1797 | } |
1798 | |
1799 | switch (op) { |
1800 | case KVP_OP_GET_IP_INFO: |
1801 | kvp_ip_val = &hv_msg->body.kvp_ip_val; |
1802 | |
1803 | error = kvp_mac_to_ip(kvp_ip_val); |
1804 | |
1805 | if (error) |
1806 | hv_msg->error = error; |
1807 | |
1808 | break; |
1809 | |
1810 | case KVP_OP_SET_IP_INFO: |
1811 | kvp_ip_val = &hv_msg->body.kvp_ip_val; |
1812 | if_name = kvp_get_if_name( |
1813 | guid: (char *)kvp_ip_val->adapter_id); |
1814 | if (if_name == NULL) { |
1815 | /* |
1816 | * We could not map the guid to an |
1817 | * interface name; return error. |
1818 | */ |
1819 | hv_msg->error = HV_GUID_NOTFOUND; |
1820 | break; |
1821 | } |
1822 | error = kvp_set_ip_info(if_name, new_val: kvp_ip_val); |
1823 | if (error) |
1824 | hv_msg->error = error; |
1825 | |
1826 | free(if_name); |
1827 | break; |
1828 | |
1829 | case KVP_OP_SET: |
1830 | if (kvp_key_add_or_modify(pool, |
1831 | key: hv_msg->body.kvp_set.data.key, |
1832 | key_size: hv_msg->body.kvp_set.data.key_size, |
1833 | value: hv_msg->body.kvp_set.data.value, |
1834 | value_size: hv_msg->body.kvp_set.data.value_size)) |
1835 | hv_msg->error = HV_S_CONT; |
1836 | break; |
1837 | |
1838 | case KVP_OP_GET: |
1839 | if (kvp_get_value(pool, |
1840 | key: hv_msg->body.kvp_set.data.key, |
1841 | key_size: hv_msg->body.kvp_set.data.key_size, |
1842 | value: hv_msg->body.kvp_set.data.value, |
1843 | value_size: hv_msg->body.kvp_set.data.value_size)) |
1844 | hv_msg->error = HV_S_CONT; |
1845 | break; |
1846 | |
1847 | case KVP_OP_DELETE: |
1848 | if (kvp_key_delete(pool, |
1849 | key: hv_msg->body.kvp_delete.key, |
1850 | key_size: hv_msg->body.kvp_delete.key_size)) |
1851 | hv_msg->error = HV_S_CONT; |
1852 | break; |
1853 | |
1854 | default: |
1855 | break; |
1856 | } |
1857 | |
1858 | if (op != KVP_OP_ENUMERATE) |
1859 | goto kvp_done; |
1860 | |
1861 | /* |
1862 | * If the pool is KVP_POOL_AUTO, dynamically generate |
1863 | * both the key and the value; if not read from the |
1864 | * appropriate pool. |
1865 | */ |
1866 | if (pool != KVP_POOL_AUTO) { |
1867 | if (kvp_pool_enumerate(pool, |
1868 | index: hv_msg->body.kvp_enum_data.index, |
1869 | key: hv_msg->body.kvp_enum_data.data.key, |
1870 | HV_KVP_EXCHANGE_MAX_KEY_SIZE, |
1871 | value: hv_msg->body.kvp_enum_data.data.value, |
1872 | HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) |
1873 | hv_msg->error = HV_S_CONT; |
1874 | goto kvp_done; |
1875 | } |
1876 | |
1877 | key_name = (char *)hv_msg->body.kvp_enum_data.data.key; |
1878 | key_value = (char *)hv_msg->body.kvp_enum_data.data.value; |
1879 | |
1880 | switch (hv_msg->body.kvp_enum_data.index) { |
1881 | case FullyQualifiedDomainName: |
1882 | strcpy(p: key_value, q: full_domain_name); |
1883 | strcpy(p: key_name, q: "FullyQualifiedDomainName" ); |
1884 | break; |
1885 | case IntegrationServicesVersion: |
1886 | strcpy(p: key_name, q: "IntegrationServicesVersion" ); |
1887 | strcpy(p: key_value, q: lic_version); |
1888 | break; |
1889 | case NetworkAddressIPv4: |
1890 | kvp_get_ip_info(AF_INET, NULL, op: KVP_OP_ENUMERATE, |
1891 | out_buffer: key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE); |
1892 | strcpy(p: key_name, q: "NetworkAddressIPv4" ); |
1893 | break; |
1894 | case NetworkAddressIPv6: |
1895 | kvp_get_ip_info(AF_INET6, NULL, op: KVP_OP_ENUMERATE, |
1896 | out_buffer: key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE); |
1897 | strcpy(p: key_name, q: "NetworkAddressIPv6" ); |
1898 | break; |
1899 | case OSBuildNumber: |
1900 | strcpy(p: key_value, q: os_build); |
1901 | strcpy(p: key_name, q: "OSBuildNumber" ); |
1902 | break; |
1903 | case OSName: |
1904 | strcpy(p: key_value, q: os_name); |
1905 | strcpy(p: key_name, q: "OSName" ); |
1906 | break; |
1907 | case OSMajorVersion: |
1908 | strcpy(p: key_value, q: os_major); |
1909 | strcpy(p: key_name, q: "OSMajorVersion" ); |
1910 | break; |
1911 | case OSMinorVersion: |
1912 | strcpy(p: key_value, q: os_minor); |
1913 | strcpy(p: key_name, q: "OSMinorVersion" ); |
1914 | break; |
1915 | case OSVersion: |
1916 | strcpy(p: key_value, q: os_version); |
1917 | strcpy(p: key_name, q: "OSVersion" ); |
1918 | break; |
1919 | case ProcessorArchitecture: |
1920 | strcpy(p: key_value, q: processor_arch); |
1921 | strcpy(p: key_name, q: "ProcessorArchitecture" ); |
1922 | break; |
1923 | default: |
1924 | hv_msg->error = HV_S_CONT; |
1925 | break; |
1926 | } |
1927 | |
1928 | /* |
1929 | * Send the value back to the kernel. Note: the write() may |
1930 | * return an error due to hibernation; we can ignore the error |
1931 | * by resetting the dev file, i.e. closing and re-opening it. |
1932 | */ |
1933 | kvp_done: |
1934 | len = write(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg)); |
1935 | if (len != sizeof(struct hv_kvp_msg)) { |
1936 | syslog(LOG_ERR, "write failed; error: %d %s" , errno, |
1937 | strerror(errno)); |
1938 | goto reopen_kvp_fd; |
1939 | } |
1940 | } |
1941 | |
1942 | close(kvp_fd); |
1943 | exit(0); |
1944 | } |
1945 | |