f971f6dd37
If the network configuration strings are passed as a combination of IPv4 and IPv6 addresses, the current KVP daemon does not handle processing for the keyfile configuration format. With these changes, the keyfile config generation logic scans through the list twice to generate IPv4 and IPv6 sections for the configuration files to handle this support. Testcases ran:Rhel 9, Hyper-V VMs (IPv4 only, IPv6 only, IPv4 and IPv6 combination) Co-developed-by: Ani Sinha <anisinha@redhat.com> Signed-off-by: Ani Sinha <anisinha@redhat.com> Signed-off-by: Shradha Gupta <shradhagupta@linux.microsoft.com> Reviewed-by: Easwar Hariharan <eahariha@linux.microsoft.com> Tested-by: Ani Sinha <anisinha@redhat.com> Reviewed-by: Ani Sinha <anisinha@redhat.com> Link: https://lore.kernel.org/r/1711115162-11629-1-git-send-email-shradhagupta@linux.microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org> Message-ID: <1711115162-11629-1-git-send-email-shradhagupta@linux.microsoft.com>
1945 lines
43 KiB
C
1945 lines
43 KiB
C
/*
|
|
* An implementation of key value pair (KVP) functionality for Linux.
|
|
*
|
|
*
|
|
* Copyright (C) 2010, Novell, Inc.
|
|
* Author : K. Y. Srinivasan <ksrinivasan@novell.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published
|
|
* by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
|
|
* NON INFRINGEMENT. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <sys/poll.h>
|
|
#include <sys/utsname.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <arpa/inet.h>
|
|
#include <linux/hyperv.h>
|
|
#include <ifaddrs.h>
|
|
#include <netdb.h>
|
|
#include <syslog.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <net/if.h>
|
|
#include <limits.h>
|
|
#include <getopt.h>
|
|
|
|
/*
|
|
* KVP protocol: The user mode component first registers with the
|
|
* kernel component. Subsequently, the kernel component requests, data
|
|
* for the specified keys. In response to this message the user mode component
|
|
* fills in the value corresponding to the specified key. We overload the
|
|
* sequence field in the cn_msg header to define our KVP message types.
|
|
*
|
|
* We use this infrastructure for also supporting queries from user mode
|
|
* application for state that may be maintained in the KVP kernel component.
|
|
*
|
|
*/
|
|
|
|
|
|
enum key_index {
|
|
FullyQualifiedDomainName = 0,
|
|
IntegrationServicesVersion, /*This key is serviced in the kernel*/
|
|
NetworkAddressIPv4,
|
|
NetworkAddressIPv6,
|
|
OSBuildNumber,
|
|
OSName,
|
|
OSMajorVersion,
|
|
OSMinorVersion,
|
|
OSVersion,
|
|
ProcessorArchitecture
|
|
};
|
|
|
|
|
|
enum {
|
|
IPADDR = 0,
|
|
NETMASK,
|
|
GATEWAY,
|
|
DNS
|
|
};
|
|
|
|
enum {
|
|
IPV4 = 1,
|
|
IPV6,
|
|
IP_TYPE_MAX
|
|
};
|
|
|
|
static int in_hand_shake;
|
|
|
|
static char *os_name = "";
|
|
static char *os_major = "";
|
|
static char *os_minor = "";
|
|
static char *processor_arch;
|
|
static char *os_build;
|
|
static char *os_version;
|
|
static char *lic_version = "Unknown version";
|
|
static char full_domain_name[HV_KVP_EXCHANGE_MAX_VALUE_SIZE];
|
|
static struct utsname uts_buf;
|
|
|
|
/*
|
|
* The location of the interface configuration file.
|
|
*/
|
|
|
|
#define KVP_CONFIG_LOC "/var/lib/hyperv"
|
|
|
|
#ifndef KVP_SCRIPTS_PATH
|
|
#define KVP_SCRIPTS_PATH "/usr/libexec/hypervkvpd/"
|
|
#endif
|
|
|
|
#define KVP_NET_DIR "/sys/class/net/"
|
|
|
|
#define MAX_FILE_NAME 100
|
|
#define ENTRIES_PER_BLOCK 50
|
|
/*
|
|
* Change this entry if the number of addresses increases in future
|
|
*/
|
|
#define MAX_IP_ENTRIES 64
|
|
#define OUTSTR_BUF_SIZE ((INET6_ADDRSTRLEN + 1) * MAX_IP_ENTRIES)
|
|
|
|
struct kvp_record {
|
|
char key[HV_KVP_EXCHANGE_MAX_KEY_SIZE];
|
|
char value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE];
|
|
};
|
|
|
|
struct kvp_file_state {
|
|
int fd;
|
|
int num_blocks;
|
|
struct kvp_record *records;
|
|
int num_records;
|
|
char fname[MAX_FILE_NAME];
|
|
};
|
|
|
|
static struct kvp_file_state kvp_file_info[KVP_POOL_COUNT];
|
|
|
|
static void kvp_acquire_lock(int pool)
|
|
{
|
|
struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0};
|
|
fl.l_pid = getpid();
|
|
|
|
if (fcntl(kvp_file_info[pool].fd, F_SETLKW, &fl) == -1) {
|
|
syslog(LOG_ERR, "Failed to acquire the lock pool: %d; error: %d %s", pool,
|
|
errno, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
static void kvp_release_lock(int pool)
|
|
{
|
|
struct flock fl = {F_UNLCK, SEEK_SET, 0, 0, 0};
|
|
fl.l_pid = getpid();
|
|
|
|
if (fcntl(kvp_file_info[pool].fd, F_SETLK, &fl) == -1) {
|
|
syslog(LOG_ERR, "Failed to release the lock pool: %d; error: %d %s", pool,
|
|
errno, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
static void kvp_update_file(int pool)
|
|
{
|
|
FILE *filep;
|
|
|
|
/*
|
|
* We are going to write our in-memory registry out to
|
|
* disk; acquire the lock first.
|
|
*/
|
|
kvp_acquire_lock(pool);
|
|
|
|
filep = fopen(kvp_file_info[pool].fname, "we");
|
|
if (!filep) {
|
|
syslog(LOG_ERR, "Failed to open file, pool: %d; error: %d %s", pool,
|
|
errno, strerror(errno));
|
|
kvp_release_lock(pool);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
fwrite(kvp_file_info[pool].records, sizeof(struct kvp_record),
|
|
kvp_file_info[pool].num_records, filep);
|
|
|
|
if (ferror(filep) || fclose(filep)) {
|
|
kvp_release_lock(pool);
|
|
syslog(LOG_ERR, "Failed to write file, pool: %d", pool);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
kvp_release_lock(pool);
|
|
}
|
|
|
|
static void kvp_update_mem_state(int pool)
|
|
{
|
|
FILE *filep;
|
|
size_t records_read = 0;
|
|
struct kvp_record *record = kvp_file_info[pool].records;
|
|
struct kvp_record *readp;
|
|
int num_blocks = kvp_file_info[pool].num_blocks;
|
|
int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK;
|
|
|
|
kvp_acquire_lock(pool);
|
|
|
|
filep = fopen(kvp_file_info[pool].fname, "re");
|
|
if (!filep) {
|
|
syslog(LOG_ERR, "Failed to open file, pool: %d; error: %d %s", pool,
|
|
errno, strerror(errno));
|
|
kvp_release_lock(pool);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
for (;;) {
|
|
readp = &record[records_read];
|
|
records_read += fread(readp, sizeof(struct kvp_record),
|
|
ENTRIES_PER_BLOCK * num_blocks - records_read,
|
|
filep);
|
|
|
|
if (ferror(filep)) {
|
|
syslog(LOG_ERR,
|
|
"Failed to read file, pool: %d; error: %d %s",
|
|
pool, errno, strerror(errno));
|
|
kvp_release_lock(pool);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!feof(filep)) {
|
|
/*
|
|
* We have more data to read.
|
|
*/
|
|
num_blocks++;
|
|
record = realloc(record, alloc_unit * num_blocks);
|
|
|
|
if (record == NULL) {
|
|
syslog(LOG_ERR, "malloc failed");
|
|
kvp_release_lock(pool);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
kvp_file_info[pool].num_blocks = num_blocks;
|
|
kvp_file_info[pool].records = record;
|
|
kvp_file_info[pool].num_records = records_read;
|
|
|
|
fclose(filep);
|
|
kvp_release_lock(pool);
|
|
}
|
|
|
|
static int kvp_file_init(void)
|
|
{
|
|
int fd;
|
|
char *fname;
|
|
int i;
|
|
int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK;
|
|
|
|
if (access(KVP_CONFIG_LOC, F_OK)) {
|
|
if (mkdir(KVP_CONFIG_LOC, 0755 /* rwxr-xr-x */)) {
|
|
syslog(LOG_ERR, "Failed to create '%s'; error: %d %s", KVP_CONFIG_LOC,
|
|
errno, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < KVP_POOL_COUNT; i++) {
|
|
fname = kvp_file_info[i].fname;
|
|
sprintf(fname, "%s/.kvp_pool_%d", KVP_CONFIG_LOC, i);
|
|
fd = open(fname, O_RDWR | O_CREAT | O_CLOEXEC, 0644 /* rw-r--r-- */);
|
|
|
|
if (fd == -1)
|
|
return 1;
|
|
|
|
kvp_file_info[i].fd = fd;
|
|
kvp_file_info[i].num_blocks = 1;
|
|
kvp_file_info[i].records = malloc(alloc_unit);
|
|
if (kvp_file_info[i].records == NULL)
|
|
return 1;
|
|
kvp_file_info[i].num_records = 0;
|
|
kvp_update_mem_state(i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int kvp_key_delete(int pool, const __u8 *key, int key_size)
|
|
{
|
|
int i;
|
|
int j, k;
|
|
int num_records;
|
|
struct kvp_record *record;
|
|
|
|
/*
|
|
* First update the in-memory state.
|
|
*/
|
|
kvp_update_mem_state(pool);
|
|
|
|
num_records = kvp_file_info[pool].num_records;
|
|
record = kvp_file_info[pool].records;
|
|
|
|
for (i = 0; i < num_records; i++) {
|
|
if (memcmp(key, record[i].key, key_size))
|
|
continue;
|
|
/*
|
|
* Found a match; just move the remaining
|
|
* entries up.
|
|
*/
|
|
if (i == (num_records - 1)) {
|
|
kvp_file_info[pool].num_records--;
|
|
kvp_update_file(pool);
|
|
return 0;
|
|
}
|
|
|
|
j = i;
|
|
k = j + 1;
|
|
for (; k < num_records; k++) {
|
|
strcpy(record[j].key, record[k].key);
|
|
strcpy(record[j].value, record[k].value);
|
|
j++;
|
|
}
|
|
|
|
kvp_file_info[pool].num_records--;
|
|
kvp_update_file(pool);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int kvp_key_add_or_modify(int pool, const __u8 *key, int key_size,
|
|
const __u8 *value, int value_size)
|
|
{
|
|
int i;
|
|
int num_records;
|
|
struct kvp_record *record;
|
|
int num_blocks;
|
|
|
|
if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) ||
|
|
(value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE))
|
|
return 1;
|
|
|
|
/*
|
|
* First update the in-memory state.
|
|
*/
|
|
kvp_update_mem_state(pool);
|
|
|
|
num_records = kvp_file_info[pool].num_records;
|
|
record = kvp_file_info[pool].records;
|
|
num_blocks = kvp_file_info[pool].num_blocks;
|
|
|
|
for (i = 0; i < num_records; i++) {
|
|
if (memcmp(key, record[i].key, key_size))
|
|
continue;
|
|
/*
|
|
* Found a match; just update the value -
|
|
* this is the modify case.
|
|
*/
|
|
memcpy(record[i].value, value, value_size);
|
|
kvp_update_file(pool);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Need to add a new entry;
|
|
*/
|
|
if (num_records == (ENTRIES_PER_BLOCK * num_blocks)) {
|
|
/* Need to allocate a larger array for reg entries. */
|
|
record = realloc(record, sizeof(struct kvp_record) *
|
|
ENTRIES_PER_BLOCK * (num_blocks + 1));
|
|
|
|
if (record == NULL)
|
|
return 1;
|
|
kvp_file_info[pool].num_blocks++;
|
|
|
|
}
|
|
memcpy(record[i].value, value, value_size);
|
|
memcpy(record[i].key, key, key_size);
|
|
kvp_file_info[pool].records = record;
|
|
kvp_file_info[pool].num_records++;
|
|
kvp_update_file(pool);
|
|
return 0;
|
|
}
|
|
|
|
static int kvp_get_value(int pool, const __u8 *key, int key_size, __u8 *value,
|
|
int value_size)
|
|
{
|
|
int i;
|
|
int num_records;
|
|
struct kvp_record *record;
|
|
|
|
if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) ||
|
|
(value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE))
|
|
return 1;
|
|
|
|
/*
|
|
* First update the in-memory state.
|
|
*/
|
|
kvp_update_mem_state(pool);
|
|
|
|
num_records = kvp_file_info[pool].num_records;
|
|
record = kvp_file_info[pool].records;
|
|
|
|
for (i = 0; i < num_records; i++) {
|
|
if (memcmp(key, record[i].key, key_size))
|
|
continue;
|
|
/*
|
|
* Found a match; just copy the value out.
|
|
*/
|
|
memcpy(value, record[i].value, value_size);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size,
|
|
__u8 *value, int value_size)
|
|
{
|
|
struct kvp_record *record;
|
|
|
|
/*
|
|
* First update our in-memory database.
|
|
*/
|
|
kvp_update_mem_state(pool);
|
|
record = kvp_file_info[pool].records;
|
|
|
|
if (index >= kvp_file_info[pool].num_records) {
|
|
return 1;
|
|
}
|
|
|
|
memcpy(key, record[index].key, key_size);
|
|
memcpy(value, record[index].value, value_size);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void kvp_get_os_info(void)
|
|
{
|
|
FILE *file;
|
|
char *p, buf[512];
|
|
|
|
uname(&uts_buf);
|
|
os_version = uts_buf.release;
|
|
os_build = strdup(uts_buf.release);
|
|
|
|
os_name = uts_buf.sysname;
|
|
processor_arch = uts_buf.machine;
|
|
|
|
/*
|
|
* The current windows host (win7) expects the build
|
|
* string to be of the form: x.y.z
|
|
* Strip additional information we may have.
|
|
*/
|
|
p = strchr(os_version, '-');
|
|
if (p)
|
|
*p = '\0';
|
|
|
|
/*
|
|
* Parse the /etc/os-release file if present:
|
|
* https://www.freedesktop.org/software/systemd/man/os-release.html
|
|
*/
|
|
file = fopen("/etc/os-release", "r");
|
|
if (file != NULL) {
|
|
while (fgets(buf, sizeof(buf), file)) {
|
|
char *value, *q;
|
|
|
|
/* Ignore comments */
|
|
if (buf[0] == '#')
|
|
continue;
|
|
|
|
/* Split into name=value */
|
|
p = strchr(buf, '=');
|
|
if (!p)
|
|
continue;
|
|
*p++ = 0;
|
|
|
|
/* Remove quotes and newline; un-escape */
|
|
value = p;
|
|
q = p;
|
|
while (*p) {
|
|
if (*p == '\\') {
|
|
++p;
|
|
if (!*p)
|
|
break;
|
|
*q++ = *p++;
|
|
} else if (*p == '\'' || *p == '"' ||
|
|
*p == '\n') {
|
|
++p;
|
|
} else {
|
|
*q++ = *p++;
|
|
}
|
|
}
|
|
*q = 0;
|
|
|
|
if (!strcmp(buf, "NAME")) {
|
|
p = strdup(value);
|
|
if (!p)
|
|
break;
|
|
os_name = p;
|
|
} else if (!strcmp(buf, "VERSION_ID")) {
|
|
p = strdup(value);
|
|
if (!p)
|
|
break;
|
|
os_major = p;
|
|
}
|
|
}
|
|
fclose(file);
|
|
return;
|
|
}
|
|
|
|
/* Fallback for older RH/SUSE releases */
|
|
file = fopen("/etc/SuSE-release", "r");
|
|
if (file != NULL)
|
|
goto kvp_osinfo_found;
|
|
file = fopen("/etc/redhat-release", "r");
|
|
if (file != NULL)
|
|
goto kvp_osinfo_found;
|
|
|
|
/*
|
|
* We don't have information about the os.
|
|
*/
|
|
return;
|
|
|
|
kvp_osinfo_found:
|
|
/* up to three lines */
|
|
p = fgets(buf, sizeof(buf), file);
|
|
if (p) {
|
|
p = strchr(buf, '\n');
|
|
if (p)
|
|
*p = '\0';
|
|
p = strdup(buf);
|
|
if (!p)
|
|
goto done;
|
|
os_name = p;
|
|
|
|
/* second line */
|
|
p = fgets(buf, sizeof(buf), file);
|
|
if (p) {
|
|
p = strchr(buf, '\n');
|
|
if (p)
|
|
*p = '\0';
|
|
p = strdup(buf);
|
|
if (!p)
|
|
goto done;
|
|
os_major = p;
|
|
|
|
/* third line */
|
|
p = fgets(buf, sizeof(buf), file);
|
|
if (p) {
|
|
p = strchr(buf, '\n');
|
|
if (p)
|
|
*p = '\0';
|
|
p = strdup(buf);
|
|
if (p)
|
|
os_minor = p;
|
|
}
|
|
}
|
|
}
|
|
|
|
done:
|
|
fclose(file);
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Retrieve an interface name corresponding to the specified guid.
|
|
* If there is a match, the function returns a pointer
|
|
* to the interface name and if not, a NULL is returned.
|
|
* If a match is found, the caller is responsible for
|
|
* freeing the memory.
|
|
*/
|
|
|
|
static char *kvp_get_if_name(char *guid)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *entry;
|
|
FILE *file;
|
|
char *p, *x;
|
|
char *if_name = NULL;
|
|
char buf[256];
|
|
char dev_id[PATH_MAX];
|
|
|
|
dir = opendir(KVP_NET_DIR);
|
|
if (dir == NULL)
|
|
return NULL;
|
|
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
/*
|
|
* Set the state for the next pass.
|
|
*/
|
|
snprintf(dev_id, sizeof(dev_id), "%s%s/device/device_id",
|
|
KVP_NET_DIR, entry->d_name);
|
|
|
|
file = fopen(dev_id, "r");
|
|
if (file == NULL)
|
|
continue;
|
|
|
|
p = fgets(buf, sizeof(buf), file);
|
|
if (p) {
|
|
x = strchr(p, '\n');
|
|
if (x)
|
|
*x = '\0';
|
|
|
|
if (!strcmp(p, guid)) {
|
|
/*
|
|
* Found the guid match; return the interface
|
|
* name. The caller will free the memory.
|
|
*/
|
|
if_name = strdup(entry->d_name);
|
|
fclose(file);
|
|
break;
|
|
}
|
|
}
|
|
fclose(file);
|
|
}
|
|
|
|
closedir(dir);
|
|
return if_name;
|
|
}
|
|
|
|
/*
|
|
* Retrieve the MAC address given the interface name.
|
|
*/
|
|
|
|
static char *kvp_if_name_to_mac(char *if_name)
|
|
{
|
|
FILE *file;
|
|
char *p, *x;
|
|
char buf[256];
|
|
char addr_file[PATH_MAX];
|
|
unsigned int i;
|
|
char *mac_addr = NULL;
|
|
|
|
snprintf(addr_file, sizeof(addr_file), "%s%s%s", KVP_NET_DIR,
|
|
if_name, "/address");
|
|
|
|
file = fopen(addr_file, "r");
|
|
if (file == NULL)
|
|
return NULL;
|
|
|
|
p = fgets(buf, sizeof(buf), file);
|
|
if (p) {
|
|
x = strchr(p, '\n');
|
|
if (x)
|
|
*x = '\0';
|
|
for (i = 0; i < strlen(p); i++)
|
|
p[i] = toupper(p[i]);
|
|
mac_addr = strdup(p);
|
|
}
|
|
|
|
fclose(file);
|
|
return mac_addr;
|
|
}
|
|
|
|
static void kvp_process_ipconfig_file(char *cmd,
|
|
char *config_buf, unsigned int len,
|
|
int element_size, int offset)
|
|
{
|
|
char buf[256];
|
|
char *p;
|
|
char *x;
|
|
FILE *file;
|
|
|
|
/*
|
|
* First execute the command.
|
|
*/
|
|
file = popen(cmd, "r");
|
|
if (file == NULL)
|
|
return;
|
|
|
|
if (offset == 0)
|
|
memset(config_buf, 0, len);
|
|
while ((p = fgets(buf, sizeof(buf), file)) != NULL) {
|
|
if (len < strlen(config_buf) + element_size + 1)
|
|
break;
|
|
|
|
x = strchr(p, '\n');
|
|
if (x)
|
|
*x = '\0';
|
|
|
|
strcat(config_buf, p);
|
|
strcat(config_buf, ";");
|
|
}
|
|
pclose(file);
|
|
}
|
|
|
|
static void kvp_get_ipconfig_info(char *if_name,
|
|
struct hv_kvp_ipaddr_value *buffer)
|
|
{
|
|
char cmd[512];
|
|
char dhcp_info[128];
|
|
char *p;
|
|
FILE *file;
|
|
|
|
/*
|
|
* Get the address of default gateway (ipv4).
|
|
*/
|
|
sprintf(cmd, "%s %s", "ip route show dev", if_name);
|
|
strcat(cmd, " | awk '/default/ {print $3 }'");
|
|
|
|
/*
|
|
* Execute the command to gather gateway info.
|
|
*/
|
|
kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way,
|
|
(MAX_GATEWAY_SIZE * 2), INET_ADDRSTRLEN, 0);
|
|
|
|
/*
|
|
* Get the address of default gateway (ipv6).
|
|
*/
|
|
sprintf(cmd, "%s %s", "ip -f inet6 route show dev", if_name);
|
|
strcat(cmd, " | awk '/default/ {print $3 }'");
|
|
|
|
/*
|
|
* Execute the command to gather gateway info (ipv6).
|
|
*/
|
|
kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way,
|
|
(MAX_GATEWAY_SIZE * 2), INET6_ADDRSTRLEN, 1);
|
|
|
|
|
|
/*
|
|
* Gather the DNS state.
|
|
* Since there is no standard way to get this information
|
|
* across various distributions of interest; we just invoke
|
|
* an external script that needs to be ported across distros
|
|
* of interest.
|
|
*
|
|
* Following is the expected format of the information from the script:
|
|
*
|
|
* ipaddr1 (nameserver1)
|
|
* ipaddr2 (nameserver2)
|
|
* .
|
|
* .
|
|
*/
|
|
|
|
sprintf(cmd, KVP_SCRIPTS_PATH "%s", "hv_get_dns_info");
|
|
|
|
/*
|
|
* Execute the command to gather DNS info.
|
|
*/
|
|
kvp_process_ipconfig_file(cmd, (char *)buffer->dns_addr,
|
|
(MAX_IP_ADDR_SIZE * 2), INET_ADDRSTRLEN, 0);
|
|
|
|
/*
|
|
* Gather the DHCP state.
|
|
* We will gather this state by invoking an external script.
|
|
* The parameter to the script is the interface name.
|
|
* Here is the expected output:
|
|
*
|
|
* Enabled: DHCP enabled.
|
|
*/
|
|
|
|
sprintf(cmd, KVP_SCRIPTS_PATH "%s %s", "hv_get_dhcp_info", if_name);
|
|
|
|
file = popen(cmd, "r");
|
|
if (file == NULL)
|
|
return;
|
|
|
|
p = fgets(dhcp_info, sizeof(dhcp_info), file);
|
|
if (p == NULL) {
|
|
pclose(file);
|
|
return;
|
|
}
|
|
|
|
if (!strncmp(p, "Enabled", 7))
|
|
buffer->dhcp_enabled = 1;
|
|
else
|
|
buffer->dhcp_enabled = 0;
|
|
|
|
pclose(file);
|
|
}
|
|
|
|
|
|
static unsigned int hweight32(unsigned int *w)
|
|
{
|
|
unsigned int res = *w - ((*w >> 1) & 0x55555555);
|
|
res = (res & 0x33333333) + ((res >> 2) & 0x33333333);
|
|
res = (res + (res >> 4)) & 0x0F0F0F0F;
|
|
res = res + (res >> 8);
|
|
return (res + (res >> 16)) & 0x000000FF;
|
|
}
|
|
|
|
static int kvp_process_ip_address(void *addrp,
|
|
int family, char *buffer,
|
|
int length, int *offset)
|
|
{
|
|
struct sockaddr_in *addr;
|
|
struct sockaddr_in6 *addr6;
|
|
int addr_length;
|
|
char tmp[50];
|
|
const char *str;
|
|
|
|
if (family == AF_INET) {
|
|
addr = addrp;
|
|
str = inet_ntop(family, &addr->sin_addr, tmp, 50);
|
|
addr_length = INET_ADDRSTRLEN;
|
|
} else {
|
|
addr6 = addrp;
|
|
str = inet_ntop(family, &addr6->sin6_addr.s6_addr, tmp, 50);
|
|
addr_length = INET6_ADDRSTRLEN;
|
|
}
|
|
|
|
if ((length - *offset) < addr_length + 2)
|
|
return HV_E_FAIL;
|
|
if (str == NULL) {
|
|
strcpy(buffer, "inet_ntop failed\n");
|
|
return HV_E_FAIL;
|
|
}
|
|
if (*offset == 0)
|
|
strcpy(buffer, tmp);
|
|
else {
|
|
strcat(buffer, ";");
|
|
strcat(buffer, tmp);
|
|
}
|
|
|
|
*offset += strlen(str) + 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
kvp_get_ip_info(int family, char *if_name, int op,
|
|
void *out_buffer, unsigned int length)
|
|
{
|
|
struct ifaddrs *ifap;
|
|
struct ifaddrs *curp;
|
|
int offset = 0;
|
|
int sn_offset = 0;
|
|
int error = 0;
|
|
char *buffer;
|
|
struct hv_kvp_ipaddr_value *ip_buffer = NULL;
|
|
char cidr_mask[5]; /* /xyz */
|
|
int weight;
|
|
int i;
|
|
unsigned int *w;
|
|
char *sn_str;
|
|
struct sockaddr_in6 *addr6;
|
|
|
|
if (op == KVP_OP_ENUMERATE) {
|
|
buffer = out_buffer;
|
|
} else {
|
|
ip_buffer = out_buffer;
|
|
buffer = (char *)ip_buffer->ip_addr;
|
|
ip_buffer->addr_family = 0;
|
|
}
|
|
/*
|
|
* On entry into this function, the buffer is capable of holding the
|
|
* maximum key value.
|
|
*/
|
|
|
|
if (getifaddrs(&ifap)) {
|
|
strcpy(buffer, "getifaddrs failed\n");
|
|
return HV_E_FAIL;
|
|
}
|
|
|
|
curp = ifap;
|
|
while (curp != NULL) {
|
|
if (curp->ifa_addr == NULL) {
|
|
curp = curp->ifa_next;
|
|
continue;
|
|
}
|
|
|
|
if ((if_name != NULL) &&
|
|
(strncmp(curp->ifa_name, if_name, strlen(if_name)))) {
|
|
/*
|
|
* We want info about a specific interface;
|
|
* just continue.
|
|
*/
|
|
curp = curp->ifa_next;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We only support two address families: AF_INET and AF_INET6.
|
|
* If a family value of 0 is specified, we collect both
|
|
* supported address families; if not we gather info on
|
|
* the specified address family.
|
|
*/
|
|
if ((((family != 0) &&
|
|
(curp->ifa_addr->sa_family != family))) ||
|
|
(curp->ifa_flags & IFF_LOOPBACK)) {
|
|
curp = curp->ifa_next;
|
|
continue;
|
|
}
|
|
if ((curp->ifa_addr->sa_family != AF_INET) &&
|
|
(curp->ifa_addr->sa_family != AF_INET6)) {
|
|
curp = curp->ifa_next;
|
|
continue;
|
|
}
|
|
|
|
if (op == KVP_OP_GET_IP_INFO) {
|
|
/*
|
|
* Gather info other than the IP address.
|
|
* IP address info will be gathered later.
|
|
*/
|
|
if (curp->ifa_addr->sa_family == AF_INET) {
|
|
ip_buffer->addr_family |= ADDR_FAMILY_IPV4;
|
|
/*
|
|
* Get subnet info.
|
|
*/
|
|
error = kvp_process_ip_address(
|
|
curp->ifa_netmask,
|
|
AF_INET,
|
|
(char *)
|
|
ip_buffer->sub_net,
|
|
length,
|
|
&sn_offset);
|
|
if (error)
|
|
goto gather_ipaddr;
|
|
} else {
|
|
ip_buffer->addr_family |= ADDR_FAMILY_IPV6;
|
|
|
|
/*
|
|
* Get subnet info in CIDR format.
|
|
*/
|
|
weight = 0;
|
|
sn_str = (char *)ip_buffer->sub_net;
|
|
addr6 = (struct sockaddr_in6 *)
|
|
curp->ifa_netmask;
|
|
w = addr6->sin6_addr.s6_addr32;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
weight += hweight32(&w[i]);
|
|
|
|
sprintf(cidr_mask, "/%d", weight);
|
|
if (length < sn_offset + strlen(cidr_mask) + 1)
|
|
goto gather_ipaddr;
|
|
|
|
if (sn_offset == 0)
|
|
strcpy(sn_str, cidr_mask);
|
|
else {
|
|
strcat((char *)ip_buffer->sub_net, ";");
|
|
strcat(sn_str, cidr_mask);
|
|
}
|
|
sn_offset += strlen(sn_str) + 1;
|
|
}
|
|
|
|
/*
|
|
* Collect other ip related configuration info.
|
|
*/
|
|
|
|
kvp_get_ipconfig_info(if_name, ip_buffer);
|
|
}
|
|
|
|
gather_ipaddr:
|
|
error = kvp_process_ip_address(curp->ifa_addr,
|
|
curp->ifa_addr->sa_family,
|
|
buffer,
|
|
length, &offset);
|
|
if (error)
|
|
goto getaddr_done;
|
|
|
|
curp = curp->ifa_next;
|
|
}
|
|
|
|
getaddr_done:
|
|
freeifaddrs(ifap);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Retrieve the IP given the MAC address.
|
|
*/
|
|
static int kvp_mac_to_ip(struct hv_kvp_ipaddr_value *kvp_ip_val)
|
|
{
|
|
char *mac = (char *)kvp_ip_val->adapter_id;
|
|
DIR *dir;
|
|
struct dirent *entry;
|
|
FILE *file;
|
|
char *p, *x;
|
|
char *if_name = NULL;
|
|
char buf[256];
|
|
char dev_id[PATH_MAX];
|
|
unsigned int i;
|
|
int error = HV_E_FAIL;
|
|
|
|
dir = opendir(KVP_NET_DIR);
|
|
if (dir == NULL)
|
|
return HV_E_FAIL;
|
|
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
/*
|
|
* Set the state for the next pass.
|
|
*/
|
|
snprintf(dev_id, sizeof(dev_id), "%s%s/address", KVP_NET_DIR,
|
|
entry->d_name);
|
|
|
|
file = fopen(dev_id, "r");
|
|
if (file == NULL)
|
|
continue;
|
|
|
|
p = fgets(buf, sizeof(buf), file);
|
|
fclose(file);
|
|
if (!p)
|
|
continue;
|
|
|
|
x = strchr(p, '\n');
|
|
if (x)
|
|
*x = '\0';
|
|
|
|
for (i = 0; i < strlen(p); i++)
|
|
p[i] = toupper(p[i]);
|
|
|
|
if (strcmp(p, mac))
|
|
continue;
|
|
|
|
/*
|
|
* Found the MAC match.
|
|
* A NIC (e.g. VF) matching the MAC, but without IP, is skipped.
|
|
*/
|
|
if_name = entry->d_name;
|
|
if (!if_name)
|
|
continue;
|
|
|
|
error = kvp_get_ip_info(0, if_name, KVP_OP_GET_IP_INFO,
|
|
kvp_ip_val, MAX_IP_ADDR_SIZE * 2);
|
|
|
|
if (!error && strlen((char *)kvp_ip_val->ip_addr))
|
|
break;
|
|
}
|
|
|
|
closedir(dir);
|
|
return error;
|
|
}
|
|
|
|
static int expand_ipv6(char *addr, int type)
|
|
{
|
|
int ret;
|
|
struct in6_addr v6_addr;
|
|
|
|
ret = inet_pton(AF_INET6, addr, &v6_addr);
|
|
|
|
if (ret != 1) {
|
|
if (type == NETMASK)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
sprintf(addr, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
|
|
"%02x%02x:%02x%02x:%02x%02x",
|
|
(int)v6_addr.s6_addr[0], (int)v6_addr.s6_addr[1],
|
|
(int)v6_addr.s6_addr[2], (int)v6_addr.s6_addr[3],
|
|
(int)v6_addr.s6_addr[4], (int)v6_addr.s6_addr[5],
|
|
(int)v6_addr.s6_addr[6], (int)v6_addr.s6_addr[7],
|
|
(int)v6_addr.s6_addr[8], (int)v6_addr.s6_addr[9],
|
|
(int)v6_addr.s6_addr[10], (int)v6_addr.s6_addr[11],
|
|
(int)v6_addr.s6_addr[12], (int)v6_addr.s6_addr[13],
|
|
(int)v6_addr.s6_addr[14], (int)v6_addr.s6_addr[15]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
static int is_ipv4(char *addr)
|
|
{
|
|
int ret;
|
|
struct in_addr ipv4_addr;
|
|
|
|
ret = inet_pton(AF_INET, addr, &ipv4_addr);
|
|
|
|
if (ret == 1)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_ip_val_buffer(char *in_buf, int *offset,
|
|
char *out_buf, int out_len)
|
|
{
|
|
char *x;
|
|
char *start;
|
|
|
|
/*
|
|
* in_buf has sequence of characters that are separated by
|
|
* the character ';'. The last sequence does not have the
|
|
* terminating ";" character.
|
|
*/
|
|
start = in_buf + *offset;
|
|
|
|
x = strchr(start, ';');
|
|
if (x)
|
|
*x = 0;
|
|
else
|
|
x = start + strlen(start);
|
|
|
|
if (strlen(start) != 0) {
|
|
int i = 0;
|
|
/*
|
|
* Get rid of leading spaces.
|
|
*/
|
|
while (start[i] == ' ')
|
|
i++;
|
|
|
|
if ((x - start) <= out_len) {
|
|
strcpy(out_buf, (start + i));
|
|
*offset += (x - start) + 1;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int kvp_write_file(FILE *f, char *s1, char *s2, char *s3)
|
|
{
|
|
int ret;
|
|
|
|
ret = fprintf(f, "%s%s%s%s\n", s1, s2, "=", s3);
|
|
|
|
if (ret < 0)
|
|
return HV_E_FAIL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int process_ip_string(FILE *f, char *ip_string, int type)
|
|
{
|
|
int error = 0;
|
|
char addr[INET6_ADDRSTRLEN];
|
|
int i = 0;
|
|
int j = 0;
|
|
char str[256];
|
|
char sub_str[13];
|
|
int offset = 0;
|
|
|
|
memset(addr, 0, sizeof(addr));
|
|
|
|
while (parse_ip_val_buffer(ip_string, &offset, addr,
|
|
(MAX_IP_ADDR_SIZE * 2))) {
|
|
|
|
sub_str[0] = 0;
|
|
if (is_ipv4(addr)) {
|
|
switch (type) {
|
|
case IPADDR:
|
|
snprintf(str, sizeof(str), "%s", "IPADDR");
|
|
break;
|
|
case NETMASK:
|
|
snprintf(str, sizeof(str), "%s", "NETMASK");
|
|
break;
|
|
case GATEWAY:
|
|
snprintf(str, sizeof(str), "%s", "GATEWAY");
|
|
break;
|
|
case DNS:
|
|
snprintf(str, sizeof(str), "%s", "DNS");
|
|
break;
|
|
}
|
|
|
|
if (type == DNS) {
|
|
snprintf(sub_str, sizeof(sub_str), "%d", ++i);
|
|
} else if (type == GATEWAY && i == 0) {
|
|
++i;
|
|
} else {
|
|
snprintf(sub_str, sizeof(sub_str), "%d", i++);
|
|
}
|
|
|
|
|
|
} else if (expand_ipv6(addr, type)) {
|
|
switch (type) {
|
|
case IPADDR:
|
|
snprintf(str, sizeof(str), "%s", "IPV6ADDR");
|
|
break;
|
|
case NETMASK:
|
|
snprintf(str, sizeof(str), "%s", "IPV6NETMASK");
|
|
break;
|
|
case GATEWAY:
|
|
snprintf(str, sizeof(str), "%s",
|
|
"IPV6_DEFAULTGW");
|
|
break;
|
|
case DNS:
|
|
snprintf(str, sizeof(str), "%s", "DNS");
|
|
break;
|
|
}
|
|
|
|
if (type == DNS) {
|
|
snprintf(sub_str, sizeof(sub_str), "%d", ++i);
|
|
} else if (j == 0) {
|
|
++j;
|
|
} else {
|
|
snprintf(sub_str, sizeof(sub_str), "_%d", j++);
|
|
}
|
|
} else {
|
|
return HV_INVALIDARG;
|
|
}
|
|
|
|
error = kvp_write_file(f, str, sub_str, addr);
|
|
if (error)
|
|
return error;
|
|
memset(addr, 0, sizeof(addr));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ip_version_check(const char *input_addr)
|
|
{
|
|
struct in6_addr addr;
|
|
|
|
if (inet_pton(AF_INET, input_addr, &addr))
|
|
return IPV4;
|
|
else if (inet_pton(AF_INET6, input_addr, &addr))
|
|
return IPV6;
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Only IPv4 subnet strings needs to be converted to plen
|
|
* For IPv6 the subnet is already privided in plen format
|
|
*/
|
|
static int kvp_subnet_to_plen(char *subnet_addr_str)
|
|
{
|
|
int plen = 0;
|
|
struct in_addr subnet_addr4;
|
|
|
|
/*
|
|
* Convert subnet address to binary representation
|
|
*/
|
|
if (inet_pton(AF_INET, subnet_addr_str, &subnet_addr4) == 1) {
|
|
uint32_t subnet_mask = ntohl(subnet_addr4.s_addr);
|
|
|
|
while (subnet_mask & 0x80000000) {
|
|
plen++;
|
|
subnet_mask <<= 1;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
return plen;
|
|
}
|
|
|
|
static int process_dns_gateway_nm(FILE *f, char *ip_string, int type,
|
|
int ip_sec)
|
|
{
|
|
char addr[INET6_ADDRSTRLEN], *output_str;
|
|
int ip_offset = 0, error = 0, ip_ver;
|
|
char *param_name;
|
|
|
|
if (type == DNS)
|
|
param_name = "dns";
|
|
else if (type == GATEWAY)
|
|
param_name = "gateway";
|
|
else
|
|
return -EINVAL;
|
|
|
|
output_str = (char *)calloc(OUTSTR_BUF_SIZE, sizeof(char));
|
|
if (!output_str)
|
|
return -ENOMEM;
|
|
|
|
while (1) {
|
|
memset(addr, 0, sizeof(addr));
|
|
|
|
if (!parse_ip_val_buffer(ip_string, &ip_offset, addr,
|
|
(MAX_IP_ADDR_SIZE * 2)))
|
|
break;
|
|
|
|
ip_ver = ip_version_check(addr);
|
|
if (ip_ver < 0)
|
|
continue;
|
|
|
|
if ((ip_ver == IPV4 && ip_sec == IPV4) ||
|
|
(ip_ver == IPV6 && ip_sec == IPV6)) {
|
|
/*
|
|
* do a bound check to avoid out-of bound writes
|
|
*/
|
|
if ((OUTSTR_BUF_SIZE - strlen(output_str)) >
|
|
(strlen(addr) + 1)) {
|
|
strncat(output_str, addr,
|
|
OUTSTR_BUF_SIZE -
|
|
strlen(output_str) - 1);
|
|
strncat(output_str, ",",
|
|
OUTSTR_BUF_SIZE -
|
|
strlen(output_str) - 1);
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (strlen(output_str)) {
|
|
/*
|
|
* This is to get rid of that extra comma character
|
|
* in the end of the string
|
|
*/
|
|
output_str[strlen(output_str) - 1] = '\0';
|
|
error = fprintf(f, "%s=%s\n", param_name, output_str);
|
|
}
|
|
|
|
free(output_str);
|
|
return error;
|
|
}
|
|
|
|
static int process_ip_string_nm(FILE *f, char *ip_string, char *subnet,
|
|
int ip_sec)
|
|
{
|
|
char addr[INET6_ADDRSTRLEN];
|
|
char subnet_addr[INET6_ADDRSTRLEN];
|
|
int error = 0, i = 0;
|
|
int ip_offset = 0, subnet_offset = 0;
|
|
int plen, ip_ver;
|
|
|
|
memset(addr, 0, sizeof(addr));
|
|
memset(subnet_addr, 0, sizeof(subnet_addr));
|
|
|
|
while (parse_ip_val_buffer(ip_string, &ip_offset, addr,
|
|
(MAX_IP_ADDR_SIZE * 2)) &&
|
|
parse_ip_val_buffer(subnet,
|
|
&subnet_offset,
|
|
subnet_addr,
|
|
(MAX_IP_ADDR_SIZE *
|
|
2))) {
|
|
ip_ver = ip_version_check(addr);
|
|
if (ip_ver < 0)
|
|
continue;
|
|
|
|
if (ip_ver == IPV4 && ip_sec == IPV4)
|
|
plen = kvp_subnet_to_plen((char *)subnet_addr);
|
|
else if (ip_ver == IPV6 && ip_sec == IPV6)
|
|
plen = atoi(subnet_addr);
|
|
else
|
|
continue;
|
|
|
|
if (plen < 0)
|
|
return plen;
|
|
|
|
error = fprintf(f, "address%d=%s/%d\n", ++i, (char *)addr,
|
|
plen);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
memset(addr, 0, sizeof(addr));
|
|
memset(subnet_addr, 0, sizeof(subnet_addr));
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
|
|
{
|
|
int error = 0, ip_ver;
|
|
char if_filename[PATH_MAX];
|
|
char nm_filename[PATH_MAX];
|
|
FILE *ifcfg_file, *nmfile;
|
|
char cmd[PATH_MAX];
|
|
char *mac_addr;
|
|
int str_len;
|
|
|
|
/*
|
|
* Set the configuration for the specified interface with
|
|
* the information provided. Since there is no standard
|
|
* way to configure an interface, we will have an external
|
|
* script that does the job of configuring the interface and
|
|
* flushing the configuration.
|
|
*
|
|
* The parameters passed to this external script are:
|
|
* 1. A configuration file that has the specified configuration.
|
|
*
|
|
* We will embed the name of the interface in the configuration
|
|
* file: ifcfg-ethx (where ethx is the interface name).
|
|
*
|
|
* The information provided here may be more than what is needed
|
|
* in a given distro to configure the interface and so are free
|
|
* ignore information that may not be relevant.
|
|
*
|
|
* Here is the ifcfg format of the ip configuration file:
|
|
*
|
|
* HWADDR=macaddr
|
|
* DEVICE=interface name
|
|
* BOOTPROTO=<protocol> (where <protocol> is "dhcp" if DHCP is configured
|
|
* or "none" if no boot-time protocol should be used)
|
|
*
|
|
* IPADDR0=ipaddr1
|
|
* IPADDR1=ipaddr2
|
|
* IPADDRx=ipaddry (where y = x + 1)
|
|
*
|
|
* NETMASK0=netmask1
|
|
* NETMASKx=netmasky (where y = x + 1)
|
|
*
|
|
* GATEWAY=ipaddr1
|
|
* GATEWAYx=ipaddry (where y = x + 1)
|
|
*
|
|
* DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc)
|
|
*
|
|
* IPV6 addresses will be tagged as IPV6ADDR, IPV6 gateway will be
|
|
* tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as
|
|
* IPV6NETMASK.
|
|
*
|
|
* Here is the keyfile format of the ip configuration file:
|
|
*
|
|
* [ethernet]
|
|
* mac-address=macaddr
|
|
* [connection]
|
|
* interface-name=interface name
|
|
*
|
|
* [ipv4]
|
|
* method=<protocol> (where <protocol> is "auto" if DHCP is configured
|
|
* or "manual" if no boot-time protocol should be used)
|
|
*
|
|
* address1=ipaddr1/plen
|
|
* address2=ipaddr2/plen
|
|
*
|
|
* gateway=gateway1;gateway2
|
|
*
|
|
* dns=dns1;dns2
|
|
*
|
|
* [ipv6]
|
|
* address1=ipaddr1/plen
|
|
* address2=ipaddr2/plen
|
|
*
|
|
* gateway=gateway1;gateway2
|
|
*
|
|
* dns=dns1;dns2
|
|
*
|
|
* The host can specify multiple ipv4 and ipv6 addresses to be
|
|
* configured for the interface. Furthermore, the configuration
|
|
* needs to be persistent. A subsequent GET call on the interface
|
|
* is expected to return the configuration that is set via the SET
|
|
* call.
|
|
*/
|
|
|
|
/*
|
|
* We are populating both ifcfg and nmconnection files
|
|
*/
|
|
snprintf(if_filename, sizeof(if_filename), "%s%s%s", KVP_CONFIG_LOC,
|
|
"/ifcfg-", if_name);
|
|
|
|
ifcfg_file = fopen(if_filename, "w");
|
|
|
|
if (!ifcfg_file) {
|
|
syslog(LOG_ERR, "Failed to open config file; error: %d %s",
|
|
errno, strerror(errno));
|
|
return HV_E_FAIL;
|
|
}
|
|
|
|
snprintf(nm_filename, sizeof(nm_filename), "%s%s%s%s", KVP_CONFIG_LOC,
|
|
"/", if_name, ".nmconnection");
|
|
|
|
nmfile = fopen(nm_filename, "w");
|
|
|
|
if (!nmfile) {
|
|
syslog(LOG_ERR, "Failed to open config file; error: %d %s",
|
|
errno, strerror(errno));
|
|
fclose(ifcfg_file);
|
|
return HV_E_FAIL;
|
|
}
|
|
|
|
/*
|
|
* First write out the MAC address.
|
|
*/
|
|
|
|
mac_addr = kvp_if_name_to_mac(if_name);
|
|
if (mac_addr == NULL) {
|
|
error = HV_E_FAIL;
|
|
goto setval_error;
|
|
}
|
|
|
|
error = kvp_write_file(ifcfg_file, "HWADDR", "", mac_addr);
|
|
if (error < 0)
|
|
goto setmac_error;
|
|
|
|
error = kvp_write_file(ifcfg_file, "DEVICE", "", if_name);
|
|
if (error < 0)
|
|
goto setmac_error;
|
|
|
|
error = fprintf(nmfile, "\n[connection]\n");
|
|
if (error < 0)
|
|
goto setmac_error;
|
|
|
|
error = kvp_write_file(nmfile, "interface-name", "", if_name);
|
|
if (error)
|
|
goto setmac_error;
|
|
|
|
error = fprintf(nmfile, "\n[ethernet]\n");
|
|
if (error < 0)
|
|
goto setmac_error;
|
|
|
|
error = kvp_write_file(nmfile, "mac-address", "", mac_addr);
|
|
if (error)
|
|
goto setmac_error;
|
|
|
|
free(mac_addr);
|
|
|
|
/*
|
|
* The dhcp_enabled flag is only for IPv4. In the case the host only
|
|
* injects an IPv6 address, the flag is true, but we still need to
|
|
* proceed to parse and pass the IPv6 information to the
|
|
* disto-specific script hv_set_ifconfig.
|
|
*/
|
|
|
|
/*
|
|
* First populate the ifcfg file format
|
|
*/
|
|
if (new_val->dhcp_enabled) {
|
|
error = kvp_write_file(ifcfg_file, "BOOTPROTO", "", "dhcp");
|
|
if (error)
|
|
goto setval_error;
|
|
} else {
|
|
error = kvp_write_file(ifcfg_file, "BOOTPROTO", "", "none");
|
|
if (error)
|
|
goto setval_error;
|
|
}
|
|
|
|
error = process_ip_string(ifcfg_file, (char *)new_val->ip_addr,
|
|
IPADDR);
|
|
if (error)
|
|
goto setval_error;
|
|
|
|
error = process_ip_string(ifcfg_file, (char *)new_val->sub_net,
|
|
NETMASK);
|
|
if (error)
|
|
goto setval_error;
|
|
|
|
error = process_ip_string(ifcfg_file, (char *)new_val->gate_way,
|
|
GATEWAY);
|
|
if (error)
|
|
goto setval_error;
|
|
|
|
error = process_ip_string(ifcfg_file, (char *)new_val->dns_addr, DNS);
|
|
if (error)
|
|
goto setval_error;
|
|
|
|
/*
|
|
* Now we populate the keyfile format
|
|
*
|
|
* The keyfile format expects the IPv6 and IPv4 configuration in
|
|
* different sections. Therefore we iterate through the list twice,
|
|
* once to populate the IPv4 section and the next time for IPv6
|
|
*/
|
|
ip_ver = IPV4;
|
|
do {
|
|
if (ip_ver == IPV4) {
|
|
error = fprintf(nmfile, "\n[ipv4]\n");
|
|
if (error < 0)
|
|
goto setval_error;
|
|
} else {
|
|
error = fprintf(nmfile, "\n[ipv6]\n");
|
|
if (error < 0)
|
|
goto setval_error;
|
|
}
|
|
|
|
/*
|
|
* Write the configuration for ipaddress, netmask, gateway and
|
|
* name services
|
|
*/
|
|
error = process_ip_string_nm(nmfile, (char *)new_val->ip_addr,
|
|
(char *)new_val->sub_net,
|
|
ip_ver);
|
|
if (error < 0)
|
|
goto setval_error;
|
|
|
|
/*
|
|
* As dhcp_enabled is only valid for ipv4, we do not set dhcp
|
|
* methods for ipv6 based on dhcp_enabled flag.
|
|
*
|
|
* For ipv4, set method to manual only when dhcp_enabled is
|
|
* false and specific ipv4 addresses are configured. If neither
|
|
* dhcp_enabled is true and no ipv4 addresses are configured,
|
|
* set method to 'disabled'.
|
|
*
|
|
* For ipv6, set method to manual when we configure ipv6
|
|
* addresses. Otherwise set method to 'auto' so that SLAAC from
|
|
* RA may be used.
|
|
*/
|
|
if (ip_ver == IPV4) {
|
|
if (new_val->dhcp_enabled) {
|
|
error = kvp_write_file(nmfile, "method", "",
|
|
"auto");
|
|
if (error < 0)
|
|
goto setval_error;
|
|
} else if (error) {
|
|
error = kvp_write_file(nmfile, "method", "",
|
|
"manual");
|
|
if (error < 0)
|
|
goto setval_error;
|
|
} else {
|
|
error = kvp_write_file(nmfile, "method", "",
|
|
"disabled");
|
|
if (error < 0)
|
|
goto setval_error;
|
|
}
|
|
} else if (ip_ver == IPV6) {
|
|
if (error) {
|
|
error = kvp_write_file(nmfile, "method", "",
|
|
"manual");
|
|
if (error < 0)
|
|
goto setval_error;
|
|
} else {
|
|
error = kvp_write_file(nmfile, "method", "",
|
|
"auto");
|
|
if (error < 0)
|
|
goto setval_error;
|
|
}
|
|
}
|
|
|
|
error = process_dns_gateway_nm(nmfile,
|
|
(char *)new_val->gate_way,
|
|
GATEWAY, ip_ver);
|
|
if (error < 0)
|
|
goto setval_error;
|
|
|
|
error = process_dns_gateway_nm(nmfile,
|
|
(char *)new_val->dns_addr, DNS,
|
|
ip_ver);
|
|
if (error < 0)
|
|
goto setval_error;
|
|
|
|
ip_ver++;
|
|
} while (ip_ver < IP_TYPE_MAX);
|
|
|
|
fclose(nmfile);
|
|
fclose(ifcfg_file);
|
|
|
|
/*
|
|
* Now that we have populated the configuration file,
|
|
* invoke the external script to do its magic.
|
|
*/
|
|
|
|
str_len = snprintf(cmd, sizeof(cmd), KVP_SCRIPTS_PATH "%s %s %s",
|
|
"hv_set_ifconfig", if_filename, nm_filename);
|
|
/*
|
|
* This is a little overcautious, but it's necessary to suppress some
|
|
* false warnings from gcc 8.0.1.
|
|
*/
|
|
if (str_len <= 0 || (unsigned int)str_len >= sizeof(cmd)) {
|
|
syslog(LOG_ERR, "Cmd '%s' (len=%d) may be too long",
|
|
cmd, str_len);
|
|
return HV_E_FAIL;
|
|
}
|
|
|
|
if (system(cmd)) {
|
|
syslog(LOG_ERR, "Failed to execute cmd '%s'; error: %d %s",
|
|
cmd, errno, strerror(errno));
|
|
return HV_E_FAIL;
|
|
}
|
|
return 0;
|
|
setmac_error:
|
|
free(mac_addr);
|
|
setval_error:
|
|
syslog(LOG_ERR, "Failed to write config file");
|
|
fclose(ifcfg_file);
|
|
fclose(nmfile);
|
|
return error;
|
|
}
|
|
|
|
|
|
static void
|
|
kvp_get_domain_name(char *buffer, int length)
|
|
{
|
|
struct addrinfo hints, *info ;
|
|
int error = 0;
|
|
|
|
gethostname(buffer, length);
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_INET; /*Get only ipv4 addrinfo. */
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags = AI_CANONNAME;
|
|
|
|
error = getaddrinfo(buffer, NULL, &hints, &info);
|
|
if (error != 0) {
|
|
snprintf(buffer, length, "getaddrinfo failed: 0x%x %s",
|
|
error, gai_strerror(error));
|
|
return;
|
|
}
|
|
snprintf(buffer, length, "%s", info->ai_canonname);
|
|
freeaddrinfo(info);
|
|
}
|
|
|
|
void print_usage(char *argv[])
|
|
{
|
|
fprintf(stderr, "Usage: %s [options]\n"
|
|
"Options are:\n"
|
|
" -n, --no-daemon stay in foreground, don't daemonize\n"
|
|
" -h, --help print this help\n", argv[0]);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int kvp_fd = -1, len;
|
|
int error;
|
|
struct pollfd pfd;
|
|
char *p;
|
|
struct hv_kvp_msg hv_msg[1];
|
|
char *key_value;
|
|
char *key_name;
|
|
int op;
|
|
int pool;
|
|
char *if_name;
|
|
struct hv_kvp_ipaddr_value *kvp_ip_val;
|
|
int daemonize = 1, long_index = 0, opt;
|
|
|
|
static struct option long_options[] = {
|
|
{"help", no_argument, 0, 'h' },
|
|
{"no-daemon", no_argument, 0, 'n' },
|
|
{0, 0, 0, 0 }
|
|
};
|
|
|
|
while ((opt = getopt_long(argc, argv, "hn", long_options,
|
|
&long_index)) != -1) {
|
|
switch (opt) {
|
|
case 'n':
|
|
daemonize = 0;
|
|
break;
|
|
case 'h':
|
|
print_usage(argv);
|
|
exit(0);
|
|
default:
|
|
print_usage(argv);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (daemonize && daemon(1, 0))
|
|
return 1;
|
|
|
|
openlog("KVP", 0, LOG_USER);
|
|
syslog(LOG_INFO, "KVP starting; pid is:%d", getpid());
|
|
|
|
/*
|
|
* Retrieve OS release information.
|
|
*/
|
|
kvp_get_os_info();
|
|
/*
|
|
* Cache Fully Qualified Domain Name because getaddrinfo takes an
|
|
* unpredictable amount of time to finish.
|
|
*/
|
|
kvp_get_domain_name(full_domain_name, sizeof(full_domain_name));
|
|
|
|
if (kvp_file_init()) {
|
|
syslog(LOG_ERR, "Failed to initialize the pools");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
reopen_kvp_fd:
|
|
if (kvp_fd != -1)
|
|
close(kvp_fd);
|
|
in_hand_shake = 1;
|
|
kvp_fd = open("/dev/vmbus/hv_kvp", O_RDWR | O_CLOEXEC);
|
|
|
|
if (kvp_fd < 0) {
|
|
syslog(LOG_ERR, "open /dev/vmbus/hv_kvp failed; error: %d %s",
|
|
errno, strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Register ourselves with the kernel.
|
|
*/
|
|
hv_msg->kvp_hdr.operation = KVP_OP_REGISTER1;
|
|
len = write(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg));
|
|
if (len != sizeof(struct hv_kvp_msg)) {
|
|
syslog(LOG_ERR, "registration to kernel failed; error: %d %s",
|
|
errno, strerror(errno));
|
|
close(kvp_fd);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
pfd.fd = kvp_fd;
|
|
|
|
while (1) {
|
|
pfd.events = POLLIN;
|
|
pfd.revents = 0;
|
|
|
|
if (poll(&pfd, 1, -1) < 0) {
|
|
syslog(LOG_ERR, "poll failed; error: %d %s", errno, strerror(errno));
|
|
if (errno == EINVAL) {
|
|
close(kvp_fd);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
else
|
|
continue;
|
|
}
|
|
|
|
len = read(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg));
|
|
|
|
if (len != sizeof(struct hv_kvp_msg)) {
|
|
syslog(LOG_ERR, "read failed; error:%d %s",
|
|
errno, strerror(errno));
|
|
goto reopen_kvp_fd;
|
|
}
|
|
|
|
/*
|
|
* We will use the KVP header information to pass back
|
|
* the error from this daemon. So, first copy the state
|
|
* and set the error code to success.
|
|
*/
|
|
op = hv_msg->kvp_hdr.operation;
|
|
pool = hv_msg->kvp_hdr.pool;
|
|
hv_msg->error = HV_S_OK;
|
|
|
|
if ((in_hand_shake) && (op == KVP_OP_REGISTER1)) {
|
|
/*
|
|
* Driver is registering with us; stash away the version
|
|
* information.
|
|
*/
|
|
in_hand_shake = 0;
|
|
p = (char *)hv_msg->body.kvp_register.version;
|
|
lic_version = malloc(strlen(p) + 1);
|
|
if (lic_version) {
|
|
strcpy(lic_version, p);
|
|
syslog(LOG_INFO, "KVP LIC Version: %s",
|
|
lic_version);
|
|
} else {
|
|
syslog(LOG_ERR, "malloc failed");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
switch (op) {
|
|
case KVP_OP_GET_IP_INFO:
|
|
kvp_ip_val = &hv_msg->body.kvp_ip_val;
|
|
|
|
error = kvp_mac_to_ip(kvp_ip_val);
|
|
|
|
if (error)
|
|
hv_msg->error = error;
|
|
|
|
break;
|
|
|
|
case KVP_OP_SET_IP_INFO:
|
|
kvp_ip_val = &hv_msg->body.kvp_ip_val;
|
|
if_name = kvp_get_if_name(
|
|
(char *)kvp_ip_val->adapter_id);
|
|
if (if_name == NULL) {
|
|
/*
|
|
* We could not map the guid to an
|
|
* interface name; return error.
|
|
*/
|
|
hv_msg->error = HV_GUID_NOTFOUND;
|
|
break;
|
|
}
|
|
error = kvp_set_ip_info(if_name, kvp_ip_val);
|
|
if (error)
|
|
hv_msg->error = error;
|
|
|
|
free(if_name);
|
|
break;
|
|
|
|
case KVP_OP_SET:
|
|
if (kvp_key_add_or_modify(pool,
|
|
hv_msg->body.kvp_set.data.key,
|
|
hv_msg->body.kvp_set.data.key_size,
|
|
hv_msg->body.kvp_set.data.value,
|
|
hv_msg->body.kvp_set.data.value_size))
|
|
hv_msg->error = HV_S_CONT;
|
|
break;
|
|
|
|
case KVP_OP_GET:
|
|
if (kvp_get_value(pool,
|
|
hv_msg->body.kvp_set.data.key,
|
|
hv_msg->body.kvp_set.data.key_size,
|
|
hv_msg->body.kvp_set.data.value,
|
|
hv_msg->body.kvp_set.data.value_size))
|
|
hv_msg->error = HV_S_CONT;
|
|
break;
|
|
|
|
case KVP_OP_DELETE:
|
|
if (kvp_key_delete(pool,
|
|
hv_msg->body.kvp_delete.key,
|
|
hv_msg->body.kvp_delete.key_size))
|
|
hv_msg->error = HV_S_CONT;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (op != KVP_OP_ENUMERATE)
|
|
goto kvp_done;
|
|
|
|
/*
|
|
* If the pool is KVP_POOL_AUTO, dynamically generate
|
|
* both the key and the value; if not read from the
|
|
* appropriate pool.
|
|
*/
|
|
if (pool != KVP_POOL_AUTO) {
|
|
if (kvp_pool_enumerate(pool,
|
|
hv_msg->body.kvp_enum_data.index,
|
|
hv_msg->body.kvp_enum_data.data.key,
|
|
HV_KVP_EXCHANGE_MAX_KEY_SIZE,
|
|
hv_msg->body.kvp_enum_data.data.value,
|
|
HV_KVP_EXCHANGE_MAX_VALUE_SIZE))
|
|
hv_msg->error = HV_S_CONT;
|
|
goto kvp_done;
|
|
}
|
|
|
|
key_name = (char *)hv_msg->body.kvp_enum_data.data.key;
|
|
key_value = (char *)hv_msg->body.kvp_enum_data.data.value;
|
|
|
|
switch (hv_msg->body.kvp_enum_data.index) {
|
|
case FullyQualifiedDomainName:
|
|
strcpy(key_value, full_domain_name);
|
|
strcpy(key_name, "FullyQualifiedDomainName");
|
|
break;
|
|
case IntegrationServicesVersion:
|
|
strcpy(key_name, "IntegrationServicesVersion");
|
|
strcpy(key_value, lic_version);
|
|
break;
|
|
case NetworkAddressIPv4:
|
|
kvp_get_ip_info(AF_INET, NULL, KVP_OP_ENUMERATE,
|
|
key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE);
|
|
strcpy(key_name, "NetworkAddressIPv4");
|
|
break;
|
|
case NetworkAddressIPv6:
|
|
kvp_get_ip_info(AF_INET6, NULL, KVP_OP_ENUMERATE,
|
|
key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE);
|
|
strcpy(key_name, "NetworkAddressIPv6");
|
|
break;
|
|
case OSBuildNumber:
|
|
strcpy(key_value, os_build);
|
|
strcpy(key_name, "OSBuildNumber");
|
|
break;
|
|
case OSName:
|
|
strcpy(key_value, os_name);
|
|
strcpy(key_name, "OSName");
|
|
break;
|
|
case OSMajorVersion:
|
|
strcpy(key_value, os_major);
|
|
strcpy(key_name, "OSMajorVersion");
|
|
break;
|
|
case OSMinorVersion:
|
|
strcpy(key_value, os_minor);
|
|
strcpy(key_name, "OSMinorVersion");
|
|
break;
|
|
case OSVersion:
|
|
strcpy(key_value, os_version);
|
|
strcpy(key_name, "OSVersion");
|
|
break;
|
|
case ProcessorArchitecture:
|
|
strcpy(key_value, processor_arch);
|
|
strcpy(key_name, "ProcessorArchitecture");
|
|
break;
|
|
default:
|
|
hv_msg->error = HV_S_CONT;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Send the value back to the kernel. Note: the write() may
|
|
* return an error due to hibernation; we can ignore the error
|
|
* by resetting the dev file, i.e. closing and re-opening it.
|
|
*/
|
|
kvp_done:
|
|
len = write(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg));
|
|
if (len != sizeof(struct hv_kvp_msg)) {
|
|
syslog(LOG_ERR, "write failed; error: %d %s", errno,
|
|
strerror(errno));
|
|
goto reopen_kvp_fd;
|
|
}
|
|
}
|
|
|
|
close(kvp_fd);
|
|
exit(0);
|
|
}
|