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