From abf90febd8fe77baaeb446a6e1e27dfec5c083ae Mon Sep 17 00:00:00 2001 From: delphij Date: Thu, 25 Sep 2014 20:34:13 +0000 Subject: [PATCH] MFC r271493,271688,271689,271696,271854: Import HyperV Key-Value Pair (KVP) driver and daemon code by Microsoft, many thanks for their continued support of FreeBSD. While I'm there, also implement a new build knob, WITHOUT_HYPERV to disable building and installing of the HyperV utilities when necessary. The HyperV utilities are only built for i386 and amd64 targets. Approved by: re (gjb) git-svn-id: svn://svn.freebsd.org/base/stable/10@272128 ccf9f872-aa2e-dd11-9fc8-001c23d0bc1f --- ObsoleteFiles.inc | 2 + contrib/hyperv/tools/hv_kvp_daemon.8 | 68 + contrib/hyperv/tools/hv_kvp_daemon.c | 1518 +++++++++++++++++ contrib/hyperv/tools/scripts/hv_get_dhcp_info | 24 + contrib/hyperv/tools/scripts/hv_get_dns_info | 12 + contrib/hyperv/tools/scripts/hv_set_ifconfig | 73 + etc/devd/Makefile | 4 + etc/devd/hyperv.conf | 19 + etc/mtree/BSD.usr.dist | 2 + etc/mtree/BSD.var.dist | 2 + libexec/Makefile | 5 + libexec/hyperv/Makefile | 10 + share/mk/bsd.own.mk | 6 + sys/conf/files.amd64 | 1 + sys/conf/files.i386 | 1 + sys/dev/hyperv/include/hyperv.h | 29 + sys/dev/hyperv/utilities/hv_kvp.c | 1001 +++++++++++ sys/dev/hyperv/utilities/hv_kvp.h | 143 +- sys/dev/hyperv/utilities/hv_util.c | 47 +- sys/dev/hyperv/utilities/unicode.h | 201 +++ sys/modules/hyperv/utilities/Makefile | 2 +- tools/build/mk/OptionalObsoleteFiles.inc | 8 + tools/build/options/WITHOUT_HYPERV | 2 + tools/build/options/WITH_HYPERV | 2 + usr.sbin/Makefile.amd64 | 3 + usr.sbin/Makefile.i386 | 3 + usr.sbin/hyperv/Makefile | 7 + usr.sbin/hyperv/Makefile.inc | 3 + usr.sbin/hyperv/tools/Makefile | 13 + 29 files changed, 3108 insertions(+), 103 deletions(-) create mode 100644 contrib/hyperv/tools/hv_kvp_daemon.8 create mode 100644 contrib/hyperv/tools/hv_kvp_daemon.c create mode 100644 contrib/hyperv/tools/scripts/hv_get_dhcp_info create mode 100644 contrib/hyperv/tools/scripts/hv_get_dns_info create mode 100644 contrib/hyperv/tools/scripts/hv_set_ifconfig create mode 100644 etc/devd/hyperv.conf create mode 100644 libexec/hyperv/Makefile create mode 100644 sys/dev/hyperv/utilities/hv_kvp.c create mode 100644 sys/dev/hyperv/utilities/unicode.h create mode 100644 tools/build/options/WITHOUT_HYPERV create mode 100644 tools/build/options/WITH_HYPERV create mode 100644 usr.sbin/hyperv/Makefile create mode 100644 usr.sbin/hyperv/Makefile.inc create mode 100644 usr.sbin/hyperv/tools/Makefile diff --git a/ObsoleteFiles.inc b/ObsoleteFiles.inc index 7bff0c530..0ddd4f59e 100644 --- a/ObsoleteFiles.inc +++ b/ObsoleteFiles.inc @@ -38,6 +38,8 @@ # xargs -n1 | sort | uniq -d; # done +# 20140925: hv_kvpd rc.d script removed in favor of devd configuration +OLD_FILES+=etc/rc.d/hv_kvpd # 20140814: libopie version bump OLD_LIBS+=usr/lib/libopie.so.7 OLD_LIBS+=usr/lib32/libopie.so.7 diff --git a/contrib/hyperv/tools/hv_kvp_daemon.8 b/contrib/hyperv/tools/hv_kvp_daemon.8 new file mode 100644 index 000000000..af574edef --- /dev/null +++ b/contrib/hyperv/tools/hv_kvp_daemon.8 @@ -0,0 +1,68 @@ +.\" Copyright (c) 2014 Microsoft Corp. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd September 10, 2013 +.Dt HYPER-V 4 +.Os +.Sh NAME +.Nm hv_kvp_daemon +.Nd Hyper-V Key Value Pair Daemon +.Sh SYNOPSIS +The \fBhv_kvp_daemon\fP daemon provides the ability to store, retrieve, modify and delete +Key Value pairs for FreeBSD guest partitions running on Hyper-V. +.Sh DESCRIPTION +Hyper-V allows administrators to store custom metadata in the form +of Key Value pairs inside the FreeBSD guest partition. Administrators can +use Windows Powershell scripts to add, read, modify and delete such +Key Value pairs. + +The \fBhv_kvp_daemon\fP accepts Key Value pair management requests from the +\fBhv_utils\fP driver and performs the actual metadata management on the file-system. + +The same daemon and driver combination is also used to set and get +IP addresses from a FreeBSD guest. + +The set functionality is particularly +useful when the FreeBSD guest is assigned a static IP address and is failed +over from one Hyper-V host to another. After failover, Hyper-V uses the set IP +functionality to automatically +update the FreeBSD guest's IP address to its original static value. + +On the other hand, the get IP functionality is used to update the guest IP +address in the Hyper-V management console window. +.Sh SEE ALSO +.Xr hv_vmbus 4 , +.Xr hv_utils 4 , +.Xr hv_netvsc 4 , +.Xr hv_storvsc 4 , +.Xr hv_ata_pci_disengage 4 +.Sh HISTORY +Support for Hyper-V in the form of ports was first released in September 2013. +The daemon was developed through a joint effort between Citrix Inc., +Microsoft Corp. and Network Appliance Inc.. +.Sh AUTHORS +.An -nosplit +.Fx +support for \fBhv_kvp_daemon\fP was first added by +.An Microsoft BSD Integration Services Team Aq bsdic@microsoft.com . diff --git a/contrib/hyperv/tools/hv_kvp_daemon.c b/contrib/hyperv/tools/hv_kvp_daemon.c new file mode 100644 index 000000000..890ca5b55 --- /dev/null +++ b/contrib/hyperv/tools/hv_kvp_daemon.c @@ -0,0 +1,1518 @@ +/*- + * Copyright (c) 2014 Microsoft Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hv_kvp.h" + +typedef uint8_t __u8; +typedef uint16_t __u16; +typedef uint32_t __u32; +typedef uint64_t __u64; + +/* + * ENUM Data + */ + +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 +}; + + +/* Global Variables */ + +/* + * The structure for operation handlers. + */ +struct kvp_op_hdlr { + int kvp_op_key; + void (*kvp_op_init)(void); + int (*kvp_op_exec)(struct hv_kvp_msg *kvp_op_msg, void *data); +}; + +static struct kvp_op_hdlr kvp_op_hdlrs[HV_KVP_OP_COUNT]; + +/* OS information */ + +static const char *os_name = ""; +static const char *os_major = ""; +static const char *os_minor = ""; +static const char *processor_arch; +static const char *os_build; +static const char *lic_version = "BSD Pre-Release version"; +static struct utsname uts_buf; + +/* Global flags */ +static int is_daemon = 1; +static int is_debugging = 0; + +#define KVP_LOG(priority, format, args...) do { \ + if (is_debugging == 1) { \ + if (is_daemon == 1) \ + syslog(priority, format, ## args); \ + else \ + printf(format, ## args); \ + } else { \ + if (priority < LOG_DEBUG) { \ + if (is_daemon == 1) \ + syslog(priority, format, ## args); \ + else \ + printf(format, ## args); \ + } \ + } \ + } while(0) + +/* + * For KVP pool file + */ + +#define MAX_FILE_NAME 100 +#define ENTRIES_PER_BLOCK 50 + +struct kvp_record { + char key[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; + char value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; +}; + +struct kvp_pool { + int pool_fd; + int num_blocks; + struct kvp_record *records; + int num_records; + char fname[MAX_FILE_NAME]; +}; + +static struct kvp_pool kvp_pools[HV_KVP_POOL_COUNT]; + + +static void +kvp_acquire_lock(int pool) +{ + struct flock fl = { 0, 0, 0, F_WRLCK, SEEK_SET, 0 }; + + fl.l_pid = getpid(); + + if (fcntl(kvp_pools[pool].pool_fd, F_SETLKW, &fl) == -1) { + KVP_LOG(LOG_ERR, "Failed to acquire the lock pool: %d", pool); + exit(EXIT_FAILURE); + } +} + + +static void +kvp_release_lock(int pool) +{ + struct flock fl = { 0, 0, 0, F_UNLCK, SEEK_SET, 0 }; + + fl.l_pid = getpid(); + + if (fcntl(kvp_pools[pool].pool_fd, F_SETLK, &fl) == -1) { + perror("fcntl"); + KVP_LOG(LOG_ERR, "Failed to release the lock pool: %d\n", pool); + exit(EXIT_FAILURE); + } +} + + +/* + * Write in-memory copy of KVP to pool files + */ +static void +kvp_update_file(int pool) +{ + FILE *filep; + size_t bytes_written; + + kvp_acquire_lock(pool); + + filep = fopen(kvp_pools[pool].fname, "w"); + if (!filep) { + kvp_release_lock(pool); + KVP_LOG(LOG_ERR, "Failed to open file, pool: %d\n", pool); + exit(EXIT_FAILURE); + } + + bytes_written = fwrite(kvp_pools[pool].records, + sizeof(struct kvp_record), + kvp_pools[pool].num_records, filep); + + if (ferror(filep) || fclose(filep)) { + kvp_release_lock(pool); + KVP_LOG(LOG_ERR, "Failed to write file, pool: %d\n", pool); + exit(EXIT_FAILURE); + } + + kvp_release_lock(pool); +} + + +/* + * Read KVPs from pool files and store in memory + */ +static void +kvp_update_mem_state(int pool) +{ + FILE *filep; + size_t records_read = 0; + struct kvp_record *record = kvp_pools[pool].records; + struct kvp_record *readp; + int num_blocks = kvp_pools[pool].num_blocks; + int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK; + + kvp_acquire_lock(pool); + + filep = fopen(kvp_pools[pool].fname, "r"); + if (!filep) { + kvp_release_lock(pool); + KVP_LOG(LOG_ERR, "Failed to open file, pool: %d\n", pool); + exit(EXIT_FAILURE); + } + for ( ; ; ) + { + readp = &record[records_read]; + records_read += fread(readp, sizeof(struct kvp_record), + ENTRIES_PER_BLOCK * num_blocks, + filep); + + if (ferror(filep)) { + KVP_LOG(LOG_ERR, "Failed to read file, pool: %d\n", pool); + exit(EXIT_FAILURE); + } + + if (!feof(filep)) { + /* + * Have more data to read. Expand the memory. + */ + num_blocks++; + record = realloc(record, alloc_unit * num_blocks); + + if (record == NULL) { + KVP_LOG(LOG_ERR, "malloc failed\n"); + exit(EXIT_FAILURE); + } + continue; + } + break; + } + + kvp_pools[pool].num_blocks = num_blocks; + kvp_pools[pool].records = record; + kvp_pools[pool].num_records = records_read; + + fclose(filep); + kvp_release_lock(pool); +} + + +static int +kvp_file_init(void) +{ + int fd; + FILE *filep; + size_t records_read; + char *fname; + struct kvp_record *record; + struct kvp_record *readp; + int num_blocks; + int i; + int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK; + + if (access("/var/db/hyperv/pool", F_OK)) { + if (mkdir("/var/db/hyperv/pool", + S_IRUSR | S_IWUSR | S_IROTH)) { + KVP_LOG(LOG_ERR, " Failed to create /var/db/hyperv/pool\n"); + exit(EXIT_FAILURE); + } + } + + for (i = 0; i < HV_KVP_POOL_COUNT; i++) + { + fname = kvp_pools[i].fname; + records_read = 0; + num_blocks = 1; + snprintf(fname, MAX_FILE_NAME, "/var/db/hyperv/pool/.kvp_pool_%d", i); + fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IROTH); + + if (fd == -1) { + return (1); + } + + + filep = fopen(fname, "r"); + if (!filep) { + return (1); + } + + record = malloc(alloc_unit * num_blocks); + if (record == NULL) { + fclose(filep); + return (1); + } + for ( ; ; ) + { + readp = &record[records_read]; + records_read += fread(readp, sizeof(struct kvp_record), + ENTRIES_PER_BLOCK, + filep); + + if (ferror(filep)) { + KVP_LOG(LOG_ERR, "Failed to read file, pool: %d\n", + i); + exit(EXIT_FAILURE); + } + + if (!feof(filep)) { + /* + * More data to read. + */ + num_blocks++; + record = realloc(record, alloc_unit * + num_blocks); + if (record == NULL) { + fclose(filep); + return (1); + } + continue; + } + break; + } + kvp_pools[i].pool_fd = fd; + kvp_pools[i].num_blocks = num_blocks; + kvp_pools[i].records = record; + kvp_pools[i].num_records = records_read; + fclose(filep); + } + + return (0); +} + + +static int +kvp_key_delete(int pool, __u8 *key, int key_size) +{ + int i; + int j, k; + int num_records; + struct kvp_record *record; + + KVP_LOG(LOG_DEBUG, "kvp_key_delete: pool = %d, " + "key = %s\n", pool, key); + + /* Update in-memory state */ + kvp_update_mem_state(pool); + + num_records = kvp_pools[pool].num_records; + record = kvp_pools[pool].records; + + for (i = 0; i < num_records; i++) + { + if (memcmp(key, record[i].key, key_size)) { + continue; + } + + KVP_LOG(LOG_DEBUG, "Found delete key in pool %d.\n", + pool); + /* + * We found a match at the end; Just update the number of + * entries and we are done. + */ + if (i == num_records) { + kvp_pools[pool].num_records--; + kvp_update_file(pool); + return (0); + } + + /* + * We found a match in the middle; Move the remaining + * entries up. + */ + 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_pools[pool].num_records--; + kvp_update_file(pool); + return (0); + } + KVP_LOG(LOG_DEBUG, "Not found delete key in pool %d.\n", + pool); + return (1); +} + + +static int +kvp_key_add_or_modify(int pool, __u8 *key, __u32 key_size, __u8 *value, + __u32 value_size) +{ + int i; + int num_records; + struct kvp_record *record; + int num_blocks; + + KVP_LOG(LOG_DEBUG, "kvp_key_add_or_modify: pool = %d, " + "key = %s, value = %s\n,", pool, key, value); + + if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) || + (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) { + KVP_LOG(LOG_ERR, "kvp_key_add_or_modify: returning 1\n"); + return (1); + } + + /* Update the in-memory state. */ + kvp_update_mem_state(pool); + + num_records = kvp_pools[pool].num_records; + record = kvp_pools[pool].records; + num_blocks = kvp_pools[pool].num_blocks; + + for (i = 0; i < num_records; i++) + { + if (memcmp(key, record[i].key, key_size)) { + continue; + } + + /* + * Key exists. Just update the value and we are done. + */ + memcpy(record[i].value, value, value_size); + kvp_update_file(pool); + return (0); + } + + /* + * Key doesn't exist; Add a new KVP. + */ + if (num_records == (ENTRIES_PER_BLOCK * num_blocks)) { + /* Increase the size of the recodrd array. */ + record = realloc(record, sizeof(struct kvp_record) * + ENTRIES_PER_BLOCK * (num_blocks + 1)); + + if (record == NULL) { + return (1); + } + kvp_pools[pool].num_blocks++; + } + memcpy(record[i].value, value, value_size); + memcpy(record[i].key, key, key_size); + kvp_pools[pool].records = record; + kvp_pools[pool].num_records++; + kvp_update_file(pool); + return (0); +} + + +static int +kvp_get_value(int pool, __u8 *key, int key_size, __u8 *value, + int value_size) +{ + int i; + int num_records; + struct kvp_record *record; + + KVP_LOG(LOG_DEBUG, "kvp_get_value: pool = %d, key = %s\n,", + pool, key); + + if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) || + (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) { + return (1); + } + + /* Update the in-memory state first. */ + kvp_update_mem_state(pool); + + num_records = kvp_pools[pool].num_records; + record = kvp_pools[pool].records; + + for (i = 0; i < num_records; i++) + { + if (memcmp(key, record[i].key, key_size)) { + continue; + } + + /* Found the key */ + 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; + + KVP_LOG(LOG_DEBUG, "kvp_pool_enumerate: pool = %d, index = %d\n,", + pool, index); + + /* First update our in-memory state first. */ + kvp_update_mem_state(pool); + record = kvp_pools[pool].records; + + /* Index starts with 0 */ + if (index >= kvp_pools[pool].num_records) { + return (1); + } + + memcpy(key, record[index].key, key_size); + memcpy(value, record[index].value, value_size); + return (0); +} + + +static void +kvp_get_os_info(void) +{ + char *p; + + uname(&uts_buf); + os_build = uts_buf.release; + os_name = uts_buf.sysname; + processor_arch = uts_buf.machine; + + /* + * Win7 host expects the build string to be of the form: x.y.z + * Strip additional information we may have. + */ + p = strchr(os_build, '-'); + if (p) { + *p = '\0'; + } + + /* + * We don't have any other information about the FreeBSD os. + */ + return; +} + +/* + * Given the interface name, return the MAC address. + */ +static char * +kvp_if_name_to_mac(char *if_name) +{ + char *mac_addr = NULL; + struct ifaddrs *ifaddrs_ptr; + struct ifaddrs *head_ifaddrs_ptr; + struct sockaddr_dl *sdl; + int status; + + status = getifaddrs(&ifaddrs_ptr); + + if (status >= 0) { + head_ifaddrs_ptr = ifaddrs_ptr; + do { + sdl = (struct sockaddr_dl *)(uintptr_t)ifaddrs_ptr->ifa_addr; + if ((sdl->sdl_type == IFT_ETHER) && + (strcmp(ifaddrs_ptr->ifa_name, if_name) == 0)) { + mac_addr = strdup(ether_ntoa((struct ether_addr *)(LLADDR(sdl)))); + break; + } + } while ((ifaddrs_ptr = ifaddrs_ptr->ifa_next) != NULL); + freeifaddrs(head_ifaddrs_ptr); + } + + return (mac_addr); +} + + +/* + * Given the MAC address, return the interface name. + */ +static char * +kvp_mac_to_if_name(char *mac) +{ + char *if_name = NULL; + struct ifaddrs *ifaddrs_ptr; + struct ifaddrs *head_ifaddrs_ptr; + struct sockaddr_dl *sdl; + int status; + size_t i; + char *buf_ptr; + + status = getifaddrs(&ifaddrs_ptr); + + if (status >= 0) { + head_ifaddrs_ptr = ifaddrs_ptr; + do { + sdl = (struct sockaddr_dl *)(uintptr_t)ifaddrs_ptr->ifa_addr; + if (sdl->sdl_type == IFT_ETHER) { + buf_ptr = strdup(ether_ntoa((struct ether_addr *)(LLADDR(sdl)))); + for (i = 0; i < strlen(buf_ptr); i++) + { + buf_ptr[i] = toupper(buf_ptr[i]); + } + + if (strncmp(buf_ptr, mac, strlen(mac)) == 0) { + /* Caller will free the memory */ + if_name = strdup(ifaddrs_ptr->ifa_name); + free(buf_ptr); + break; + }else if (buf_ptr != NULL) { + free(buf_ptr); + } + } + } while ((ifaddrs_ptr = ifaddrs_ptr->ifa_next) != NULL); + freeifaddrs(head_ifaddrs_ptr); + } + return (if_name); +} + + +static void +kvp_process_ipconfig_file(char *cmd, + char *config_buf, size_t len, + size_t 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'); + *x = '\0'; + strlcat(config_buf, p, len); + strlcat(config_buf, ";", len); + } + 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; + + /* + * Retrieve the IPV4 address of default gateway. + */ + snprintf(cmd, sizeof(cmd), "netstat -rn | grep %s | awk '/default/ {print $2 }'", if_name); + + /* + * Execute the command to gather gateway IPV4 info. + */ + kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, + (MAX_GATEWAY_SIZE * 2), INET_ADDRSTRLEN, 0); + + /* + * Retrieve the IPV6 address of default gateway. + */ + snprintf(cmd, sizeof(cmd), "netstat -rn inet6 | grep %s | awk '/default/ {print $2 }", if_name); + + /* + * Execute the command to gather gateway IPV6 info. + */ + kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way, + (MAX_GATEWAY_SIZE * 2), INET6_ADDRSTRLEN, 1); + + /* + * we just invoke an external script to get the DNS info. + * + * Following is the expected format of the information from the script: + * + * ipaddr1 (nameserver1) + * ipaddr2 (nameserver2) + * . + * . + */ + /* Scripts are stored in /usr/libexec/hyperv/ directory */ + snprintf(cmd, sizeof(cmd), "%s", "sh /usr/libexec/hyperv/hv_get_dns_info"); + + /* + * Execute the command to get DNS info. + */ + kvp_process_ipconfig_file(cmd, (char *)buffer->dns_addr, + (MAX_IP_ADDR_SIZE * 2), INET_ADDRSTRLEN, 0); + + /* + * Invoke an external script to get the DHCP state info. + * The parameter to the script is the interface name. + * Here is the expected output: + * + * Enabled: DHCP enabled. + */ + + + snprintf(cmd, sizeof(cmd), "%s %s", + "sh /usr/libexec/hyperv/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 = (struct sockaddr_in *)addrp; + str = inet_ntop(family, &addr->sin_addr, tmp, 50); + addr_length = INET_ADDRSTRLEN; + } else { + addr6 = (struct sockaddr_in6 *)addrp; + str = inet_ntop(family, &addr6->sin6_addr.s6_addr, tmp, 50); + addr_length = INET6_ADDRSTRLEN; + } + + if ((length - *offset) < addr_length + 1) { + return (HV_KVP_E_FAIL); + } + if (str == NULL) { + strlcpy(buffer, "inet_ntop failed\n", length); + return (HV_KVP_E_FAIL); + } + if (*offset == 0) { + strlcpy(buffer, tmp, length); + } else{ + strlcat(buffer, tmp, length); + } + strlcat(buffer, ";", length); + + *offset += strlen(str) + 1; + return (0); +} + + +static int +kvp_get_ip_info(int family, char *if_name, int op, + void *out_buffer, size_t length) +{ + struct ifaddrs *ifap; + struct ifaddrs *curp; + int offset = 0; + int sn_offset = 0; + int error = 0; + char *buffer; + size_t buffer_length; + struct hv_kvp_ipaddr_value *ip_buffer; + char cidr_mask[5]; + int weight; + int i; + unsigned int *w = NULL; + char *sn_str; + size_t sn_str_length; + struct sockaddr_in6 *addr6; + + if (op == HV_KVP_OP_ENUMERATE) { + buffer = out_buffer; + buffer_length = length; + } else { + ip_buffer = out_buffer; + buffer = (char *)ip_buffer->ip_addr; + buffer_length = sizeof(ip_buffer->ip_addr); + ip_buffer->addr_family = 0; + } + + if (getifaddrs(&ifap)) { + strlcpy(buffer, "getifaddrs failed\n", buffer_length); + return (HV_KVP_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 support two address families: AF_INET and AF_INET6. + * If family value is 0, we gather both supported + * address families; if not we gather info on + * the specified address family. + */ + if ((family != 0) && (curp->ifa_addr->sa_family != family)) { + 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 == HV_KVP_OP_GET_IP_INFO) { + /* + * Get the info other than the IP address. + */ + 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 kvp_get_ip_info_ipaddr; + } + } else { + ip_buffer->addr_family |= ADDR_FAMILY_IPV6; + + /* + * Get subnet info in CIDR format. + */ + weight = 0; + sn_str = (char *)ip_buffer->sub_net; + sn_str_length = sizeof(ip_buffer->sub_net); + addr6 = (struct sockaddr_in6 *)(uintptr_t) + curp->ifa_netmask; + w = (unsigned int *)(uintptr_t)addr6->sin6_addr.s6_addr; + + for (i = 0; i < 4; i++) + { + weight += hweight32(&w[i]); + } + + snprintf(cidr_mask, sizeof(cidr_mask), "/%d", weight); + if ((length - sn_offset) < + (strlen(cidr_mask) + 1)) { + goto kvp_get_ip_info_ipaddr; + } + + if (sn_offset == 0) { + strlcpy(sn_str, cidr_mask, sn_str_length); + } else{ + strlcat(sn_str, cidr_mask, sn_str_length); + } + strlcat((char *)ip_buffer->sub_net, ";", sn_str_length); + sn_offset += strlen(sn_str) + 1; + } + + /* + * Collect other ip configuration info. + */ + + kvp_get_ipconfig_info(if_name, ip_buffer); + } + +kvp_get_ip_info_ipaddr: + error = kvp_process_ip_address(curp->ifa_addr, + curp->ifa_addr->sa_family, + buffer, + length, &offset); + if (error) { + goto kvp_get_ip_info_done; + } + + curp = curp->ifa_next; + } + +kvp_get_ip_info_done: + freeifaddrs(ifap); + return (error); +} + + +static int +kvp_write_file(FILE *f, const char *s1, const char *s2, const char *s3) +{ + int ret; + + ret = fprintf(f, "%s%s%s%s\n", s1, s2, "=", s3); + + if (ret < 0) { + return (HV_KVP_E_FAIL); + } + + return (0); +} + + +static int +kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) +{ + int error = 0; + char if_file[128]; + FILE *file; + char cmd[512]; + char *mac_addr; + + /* + * FreeBSD - Configuration File + */ + snprintf(if_file, sizeof(if_file), "%s%s", "/var/db/hyperv", + "hv_set_ip_data"); + file = fopen(if_file, "w"); + + if (file == NULL) { + KVP_LOG(LOG_ERR, "FreeBSD Failed to open config file\n"); + return (HV_KVP_E_FAIL); + } + + /* + * Write out the MAC address. + */ + + mac_addr = kvp_if_name_to_mac(if_name); + if (mac_addr == NULL) { + error = HV_KVP_E_FAIL; + goto kvp_set_ip_info_error; + } + /* MAC Address */ + error = kvp_write_file(file, "HWADDR", "", mac_addr); + if (error) { + goto kvp_set_ip_info_error; + } + + /* Interface Name */ + error = kvp_write_file(file, "IF_NAME", "", if_name); + if (error) { + goto kvp_set_ip_info_error; + } + + /* IP Address */ + error = kvp_write_file(file, "IP_ADDR", "", + (char *)new_val->ip_addr); + if (error) { + goto kvp_set_ip_info_error; + } + + /* Subnet Mask */ + error = kvp_write_file(file, "SUBNET", "", + (char *)new_val->sub_net); + if (error) { + goto kvp_set_ip_info_error; + } + + + /* Gateway */ + error = kvp_write_file(file, "GATEWAY", "", + (char *)new_val->gate_way); + if (error) { + goto kvp_set_ip_info_error; + } + + /* DNS */ + error = kvp_write_file(file, "DNS", "", (char *)new_val->dns_addr); + if (error) { + goto kvp_set_ip_info_error; + } + + /* DHCP */ + if (new_val->dhcp_enabled) { + error = kvp_write_file(file, "DHCP", "", "1"); + } else{ + error = kvp_write_file(file, "DHCP", "", "0"); + } + + if (error) { + goto kvp_set_ip_info_error; + } + + free(mac_addr); + fclose(file); + + /* + * Invoke the external script with the populated + * configuration file. + */ + + snprintf(cmd, sizeof(cmd), "%s %s", + "sh /usr/libexec/hyperv/hv_set_ifconfig", if_file); + system(cmd); + return (0); + +kvp_set_ip_info_error: + KVP_LOG(LOG_ERR, "Failed to write config file\n"); + free(mac_addr); + fclose(file); + return (error); +} + + +static int +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) { + strlcpy(buffer, "getaddrinfo failed\n", length); + return (error); + } + strlcpy(buffer, info->ai_canonname, length); + freeaddrinfo(info); + return (error); +} + + +static int +kvp_op_getipinfo(struct hv_kvp_msg *op_msg, void *data __unused) +{ + struct hv_kvp_ipaddr_value *ip_val; + char *if_name; + + assert(op_msg != NULL); + KVP_LOG(LOG_DEBUG, "In kvp_op_getipinfo.\n"); + + ip_val = &op_msg->body.kvp_ip_val; + op_msg->hdr.error = HV_KVP_S_OK; + + if_name = kvp_mac_to_if_name((char *)ip_val->adapter_id); + + if (if_name == NULL) { + /* No interface found with the mac address. */ + op_msg->hdr.error = HV_KVP_E_FAIL; + goto kvp_op_getipinfo_done; + } + + op_msg->hdr.error = kvp_get_ip_info(0, if_name, + HV_KVP_OP_GET_IP_INFO, ip_val, (MAX_IP_ADDR_SIZE * 2)); + + free(if_name); + +kvp_op_getipinfo_done: + return(op_msg->hdr.error); +} + + +static int +kvp_op_setipinfo(struct hv_kvp_msg *op_msg, void *data __unused) +{ + struct hv_kvp_ipaddr_value *ip_val; + char *if_name; + + assert(op_msg != NULL); + KVP_LOG(LOG_DEBUG, "In kvp_op_setipinfo.\n"); + + ip_val = &op_msg->body.kvp_ip_val; + op_msg->hdr.error = HV_KVP_S_OK; + + if_name = (char *)ip_val->adapter_id; + + if (if_name == NULL) { + /* No adapter provided. */ + op_msg->hdr.error = HV_KVP_GUID_NOTFOUND; + goto kvp_op_setipinfo_done; + } + + op_msg->hdr.error = kvp_set_ip_info(if_name, ip_val); + +kvp_op_setipinfo_done: + return(op_msg->hdr.error); +} + + +static int +kvp_op_setgetdel(struct hv_kvp_msg *op_msg, void *data) +{ + struct kvp_op_hdlr *op_hdlr = (struct kvp_op_hdlr *)data; + int error = 0; + int op_pool; + + assert(op_msg != NULL); + assert(op_hdlr != NULL); + + op_pool = op_msg->hdr.kvp_hdr.pool; + op_msg->hdr.error = HV_KVP_S_OK; + + switch(op_hdlr->kvp_op_key) { + case HV_KVP_OP_SET: + if (op_pool == HV_KVP_POOL_AUTO) { + /* Auto Pool is not writeable from host side. */ + error = 1; + KVP_LOG(LOG_ERR, "Ilegal to write to pool %d from host\n", + op_pool); + } else { + error = kvp_key_add_or_modify(op_pool, + op_msg->body.kvp_set.data.key, + op_msg->body.kvp_set.data.key_size, + op_msg->body.kvp_set.data.msg_value.value, + op_msg->body.kvp_set.data.value_size); + } + break; + + case HV_KVP_OP_GET: + error = kvp_get_value(op_pool, + op_msg->body.kvp_get.data.key, + op_msg->body.kvp_get.data.key_size, + op_msg->body.kvp_get.data.msg_value.value, + op_msg->body.kvp_get.data.value_size); + break; + + case HV_KVP_OP_DELETE: + if (op_pool == HV_KVP_POOL_AUTO) { + /* Auto Pool is not writeable from host side. */ + error = 1; + KVP_LOG(LOG_ERR, "Ilegal to change pool %d from host\n", + op_pool); + } else { + error = kvp_key_delete(op_pool, + op_msg->body.kvp_delete.key, + op_msg->body.kvp_delete.key_size); + } + break; + + default: + break; + } + + if (error != 0) + op_msg->hdr.error = HV_KVP_S_CONT; + + return(error); +} + + +static int +kvp_op_enumerate(struct hv_kvp_msg *op_msg, void *data __unused) +{ + char *key_name, *key_value; + int error = 0; + int op_pool; + int op; + + assert(op_msg != NULL); + + op = op_msg->hdr.kvp_hdr.operation; + op_pool = op_msg->hdr.kvp_hdr.pool; + op_msg->hdr.error = HV_KVP_S_OK; + + /* + * If the pool is not HV_KVP_POOL_AUTO, read from the appropriate + * pool and return the KVP according to the index requested. + */ + if (op_pool != HV_KVP_POOL_AUTO) { + if (kvp_pool_enumerate(op_pool, + op_msg->body.kvp_enum_data.index, + op_msg->body.kvp_enum_data.data.key, + HV_KVP_EXCHANGE_MAX_KEY_SIZE, + op_msg->body.kvp_enum_data.data.msg_value.value, + HV_KVP_EXCHANGE_MAX_VALUE_SIZE)) { + op_msg->hdr.error = HV_KVP_S_CONT; + error = -1; + } + goto kvp_op_enumerate_done; + } + + key_name = (char *)op_msg->body.kvp_enum_data.data.key; + key_value = (char *)op_msg->body.kvp_enum_data.data.msg_value.value; + + switch (op_msg->body.kvp_enum_data.index) + { + case FullyQualifiedDomainName: + kvp_get_domain_name(key_value, + HV_KVP_EXCHANGE_MAX_VALUE_SIZE); + 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, HV_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, HV_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_build); + strcpy(key_name, "OSVersion"); + break; + + case ProcessorArchitecture: + strcpy(key_value, processor_arch); + strcpy(key_name, "ProcessorArchitecture"); + break; + + default: +#ifdef DEBUG + KVP_LOG(LOG_ERR, "Auto pool Index %d not found.\n", + op_msg->body.kvp_enum_data.index); +#endif + op_msg->hdr.error = HV_KVP_S_CONT; + error = -1; + break; + } + +kvp_op_enumerate_done: + return(error); +} + + +/* + * Load handler, and call init routine if provided. + */ +static int +kvp_op_load(int key, void (*init)(void), + int (*exec)(struct hv_kvp_msg *, void *)) +{ + int error = 0; + + if (key < 0 || key >= HV_KVP_OP_COUNT) { + KVP_LOG(LOG_ERR, "Operation key out of supported range\n"); + error = -1; + goto kvp_op_load_done; + } + + kvp_op_hdlrs[key].kvp_op_key = key; + kvp_op_hdlrs[key].kvp_op_init = init; + kvp_op_hdlrs[key].kvp_op_exec = exec; + + if (kvp_op_hdlrs[key].kvp_op_init != NULL) + kvp_op_hdlrs[key].kvp_op_init(); + +kvp_op_load_done: + return(error); +} + + +/* + * Initialize the operation hanlders. + */ +static int +kvp_ops_init(void) +{ + int i; + + /* Set the initial values. */ + for (i = 0; i < HV_KVP_OP_COUNT; i++) { + kvp_op_hdlrs[i].kvp_op_key = -1; + kvp_op_hdlrs[i].kvp_op_init = NULL; + kvp_op_hdlrs[i].kvp_op_exec = NULL; + } + + return(kvp_op_load(HV_KVP_OP_GET, NULL, kvp_op_setgetdel) | + kvp_op_load(HV_KVP_OP_SET, NULL, kvp_op_setgetdel) | + kvp_op_load(HV_KVP_OP_DELETE, NULL, kvp_op_setgetdel) | + kvp_op_load(HV_KVP_OP_ENUMERATE, kvp_get_os_info, + kvp_op_enumerate) | + kvp_op_load(HV_KVP_OP_GET_IP_INFO, NULL, kvp_op_getipinfo) | + kvp_op_load(HV_KVP_OP_SET_IP_INFO, NULL, kvp_op_setipinfo)); +} + + +int +main(int argc, char *argv[]) +{ + struct hv_kvp_msg *hv_kvp_dev_buf; + struct hv_kvp_msg *hv_msg; + struct pollfd hv_kvp_poll_fd[1]; + int op, pool; + int hv_kvp_dev_fd, error, len, r; + int ch; + + while ((ch = getopt(argc, argv, "dn")) != -1) { + switch (ch) { + case 'n': + /* Run as regular process for debugging purpose. */ + is_daemon = 0; + break; + case 'd': + /* Generate debugging output */ + is_debugging = 1; + break; + default: + break; + } + } + + openlog("HV_KVP", 0, LOG_USER); + + /* Become daemon first. */ + if (is_daemon == 1) + daemon(1, 0); + else + KVP_LOG(LOG_DEBUG, "Run as regular process.\n"); + + KVP_LOG(LOG_INFO, "HV_KVP starting; pid is: %d\n", getpid()); + + /* Communication buffer hv_kvp_dev_buf */ + hv_kvp_dev_buf = malloc(sizeof(*hv_kvp_dev_buf)); + /* Buffer for daemon internal use */ + hv_msg = malloc(sizeof(*hv_msg)); + + /* Memory allocation failed */ + if (hv_kvp_dev_buf == NULL || hv_msg == NULL) { + KVP_LOG(LOG_ERR, "Failed to allocate memory for hv buffer\n"); + exit(EXIT_FAILURE); + } + + /* Initialize op handlers */ + if (kvp_ops_init() != 0) { + KVP_LOG(LOG_ERR, "Failed to initizlize operation handlers\n"); + exit(EXIT_FAILURE); + } + + if (kvp_file_init()) { + KVP_LOG(LOG_ERR, "Failed to initialize the pools\n"); + exit(EXIT_FAILURE); + } + + /* Open the Character Device */ + hv_kvp_dev_fd = open("/dev/hv_kvp_dev", O_RDWR); + + if (hv_kvp_dev_fd < 0) { + KVP_LOG(LOG_ERR, "open /dev/hv_kvp_dev failed; error: %d %s\n", + errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Initialize the struct for polling the char device */ + hv_kvp_poll_fd[0].fd = hv_kvp_dev_fd; + hv_kvp_poll_fd[0].events = (POLLIN | POLLRDNORM); + + /* Register the daemon to the KVP driver */ + memset(hv_kvp_dev_buf, 0, sizeof(*hv_kvp_dev_buf)); + hv_kvp_dev_buf->hdr.kvp_hdr.operation = HV_KVP_OP_REGISTER; + len = write(hv_kvp_dev_fd, hv_kvp_dev_buf, sizeof(*hv_kvp_dev_buf)); + + + for (;;) { + r = poll (hv_kvp_poll_fd, 1, 100); + + KVP_LOG(LOG_DEBUG, "poll returned r = %d, revent = 0x%x\n", + r, hv_kvp_poll_fd[0].revents); + + if (r == 0 || (r < 0 && errno == EAGAIN) || + (r < 0 && errno == EINTR)) { + /* Nothing to read */ + continue; + } + + if (r < 0) { + /* + * For pread return failure other than EAGAIN, + * we want to exit. + */ + KVP_LOG(LOG_ERR, "Poll failed.\n"); + perror("poll"); + exit(EIO); + } + + /* Read from character device */ + len = pread(hv_kvp_dev_fd, hv_kvp_dev_buf, + sizeof(*hv_kvp_dev_buf), 0); + + if (len < 0) { + KVP_LOG(LOG_ERR, "Read failed.\n"); + perror("pread"); + exit(EIO); + } + + if (len != sizeof(struct hv_kvp_msg)) { + KVP_LOG(LOG_ERR, "read len is: %d\n", len); + continue; + } + + /* Copy hv_kvp_dev_buf to hv_msg */ + memcpy(hv_msg, hv_kvp_dev_buf, sizeof(*hv_msg)); + + /* + * We will use the KVP header information to pass back + * the error from this daemon. So, first save the op + * and pool info to local variables. + */ + + op = hv_msg->hdr.kvp_hdr.operation; + pool = hv_msg->hdr.kvp_hdr.pool; + + if (op < 0 || op >= HV_KVP_OP_COUNT || + kvp_op_hdlrs[op].kvp_op_exec == NULL) { + KVP_LOG(LOG_WARNING, + "Unsupported operation OP = %d\n", op); + hv_msg->hdr.error = HV_ERROR_NOT_SUPPORTED; + } else { + /* + * Call the operateion handler's execution routine. + */ + error = kvp_op_hdlrs[op].kvp_op_exec(hv_msg, + (void *)&kvp_op_hdlrs[op]); + if (error != 0 && hv_msg->hdr.error != HV_KVP_S_CONT) + KVP_LOG(LOG_WARNING, + "Operation failed OP = %d, error = 0x%x\n", + op, error); + } + + /* + * Send the value back to the kernel. The response is + * already in the receive buffer. + */ +hv_kvp_done: + len = pwrite(hv_kvp_dev_fd, hv_msg, sizeof(*hv_kvp_dev_buf), 0); + + if (len != sizeof(struct hv_kvp_msg)) { + KVP_LOG(LOG_ERR, "write len is: %d\n", len); + goto hv_kvp_done; + } + } +} diff --git a/contrib/hyperv/tools/scripts/hv_get_dhcp_info b/contrib/hyperv/tools/scripts/hv_get_dhcp_info new file mode 100644 index 000000000..b56efd30f --- /dev/null +++ b/contrib/hyperv/tools/scripts/hv_get_dhcp_info @@ -0,0 +1,24 @@ +#!/bin/sh + +# This is the script retrieves the DHCP state of a given interface. +# The kvp daemon code invokes this external script to gather +# DHCP setting for the specific interface. +# +# Input: Name of the interface +# +# Output: The script prints the string "Enabled" to stdout to indicate +# that DHCP is enabled on the interface. If DHCP is not enabled, +# the script prints the string "Disabled" to stdout. +# + +. /etc/rc.subr +. /etc/network.subr + +load_rc_config netif + +if dhcpif hn0; +then +echo "Enabled" +else +echo "Disabled" +fi diff --git a/contrib/hyperv/tools/scripts/hv_get_dns_info b/contrib/hyperv/tools/scripts/hv_get_dns_info new file mode 100644 index 000000000..7a34440fd --- /dev/null +++ b/contrib/hyperv/tools/scripts/hv_get_dns_info @@ -0,0 +1,12 @@ +#!/bin/sh +# This script parses /etc/resolv.conf to retrive DNS information. +# Khe kvp daemon code invokes this external script to gather +# DNS information. +# This script is expected to print the nameserver values to stdout. + +#if test -r /etc/resolv.conf +#then +# awk -- '/^nameserver/ { print $2 }' /etc/resolv.conf +#fi +cat /etc/resolv.conf 2>/dev/null | awk '/^nameserver/ { print $2 }' + diff --git a/contrib/hyperv/tools/scripts/hv_set_ifconfig b/contrib/hyperv/tools/scripts/hv_set_ifconfig new file mode 100644 index 000000000..3dc42e7bb --- /dev/null +++ b/contrib/hyperv/tools/scripts/hv_set_ifconfig @@ -0,0 +1,73 @@ +#!/bin/sh + +# This script activates an interface based on the specified +# configuration. The kvp daemon code invokes this external script +# to configure the interface. +# +# The only argument to this script is the configuration file that is to +# be used to configure the interface. +# +# Here is the format of the ip configuration file: +# +# HWADDR=macaddr +# IF_NAME=interface name +# DHCP=yes (This is optional; if yes, DHCP is configured) +# +# IPADDR=ipaddr1 +# IPADDR_1=ipaddr2 +# IPADDR_x=ipaddry (where y = x + 1) +# +# NETMASK=netmask1 +# NETMASK_x=netmasky (where y = x + 1) +# +# GATEWAY=ipaddr1 +# GATEWAY_x=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. +# +# 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. +# + +. $1 + +sed -i".bak" '/ifconfig_hn0="SYNCDHCP"/d' /etc/rc.conf +sed -i".bak" '/ifconfig_hn0="DHCP"/d' /etc/rc.conf + +# MAC Address +ifconfig $IF_NAME ether $HWADDR + +# IP and Subnet Mask +ifconfig $IF_NAME inet $IP_ADDR netmask $SUBNET + +# DNS +sed -i".bak" '/nameserver/d' /etc/resolv.conf +echo "nameserver" $DNS >> /etc/resolv.conf + +#Gateway +# Need to implment if Gateway is not present +route flush +route add default $GATEWAY +#route change default $GATEWAY + +#/etc/rc.d/netif restart +#/etc/rc.d/routing restart + + +# DHCP +if [ $DHCP -eq 1 ] +then + echo ifconfig_hn0=\"DHCP\" >> /etc/rc.conf + echo Enabled +else + echo Disabled DHCP >> /var/log/messages + echo Disabled +fi +echo "Set IP-Injection Success" diff --git a/etc/devd/Makefile b/etc/devd/Makefile index 433436b08..50bc45788 100644 --- a/etc/devd/Makefile +++ b/etc/devd/Makefile @@ -10,6 +10,10 @@ FILES+= apple.conf FILES+= asus.conf .endif +.if ${MK_HYPERV} != "no" +FILES+= hyperv.conf +.endif + NO_OBJ= FILESDIR= /etc/devd FILESMODE= 644 diff --git a/etc/devd/hyperv.conf b/etc/devd/hyperv.conf new file mode 100644 index 000000000..bed7383d0 --- /dev/null +++ b/etc/devd/hyperv.conf @@ -0,0 +1,19 @@ +# $FreeBSD$ +# +# Hyper-V specific events + +notify 10 { + match "system" "DEVFS"; + match "subsystem" "CDEV"; + match "type" "CREATE"; + match "cdev" "hv_kvp_dev"; + action "/usr/sbin/hv_kvp_daemon"; +}; + +notify 10 { + match "system" "DEVFS"; + match "subsystem" "CDEV"; + match "type" "DESTROY"; + match "cdev" "hv_kvp_dev"; + action "pkill -x hv_kvp_daemon"; +}; diff --git a/etc/mtree/BSD.usr.dist b/etc/mtree/BSD.usr.dist index 2c4085373..a310e88eb 100644 --- a/etc/mtree/BSD.usr.dist +++ b/etc/mtree/BSD.usr.dist @@ -108,6 +108,8 @@ .. bsdinstall .. + hyperv + .. lpr ru .. diff --git a/etc/mtree/BSD.var.dist b/etc/mtree/BSD.var.dist index ba02aba44..240381449 100644 --- a/etc/mtree/BSD.var.dist +++ b/etc/mtree/BSD.var.dist @@ -42,6 +42,8 @@ .. freebsd-update mode=0700 .. + hyperv mode=0700 + .. ipf mode=0700 .. pkg diff --git a/libexec/Makefile b/libexec/Makefile index cf5c12016..66e24261e 100644 --- a/libexec/Makefile +++ b/libexec/Makefile @@ -10,6 +10,7 @@ SUBDIR= ${_atf} \ fingerd \ ftpd \ getty \ + ${_hyperv} \ ${_mail.local} \ ${_mknetid} \ ${_pppoed} \ @@ -42,6 +43,10 @@ _atrun= atrun _comsat= comsat .endif +.if ${MK_HYPERV} != "no" +_hyperv= hyperv +.endif + .if ${MK_NIS} != "no" _mknetid= mknetid _ypxfr= ypxfr diff --git a/libexec/hyperv/Makefile b/libexec/hyperv/Makefile new file mode 100644 index 000000000..37abdf5b8 --- /dev/null +++ b/libexec/hyperv/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../contrib/hyperv/tools/scripts + +BINDIR= /usr/libexec/hyperv + +SCRIPTS= hv_set_ifconfig hv_get_dns_info hv_get_dhcp_info +NO_OBJ= + +.include diff --git a/share/mk/bsd.own.mk b/share/mk/bsd.own.mk index 444a909b9..f043cab9c 100644 --- a/share/mk/bsd.own.mk +++ b/share/mk/bsd.own.mk @@ -438,6 +438,12 @@ __DEFAULT_YES_OPTIONS+=FDT .else __DEFAULT_NO_OPTIONS+=FDT .endif +# HyperV is only available for x86 and amd64. +.if ${__T} == "amd64" || ${__T} == "i386" +__DEFAULT_YES_OPTIONS+=HYPERV +.else +__DEFAULT_NO_OPTIONS+=HYPERV +.endif .undef __T # diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64 index 28f9619a1..4d5cb6082 100644 --- a/sys/conf/files.amd64 +++ b/sys/conf/files.amd64 @@ -234,6 +234,7 @@ dev/hyperv/netvsc/hv_netvsc_drv_freebsd.c optional hyperv dev/hyperv/netvsc/hv_rndis_filter.c optional hyperv dev/hyperv/stordisengage/hv_ata_pci_disengage.c optional hyperv dev/hyperv/storvsc/hv_storvsc_drv_freebsd.c optional hyperv +dev/hyperv/utilities/hv_kvp.c optional hyperv dev/hyperv/utilities/hv_util.c optional hyperv dev/hyperv/vmbus/hv_channel.c optional hyperv dev/hyperv/vmbus/hv_channel_mgmt.c optional hyperv diff --git a/sys/conf/files.i386 b/sys/conf/files.i386 index 43b0e5b38..2446a7bd7 100644 --- a/sys/conf/files.i386 +++ b/sys/conf/files.i386 @@ -227,6 +227,7 @@ dev/hyperv/netvsc/hv_netvsc_drv_freebsd.c optional hyperv dev/hyperv/netvsc/hv_rndis_filter.c optional hyperv dev/hyperv/stordisengage/hv_ata_pci_disengage.c optional hyperv dev/hyperv/storvsc/hv_storvsc_drv_freebsd.c optional hyperv +dev/hyperv/utilities/hv_kvp.c optional hyperv dev/hyperv/utilities/hv_util.c optional hyperv dev/hyperv/vmbus/hv_channel.c optional hyperv dev/hyperv/vmbus/hv_channel_mgmt.c optional hyperv diff --git a/sys/dev/hyperv/include/hyperv.h b/sys/dev/hyperv/include/hyperv.h index 4257b371c..8a45d89cd 100644 --- a/sys/dev/hyperv/include/hyperv.h +++ b/sys/dev/hyperv/include/hyperv.h @@ -795,5 +795,34 @@ hv_get_phys_addr(void *virt) return (ret); } + +/** + * KVP related structures + * + */ +typedef struct hv_vmbus_service { + hv_guid guid; /* Hyper-V GUID */ + char *name; /* name of service */ + boolean_t enabled; /* service enabled */ + hv_work_queue *work_queue; /* background work queue */ + + /* + * function to initialize service + */ + int (*init)(struct hv_vmbus_service *); + + /* + * function to process Hyper-V messages + */ + void (*callback)(void *); +} hv_vmbus_service; + +extern uint8_t* receive_buffer[]; +extern hv_vmbus_service service_table[]; + +void hv_kvp_callback(void *context); +int hv_kvp_init(hv_vmbus_service *serv); +void hv_kvp_deinit(void); + #endif /* __HYPERV_H__ */ diff --git a/sys/dev/hyperv/utilities/hv_kvp.c b/sys/dev/hyperv/utilities/hv_kvp.c new file mode 100644 index 000000000..848d364a4 --- /dev/null +++ b/sys/dev/hyperv/utilities/hv_kvp.c @@ -0,0 +1,1001 @@ +/*- + * Copyright (c) 2014 Microsoft Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Author: Sainath Varanasi. + * Date: 4/2012 + * Email: bsdic@microsoft.com + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "unicode.h" +#include "hv_kvp.h" + +/* hv_kvp defines */ +#define BUFFERSIZE sizeof(struct hv_kvp_msg) +#define KVP_SUCCESS 0 +#define KVP_ERROR 1 +#define kvp_hdr hdr.kvp_hdr + +/* hv_kvp debug control */ +static int hv_kvp_log = 0; +SYSCTL_INT(_dev, OID_AUTO, hv_kvp_log, CTLFLAG_RW, &hv_kvp_log, 0, + "hv_kvp log"); + +#define hv_kvp_log_error(...) do { \ + if (hv_kvp_log > 0) \ + log(LOG_ERR, "hv_kvp: " __VA_ARGS__); \ +} while (0) + +#define hv_kvp_log_info(...) do { \ + if (hv_kvp_log > 1) \ + log(LOG_INFO, "hv_kvp: " __VA_ARGS__); \ +} while (0) + +/* character device prototypes */ +static d_open_t hv_kvp_dev_open; +static d_close_t hv_kvp_dev_close; +static d_read_t hv_kvp_dev_daemon_read; +static d_write_t hv_kvp_dev_daemon_write; +static d_poll_t hv_kvp_dev_daemon_poll; + +/* hv_kvp prototypes */ +static int hv_kvp_req_in_progress(void); +static void hv_kvp_transaction_init(uint32_t, hv_vmbus_channel *, uint64_t, uint8_t *); +static void hv_kvp_send_msg_to_daemon(void); +static void hv_kvp_process_request(void *context); + +/* hv_kvp character device structure */ +static struct cdevsw hv_kvp_cdevsw = +{ + .d_version = D_VERSION, + .d_open = hv_kvp_dev_open, + .d_close = hv_kvp_dev_close, + .d_read = hv_kvp_dev_daemon_read, + .d_write = hv_kvp_dev_daemon_write, + .d_poll = hv_kvp_dev_daemon_poll, + .d_name = "hv_kvp_dev", +}; +static struct cdev *hv_kvp_dev; +static struct hv_kvp_msg *hv_kvp_dev_buf; +struct proc *daemon_task; + +/* + * Global state to track and synchronize multiple + * KVP transaction requests from the host. + */ +static struct { + + /* Pre-allocated work item for queue */ + hv_work_item work_item; + + /* Unless specified the pending mutex should be + * used to alter the values of the following paramters: + * 1. req_in_progress + * 2. req_timed_out + * 3. pending_reqs. + */ + struct mtx pending_mutex; + + /* To track if transaction is active or not */ + boolean_t req_in_progress; + /* Tracks if daemon did not reply back in time */ + boolean_t req_timed_out; + /* Tracks if daemon is serving a request currently */ + boolean_t daemon_busy; + /* Count of KVP requests from Hyper-V. */ + uint64_t pending_reqs; + + + /* Length of host message */ + uint32_t host_msg_len; + + /* Pointer to channel */ + hv_vmbus_channel *channelp; + + /* Host message id */ + uint64_t host_msg_id; + + /* Current kvp message from the host */ + struct hv_kvp_msg *host_kvp_msg; + + /* Current kvp message for daemon */ + struct hv_kvp_msg daemon_kvp_msg; + + /* Rcv buffer for communicating with the host*/ + uint8_t *rcv_buf; + + /* Device semaphore to control communication */ + struct sema dev_sema; + + /* Indicates if daemon registered with driver */ + boolean_t register_done; + + /* Character device status */ + boolean_t dev_accessed; +} kvp_globals; + +/* global vars */ +MALLOC_DECLARE(M_HV_KVP_DEV_BUF); +MALLOC_DEFINE(M_HV_KVP_DEV_BUF, "hv_kvp_dev buffer", "buffer for hv_kvp_dev module"); + +/* + * hv_kvp low level functions + */ + +/* + * Check if kvp transaction is in progres + */ +static int +hv_kvp_req_in_progress(void) +{ + + return (kvp_globals.req_in_progress); +} + + +/* + * This routine is called whenever a message is received from the host + */ +static void +hv_kvp_transaction_init(uint32_t rcv_len, hv_vmbus_channel *rcv_channel, + uint64_t request_id, uint8_t *rcv_buf) +{ + + /* Store all the relevant message details in the global structure */ + /* Do not need to use mutex for req_in_progress here */ + kvp_globals.req_in_progress = true; + kvp_globals.host_msg_len = rcv_len; + kvp_globals.channelp = rcv_channel; + kvp_globals.host_msg_id = request_id; + kvp_globals.rcv_buf = rcv_buf; + kvp_globals.host_kvp_msg = (struct hv_kvp_msg *)&rcv_buf[ + sizeof(struct hv_vmbus_pipe_hdr) + + sizeof(struct hv_vmbus_icmsg_hdr)]; +} + + +/* + * hv_kvp - version neogtiation function + */ +static void +hv_kvp_negotiate_version(struct hv_vmbus_icmsg_hdr *icmsghdrp, + struct hv_vmbus_icmsg_negotiate *negop, + uint8_t *buf) +{ + int icframe_vercnt; + int icmsg_vercnt; + + icmsghdrp->icmsgsize = 0x10; + + negop = (struct hv_vmbus_icmsg_negotiate *)&buf[ + sizeof(struct hv_vmbus_pipe_hdr) + + sizeof(struct hv_vmbus_icmsg_hdr)]; + icframe_vercnt = negop->icframe_vercnt; + icmsg_vercnt = negop->icmsg_vercnt; + + /* + * Select the framework version number we will support + */ + if ((icframe_vercnt >= 2) && (negop->icversion_data[1].major == 3)) { + icframe_vercnt = 3; + if (icmsg_vercnt >= 2) + icmsg_vercnt = 4; + else + icmsg_vercnt = 3; + } else { + icframe_vercnt = 1; + icmsg_vercnt = 1; + } + + negop->icframe_vercnt = 1; + negop->icmsg_vercnt = 1; + negop->icversion_data[0].major = icframe_vercnt; + negop->icversion_data[0].minor = 0; + negop->icversion_data[1].major = icmsg_vercnt; + negop->icversion_data[1].minor = 0; +} + + +/* + * Convert ip related info in umsg from utf8 to utf16 and store in hmsg + */ +static int +hv_kvp_convert_utf8_ipinfo_to_utf16(struct hv_kvp_msg *umsg, + struct hv_kvp_ip_msg *host_ip_msg) +{ + int err_ip, err_subnet, err_gway, err_dns, err_adap; + int UNUSED_FLAG = 1; + + utf8_to_utf16((uint16_t *)host_ip_msg->kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE, + (char *)umsg->body.kvp_ip_val.ip_addr, + strlen((char *)umsg->body.kvp_ip_val.ip_addr), + UNUSED_FLAG, + &err_ip); + utf8_to_utf16((uint16_t *)host_ip_msg->kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE, + (char *)umsg->body.kvp_ip_val.sub_net, + strlen((char *)umsg->body.kvp_ip_val.sub_net), + UNUSED_FLAG, + &err_subnet); + utf8_to_utf16((uint16_t *)host_ip_msg->kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE, + (char *)umsg->body.kvp_ip_val.gate_way, + strlen((char *)umsg->body.kvp_ip_val.gate_way), + UNUSED_FLAG, + &err_gway); + utf8_to_utf16((uint16_t *)host_ip_msg->kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE, + (char *)umsg->body.kvp_ip_val.dns_addr, + strlen((char *)umsg->body.kvp_ip_val.dns_addr), + UNUSED_FLAG, + &err_dns); + utf8_to_utf16((uint16_t *)host_ip_msg->kvp_ip_val.adapter_id, + MAX_IP_ADDR_SIZE, + (char *)umsg->body.kvp_ip_val.adapter_id, + strlen((char *)umsg->body.kvp_ip_val.adapter_id), + UNUSED_FLAG, + &err_adap); + + host_ip_msg->kvp_ip_val.dhcp_enabled = umsg->body.kvp_ip_val.dhcp_enabled; + host_ip_msg->kvp_ip_val.addr_family = umsg->body.kvp_ip_val.addr_family; + + return (err_ip | err_subnet | err_gway | err_dns | err_adap); +} + + +/* + * Convert ip related info in hmsg from utf16 to utf8 and store in umsg + */ +static int +hv_kvp_convert_utf16_ipinfo_to_utf8(struct hv_kvp_ip_msg *host_ip_msg, + struct hv_kvp_msg *umsg) +{ + int err_ip, err_subnet, err_gway, err_dns, err_adap; + int UNUSED_FLAG = 1; + int guid_index; + struct hv_device *hv_dev; /* GUID Data Structure */ + hn_softc_t *sc; /* hn softc structure */ + char if_name[4]; + unsigned char guid_instance[40]; + char *guid_data = NULL; + char buf[39]; + + struct guid_extract { + char a1[2]; + char a2[2]; + char a3[2]; + char a4[2]; + char b1[2]; + char b2[2]; + char c1[2]; + char c2[2]; + char d[4]; + char e[12]; + }; + + struct guid_extract *id; + device_t *devs; + int devcnt; + + /* IP Address */ + utf16_to_utf8((char *)umsg->body.kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE, + (uint16_t *)host_ip_msg->kvp_ip_val.ip_addr, + MAX_IP_ADDR_SIZE, + UNUSED_FLAG, + &err_ip); + + /* Adapter ID : GUID */ + utf16_to_utf8((char *)umsg->body.kvp_ip_val.adapter_id, + MAX_ADAPTER_ID_SIZE, + (uint16_t *)host_ip_msg->kvp_ip_val.adapter_id, + MAX_ADAPTER_ID_SIZE, + UNUSED_FLAG, + &err_adap); + + if (devclass_get_devices(devclass_find("hn"), &devs, &devcnt) == 0) { + for (devcnt = devcnt - 1; devcnt >= 0; devcnt--) { + sc = device_get_softc(devs[devcnt]); + + /* Trying to find GUID of Network Device */ + hv_dev = sc->hn_dev_obj; + + for (guid_index = 0; guid_index < 16; guid_index++) { + sprintf(&guid_instance[guid_index * 2], "%02x", + hv_dev->device_id.data[guid_index]); + } + + guid_data = (char *)guid_instance; + id = (struct guid_extract *)guid_data; + snprintf(buf, sizeof(buf), "{%.2s%.2s%.2s%.2s-%.2s%.2s-%.2s%.2s-%.4s-%s}", + id->a4, id->a3, id->a2, id->a1, + id->b2, id->b1, id->c2, id->c1, id->d, id->e); + guid_data = NULL; + sprintf(if_name, "%s%d", "hn", device_get_unit(devs[devcnt])); + + if (strncmp(buf, (char *)umsg->body.kvp_ip_val.adapter_id, 39) == 0) { + strcpy((char *)umsg->body.kvp_ip_val.adapter_id, if_name); + break; + } + } + free(devs, M_TEMP); + } + + /* Address Family , DHCP , SUBNET, Gateway, DNS */ + umsg->kvp_hdr.operation = host_ip_msg->operation; + umsg->body.kvp_ip_val.addr_family = host_ip_msg->kvp_ip_val.addr_family; + umsg->body.kvp_ip_val.dhcp_enabled = host_ip_msg->kvp_ip_val.dhcp_enabled; + utf16_to_utf8((char *)umsg->body.kvp_ip_val.sub_net, MAX_IP_ADDR_SIZE, + (uint16_t *)host_ip_msg->kvp_ip_val.sub_net, + MAX_IP_ADDR_SIZE, + UNUSED_FLAG, + &err_subnet); + + utf16_to_utf8((char *)umsg->body.kvp_ip_val.gate_way, MAX_GATEWAY_SIZE, + (uint16_t *)host_ip_msg->kvp_ip_val.gate_way, + MAX_GATEWAY_SIZE, + UNUSED_FLAG, + &err_gway); + + utf16_to_utf8((char *)umsg->body.kvp_ip_val.dns_addr, MAX_IP_ADDR_SIZE, + (uint16_t *)host_ip_msg->kvp_ip_val.dns_addr, + MAX_IP_ADDR_SIZE, + UNUSED_FLAG, + &err_dns); + + return (err_ip | err_subnet | err_gway | err_dns | err_adap); +} + + +/* + * Prepare a user kvp msg based on host kvp msg (utf16 to utf8) + * Ensure utf16_utf8 takes care of the additional string terminating char!! + */ +static void +hv_kvp_convert_hostmsg_to_usermsg(void) +{ + int utf_err = 0; + uint32_t value_type; + struct hv_kvp_ip_msg *host_ip_msg = (struct hv_kvp_ip_msg *) + kvp_globals.host_kvp_msg; + + struct hv_kvp_msg *hmsg = kvp_globals.host_kvp_msg; + struct hv_kvp_msg *umsg = &kvp_globals.daemon_kvp_msg; + + memset(umsg, 0, sizeof(struct hv_kvp_msg)); + + umsg->kvp_hdr.operation = hmsg->kvp_hdr.operation; + umsg->kvp_hdr.pool = hmsg->kvp_hdr.pool; + + switch (umsg->kvp_hdr.operation) { + case HV_KVP_OP_SET_IP_INFO: + hv_kvp_convert_utf16_ipinfo_to_utf8(host_ip_msg, umsg); + break; + + case HV_KVP_OP_GET_IP_INFO: + utf16_to_utf8((char *)umsg->body.kvp_ip_val.adapter_id, + MAX_ADAPTER_ID_SIZE, + (uint16_t *)host_ip_msg->kvp_ip_val.adapter_id, + MAX_ADAPTER_ID_SIZE, 1, &utf_err); + + umsg->body.kvp_ip_val.addr_family = + host_ip_msg->kvp_ip_val.addr_family; + break; + + case HV_KVP_OP_SET: + value_type = hmsg->body.kvp_set.data.value_type; + + switch (value_type) { + case HV_REG_SZ: + umsg->body.kvp_set.data.value_size = + utf16_to_utf8( + (char *)umsg->body.kvp_set.data.msg_value.value, + HV_KVP_EXCHANGE_MAX_VALUE_SIZE - 1, + (uint16_t *)hmsg->body.kvp_set.data.msg_value.value, + hmsg->body.kvp_set.data.value_size, + 1, &utf_err); + /* utf8 encoding */ + umsg->body.kvp_set.data.value_size = + umsg->body.kvp_set.data.value_size / 2; + break; + + case HV_REG_U32: + umsg->body.kvp_set.data.value_size = + sprintf(umsg->body.kvp_set.data.msg_value.value, "%d", + hmsg->body.kvp_set.data.msg_value.value_u32) + 1; + break; + + case HV_REG_U64: + umsg->body.kvp_set.data.value_size = + sprintf(umsg->body.kvp_set.data.msg_value.value, "%llu", + (unsigned long long) + hmsg->body.kvp_set.data.msg_value.value_u64) + 1; + break; + } + + umsg->body.kvp_set.data.key_size = + utf16_to_utf8( + umsg->body.kvp_set.data.key, + HV_KVP_EXCHANGE_MAX_KEY_SIZE - 1, + (uint16_t *)hmsg->body.kvp_set.data.key, + hmsg->body.kvp_set.data.key_size, + 1, &utf_err); + + /* utf8 encoding */ + umsg->body.kvp_set.data.key_size = + umsg->body.kvp_set.data.key_size / 2; + break; + + case HV_KVP_OP_GET: + umsg->body.kvp_get.data.key_size = + utf16_to_utf8(umsg->body.kvp_get.data.key, + HV_KVP_EXCHANGE_MAX_KEY_SIZE - 1, + (uint16_t *)hmsg->body.kvp_get.data.key, + hmsg->body.kvp_get.data.key_size, + 1, &utf_err); + /* utf8 encoding */ + umsg->body.kvp_get.data.key_size = + umsg->body.kvp_get.data.key_size / 2; + break; + + case HV_KVP_OP_DELETE: + umsg->body.kvp_delete.key_size = + utf16_to_utf8(umsg->body.kvp_delete.key, + HV_KVP_EXCHANGE_MAX_KEY_SIZE - 1, + (uint16_t *)hmsg->body.kvp_delete.key, + hmsg->body.kvp_delete.key_size, + 1, &utf_err); + /* utf8 encoding */ + umsg->body.kvp_delete.key_size = + umsg->body.kvp_delete.key_size / 2; + break; + + case HV_KVP_OP_ENUMERATE: + umsg->body.kvp_enum_data.index = + hmsg->body.kvp_enum_data.index; + break; + + default: + hv_kvp_log_info("%s: daemon_kvp_msg: Invalid operation : %d\n", + __func__, umsg->kvp_hdr.operation); + } +} + + +/* + * Prepare a host kvp msg based on user kvp msg (utf8 to utf16) + */ +static int +hv_kvp_convert_usermsg_to_hostmsg(void) +{ + int hkey_len = 0, hvalue_len = 0, utf_err = 0; + struct hv_kvp_exchg_msg_value *host_exchg_data; + char *key_name, *value; + + struct hv_kvp_msg *umsg = &kvp_globals.daemon_kvp_msg; + struct hv_kvp_msg *hmsg = kvp_globals.host_kvp_msg; + struct hv_kvp_ip_msg *host_ip_msg = (struct hv_kvp_ip_msg *)hmsg; + + switch (hmsg->kvp_hdr.operation) { + case HV_KVP_OP_GET_IP_INFO: + return (hv_kvp_convert_utf8_ipinfo_to_utf16(umsg, host_ip_msg)); + + case HV_KVP_OP_SET_IP_INFO: + case HV_KVP_OP_SET: + case HV_KVP_OP_DELETE: + return (KVP_SUCCESS); + + case HV_KVP_OP_ENUMERATE: + host_exchg_data = &hmsg->body.kvp_enum_data.data; + key_name = umsg->body.kvp_enum_data.data.key; + hkey_len = utf8_to_utf16((uint16_t *)host_exchg_data->key, + ((HV_KVP_EXCHANGE_MAX_KEY_SIZE / 2) - 2), + key_name, strlen(key_name), + 1, &utf_err); + /* utf16 encoding */ + host_exchg_data->key_size = 2 * (hkey_len + 1); + value = umsg->body.kvp_enum_data.data.msg_value.value; + hvalue_len = utf8_to_utf16( + (uint16_t *)host_exchg_data->msg_value.value, + ((HV_KVP_EXCHANGE_MAX_VALUE_SIZE / 2) - 2), + value, strlen(value), + 1, &utf_err); + host_exchg_data->value_size = 2 * (hvalue_len + 1); + host_exchg_data->value_type = HV_REG_SZ; + + if ((hkey_len < 0) || (hvalue_len < 0)) + return (HV_KVP_E_FAIL); + + return (KVP_SUCCESS); + + case HV_KVP_OP_GET: + host_exchg_data = &hmsg->body.kvp_get.data; + value = umsg->body.kvp_get.data.msg_value.value; + hvalue_len = utf8_to_utf16( + (uint16_t *)host_exchg_data->msg_value.value, + ((HV_KVP_EXCHANGE_MAX_VALUE_SIZE / 2) - 2), + value, strlen(value), + 1, &utf_err); + /* Convert value size to uft16 */ + host_exchg_data->value_size = 2 * (hvalue_len + 1); + /* Use values by string */ + host_exchg_data->value_type = HV_REG_SZ; + + if ((hkey_len < 0) || (hvalue_len < 0)) + return (HV_KVP_E_FAIL); + + return (KVP_SUCCESS); + + default: + return (HV_KVP_E_FAIL); + } +} + + +/* + * Send the response back to the host. + */ +static void +hv_kvp_respond_host(int error) +{ + struct hv_vmbus_icmsg_hdr *hv_icmsg_hdrp; + + hv_icmsg_hdrp = (struct hv_vmbus_icmsg_hdr *) + &kvp_globals.rcv_buf[sizeof(struct hv_vmbus_pipe_hdr)]; + + if (error) + error = HV_KVP_E_FAIL; + + hv_icmsg_hdrp->status = error; + hv_icmsg_hdrp->icflags = HV_ICMSGHDRFLAG_TRANSACTION | HV_ICMSGHDRFLAG_RESPONSE; + + error = hv_vmbus_channel_send_packet(kvp_globals.channelp, + kvp_globals.rcv_buf, + kvp_globals.host_msg_len, kvp_globals.host_msg_id, + HV_VMBUS_PACKET_TYPE_DATA_IN_BAND, 0); + + if (error) + hv_kvp_log_info("%s: hv_kvp_respond_host: sendpacket error:%d\n", + __func__, error); +} + + +/* + * This is the main kvp kernel process that interacts with both user daemon + * and the host + */ +static void +hv_kvp_send_msg_to_daemon(void) +{ + /* Prepare kvp_msg to be sent to user */ + hv_kvp_convert_hostmsg_to_usermsg(); + + /* Send the msg to user via function deamon_read - setting sema */ + sema_post(&kvp_globals.dev_sema); +} + + +/* + * Function to read the kvp request buffer from host + * and interact with daemon + */ +static void +hv_kvp_process_request(void *context) +{ + uint8_t *kvp_buf; + hv_vmbus_channel *channel = context; + uint32_t recvlen = 0; + uint64_t requestid; + struct hv_vmbus_icmsg_hdr *icmsghdrp; + int ret = 0; + uint64_t pending_cnt = 1; + + hv_kvp_log_info("%s: entering hv_kvp_process_request\n", __func__); + kvp_buf = receive_buffer[HV_KVP]; + ret = hv_vmbus_channel_recv_packet(channel, kvp_buf, 2 * PAGE_SIZE, + &recvlen, &requestid); + + /* + * We start counting only after the daemon registers + * and therefore there could be requests pending in + * the VMBus that are not reflected in pending_cnt. + * Therefore we continue reading as long as either of + * the below conditions is true. + */ + + while ((pending_cnt>0) || ((ret == 0) && (recvlen > 0))) { + + if ((ret == 0) && (recvlen>0)) { + + icmsghdrp = (struct hv_vmbus_icmsg_hdr *) + &kvp_buf[sizeof(struct hv_vmbus_pipe_hdr)]; + + hv_kvp_transaction_init(recvlen, channel, requestid, kvp_buf); + if (icmsghdrp->icmsgtype == HV_ICMSGTYPE_NEGOTIATE) { + hv_kvp_negotiate_version(icmsghdrp, NULL, kvp_buf); + hv_kvp_respond_host(ret); + + /* + * It is ok to not acquire the mutex before setting + * req_in_progress here because negotiation is the + * first thing that happens and hence there is no + * chance of a race condition. + */ + + kvp_globals.req_in_progress = false; + hv_kvp_log_info("%s :version negotiated\n", __func__); + + } else { + if (!kvp_globals.daemon_busy) { + + hv_kvp_log_info("%s: issuing qury to daemon\n", __func__); + mtx_lock(&kvp_globals.pending_mutex); + kvp_globals.req_timed_out = false; + kvp_globals.daemon_busy = true; + mtx_unlock(&kvp_globals.pending_mutex); + + hv_kvp_send_msg_to_daemon(); + hv_kvp_log_info("%s: waiting for daemon\n", __func__); + } + + /* Wait 5 seconds for daemon to respond back */ + tsleep(&kvp_globals, 0, "kvpworkitem", 5 * hz); + hv_kvp_log_info("%s: came out of wait\n", __func__); + } + } + + mtx_lock(&kvp_globals.pending_mutex); + + /* Notice that once req_timed_out is set to true + * it will remain true until the next request is + * sent to the daemon. The response from daemon + * is forwarded to host only when this flag is + * false. + */ + kvp_globals.req_timed_out = true; + + /* + * Cancel request if so need be. + */ + if (hv_kvp_req_in_progress()) { + hv_kvp_log_info("%s: request was still active after wait so failing\n", __func__); + hv_kvp_respond_host(HV_KVP_E_FAIL); + kvp_globals.req_in_progress = false; + } + + /* + * Decrement pending request count and + */ + if (kvp_globals.pending_reqs>0) { + kvp_globals.pending_reqs = kvp_globals.pending_reqs - 1; + } + pending_cnt = kvp_globals.pending_reqs; + + mtx_unlock(&kvp_globals.pending_mutex); + + /* + * Try reading next buffer + */ + recvlen = 0; + ret = hv_vmbus_channel_recv_packet(channel, kvp_buf, 2 * PAGE_SIZE, + &recvlen, &requestid); + hv_kvp_log_info("%s: read: context %p, pending_cnt %ju ret =%d, recvlen=%d\n", + __func__, context, pending_cnt, ret, recvlen); + } +} + + +/* + * Callback routine that gets called whenever there is a message from host + */ +void +hv_kvp_callback(void *context) +{ + uint64_t pending_cnt = 0; + + if (kvp_globals.register_done == false) { + + kvp_globals.channelp = context; + } else { + + mtx_lock(&kvp_globals.pending_mutex); + kvp_globals.pending_reqs = kvp_globals.pending_reqs + 1; + pending_cnt = kvp_globals.pending_reqs; + mtx_unlock(&kvp_globals.pending_mutex); + if (pending_cnt == 1) { + hv_kvp_log_info("%s: Queuing work item\n", __func__); + hv_queue_work_item( + service_table[HV_KVP].work_queue, + hv_kvp_process_request, + context + ); + } + } +} + + +/* + * This function is called by the hv_kvp_init - + * creates character device hv_kvp_dev + * allocates memory to hv_kvp_dev_buf + * + */ +static int +hv_kvp_dev_init(void) +{ + int error = 0; + + /* initialize semaphore */ + sema_init(&kvp_globals.dev_sema, 0, "hv_kvp device semaphore"); + /* create character device */ + error = make_dev_p(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK, + &hv_kvp_dev, + &hv_kvp_cdevsw, + 0, + UID_ROOT, + GID_WHEEL, + 0640, + "hv_kvp_dev"); + + if (error != 0) + return (error); + + /* + * Malloc with M_WAITOK flag will never fail. + */ + hv_kvp_dev_buf = malloc(sizeof(*hv_kvp_dev_buf), M_HV_KVP_DEV_BUF, M_WAITOK | + M_ZERO); + + return (0); +} + + +/* + * This function is called by the hv_kvp_deinit - + * destroy character device + */ +static void +hv_kvp_dev_destroy(void) +{ + + if (daemon_task != NULL) { + PROC_LOCK(daemon_task); + kern_psignal(daemon_task, SIGKILL); + PROC_UNLOCK(daemon_task); + } + + destroy_dev(hv_kvp_dev); + free(hv_kvp_dev_buf, M_HV_KVP_DEV_BUF); + return; +} + + +static int +hv_kvp_dev_open(struct cdev *dev, int oflags, int devtype, + struct thread *td) +{ + + hv_kvp_log_info("%s: Opened device \"hv_kvp_device\" successfully.\n", __func__); + if (kvp_globals.dev_accessed) + return (-EBUSY); + + daemon_task = curproc; + kvp_globals.dev_accessed = true; + kvp_globals.daemon_busy = false; + return (0); +} + + +static int +hv_kvp_dev_close(struct cdev *dev __unused, int fflag __unused, int devtype __unused, + struct thread *td __unused) +{ + + hv_kvp_log_info("%s: Closing device \"hv_kvp_device\".\n", __func__); + kvp_globals.dev_accessed = false; + kvp_globals.register_done = false; + return (0); +} + + +/* + * hv_kvp_daemon read invokes this function + * acts as a send to daemon + */ +static int +hv_kvp_dev_daemon_read(struct cdev *dev __unused, struct uio *uio, int ioflag __unused) +{ + size_t amt; + int error = 0; + + /* Check hv_kvp daemon registration status*/ + if (!kvp_globals.register_done) + return (KVP_ERROR); + + sema_wait(&kvp_globals.dev_sema); + + memcpy(hv_kvp_dev_buf, &kvp_globals.daemon_kvp_msg, sizeof(struct hv_kvp_msg)); + + amt = MIN(uio->uio_resid, uio->uio_offset >= BUFFERSIZE + 1 ? 0 : + BUFFERSIZE + 1 - uio->uio_offset); + + if ((error = uiomove(hv_kvp_dev_buf, amt, uio)) != 0) + hv_kvp_log_info("%s: hv_kvp uiomove read failed!\n", __func__); + + return (error); +} + + +/* + * hv_kvp_daemon write invokes this function + * acts as a recieve from daemon + */ +static int +hv_kvp_dev_daemon_write(struct cdev *dev __unused, struct uio *uio, int ioflag __unused) +{ + size_t amt; + int error = 0; + + uio->uio_offset = 0; + + amt = MIN(uio->uio_resid, BUFFERSIZE); + error = uiomove(hv_kvp_dev_buf, amt, uio); + + if (error != 0) + return (error); + + memcpy(&kvp_globals.daemon_kvp_msg, hv_kvp_dev_buf, sizeof(struct hv_kvp_msg)); + + if (kvp_globals.register_done == false) { + if (kvp_globals.daemon_kvp_msg.kvp_hdr.operation == HV_KVP_OP_REGISTER) { + + kvp_globals.register_done = true; + if (kvp_globals.channelp) { + + hv_kvp_callback(kvp_globals.channelp); + } + } + else { + hv_kvp_log_info("%s, KVP Registration Failed\n", __func__); + return (KVP_ERROR); + } + } else { + + mtx_lock(&kvp_globals.pending_mutex); + + if(!kvp_globals.req_timed_out) { + + hv_kvp_convert_usermsg_to_hostmsg(); + hv_kvp_respond_host(KVP_SUCCESS); + wakeup(&kvp_globals); + kvp_globals.req_in_progress = false; + } + + kvp_globals.daemon_busy = false; + mtx_unlock(&kvp_globals.pending_mutex); + } + + return (error); +} + + +/* + * hv_kvp_daemon poll invokes this function to check if data is available + * for daemon to read. + */ +static int +hv_kvp_dev_daemon_poll(struct cdev *dev __unused, int events, struct thread *td __unused) +{ + int revents = 0; + + mtx_lock(&kvp_globals.pending_mutex); + /* + * We check global flag daemon_busy for the data availiability for + * userland to read. Deamon_busy is set to true before driver has data + * for daemon to read. It is set to false after daemon sends + * then response back to driver. + */ + if (kvp_globals.daemon_busy == true) + revents = POLLIN; + mtx_unlock(&kvp_globals.pending_mutex); + + return (revents); +} + + +/* + * hv_kvp initialization function + * called from hv_util service. + * + */ +int +hv_kvp_init(hv_vmbus_service *srv) +{ + int error = 0; + hv_work_queue *work_queue = NULL; + + memset(&kvp_globals, 0, sizeof(kvp_globals)); + + work_queue = hv_work_queue_create("KVP Service"); + if (work_queue == NULL) { + hv_kvp_log_info("%s: Work queue alloc failed\n", __func__); + error = ENOMEM; + hv_kvp_log_error("%s: ENOMEM\n", __func__); + goto Finish; + } + srv->work_queue = work_queue; + + error = hv_kvp_dev_init(); + mtx_init(&kvp_globals.pending_mutex, "hv-kvp pending mutex", + NULL, MTX_DEF); + kvp_globals.pending_reqs = 0; + + +Finish: + return (error); +} + + +void +hv_kvp_deinit(void) +{ + hv_kvp_dev_destroy(); + mtx_destroy(&kvp_globals.pending_mutex); + + return; +} diff --git a/sys/dev/hyperv/utilities/hv_kvp.h b/sys/dev/hyperv/utilities/hv_kvp.h index f7dccf77d..b67373fa7 100644 --- a/sys/dev/hyperv/utilities/hv_kvp.h +++ b/sys/dev/hyperv/utilities/hv_kvp.h @@ -1,7 +1,5 @@ /*- - * Copyright (c) 2009-2012 Microsoft Corp. - * Copyright (c) 2012 NetApp Inc. - * Copyright (c) 2012 Citrix Inc. + * Copyright (c) 2014 Microsoft Corp. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,13 +22,15 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ */ #ifndef _KVP_H #define _KVP_H /* - * An implementation of HyperV key value pair (KVP) functionality for FreeBSD + * An implementation of HyperV key value pair (KVP) functionality for FreeBSD * */ @@ -53,15 +53,15 @@ /* * bytes, including any null terminators */ -#define HV_KVP_EXCHANGE_MAX_VALUE_SIZE (2048) +#define HV_KVP_EXCHANGE_MAX_VALUE_SIZE (2048) /* * Maximum key size - the registry limit for the length of an entry name * is 256 characters, including the null terminator */ +#define HV_KVP_EXCHANGE_MAX_KEY_SIZE (512) -#define HV_KVP_EXCHANGE_MAX_KEY_SIZE (512) /* * In FreeBSD, we implement the KVP functionality in two components: @@ -111,30 +111,20 @@ * (not supported), a NULL key string is returned. */ - + /* * Registry value types. */ +#define HV_REG_SZ 1 +#define HV_REG_U32 4 +#define HV_REG_U64 8 -#define HV_REG_SZ 1 -#define HV_REG_U32 4 -#define HV_REG_U64 8 - - -/* - * Daemon code not supporting IP injection (legacy daemon). - */ - -#define HV_KVP_OP_REGISTER 4 /* * Daemon code supporting IP injection. - * The KVP opcode field is used to communicate the - * registration information; so define a namespace that - * will be distinct from the host defined KVP opcode. */ +#define HV_KVP_OP_REGISTER 4 -#define KVP_OP_REGISTER1 100 enum hv_kvp_exchg_op { HV_KVP_OP_GET = 0, @@ -155,41 +145,41 @@ enum hv_kvp_exchg_pool { HV_KVP_POOL_COUNT /* Number of pools, must be last. */ }; + /* * Some Hyper-V status codes. */ -#define HV_KVP_S_OK 0x00000000 -#define HV_KVP_E_FAIL 0x80004005 -#define HV_KVP_S_CONT 0x80070103 -#define HV_ERROR_NOT_SUPPORTED 0x80070032 -#define HV_ERROR_MACHINE_LOCKED 0x800704F7 -#define HV_ERROR_DEVICE_NOT_CONNECTED 0x8007048F -#define HV_INVALIDARG 0x80070057 -#define HV_KVP_GUID_NOTFOUND 0x80041002 +#define HV_KVP_S_OK 0x00000000 +#define HV_KVP_E_FAIL 0x80004005 +#define HV_KVP_S_CONT 0x80070103 +#define HV_ERROR_NOT_SUPPORTED 0x80070032 +#define HV_ERROR_MACHINE_LOCKED 0x800704F7 +#define HV_ERROR_DEVICE_NOT_CONNECTED 0x8007048F +#define HV_INVALIDARG 0x80070057 +#define HV_KVP_GUID_NOTFOUND 0x80041002 -#define ADDR_FAMILY_NONE 0x00 -#define ADDR_FAMILY_IPV4 0x01 -#define ADDR_FAMILY_IPV6 0x02 +#define ADDR_FAMILY_NONE 0x00 +#define ADDR_FAMILY_IPV4 0x01 +#define ADDR_FAMILY_IPV6 0x02 -#define MAX_ADAPTER_ID_SIZE 128 -#define MAX_IP_ADDR_SIZE 1024 -#define MAX_GATEWAY_SIZE 512 +#define MAX_ADAPTER_ID_SIZE 128 +#define MAX_IP_ADDR_SIZE 1024 +#define MAX_GATEWAY_SIZE 512 struct hv_kvp_ipaddr_value { - uint16_t adapter_id[MAX_ADAPTER_ID_SIZE]; - uint8_t addr_family; - uint8_t dhcp_enabled; - uint16_t ip_addr[MAX_IP_ADDR_SIZE]; - uint16_t sub_net[MAX_IP_ADDR_SIZE]; - uint16_t gate_way[MAX_GATEWAY_SIZE]; - uint16_t dns_addr[MAX_IP_ADDR_SIZE]; -} __attribute__((packed)); - + uint16_t adapter_id[MAX_ADAPTER_ID_SIZE]; + uint8_t addr_family; + uint8_t dhcp_enabled; + uint16_t ip_addr[MAX_IP_ADDR_SIZE]; + uint16_t sub_net[MAX_IP_ADDR_SIZE]; + uint16_t gate_way[MAX_GATEWAY_SIZE]; + uint16_t dns_addr[MAX_IP_ADDR_SIZE]; +}__attribute__((packed)); struct hv_kvp_hdr { - uint8_t operation; - uint8_t pool; + uint8_t operation; + uint8_t pool; uint16_t pad; } __attribute__((packed)); @@ -197,9 +187,9 @@ struct hv_kvp_exchg_msg_value { uint32_t value_type; uint32_t key_size; uint32_t value_size; - uint8_t key[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; + uint8_t key[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; union { - uint8_t value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; + uint8_t value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; uint32_t value_u32; uint64_t value_u64; } msg_value; @@ -229,16 +219,16 @@ struct hv_kvp_register { struct hv_kvp_msg { union { - struct hv_kvp_hdr kvp_hdr; - int error; + struct hv_kvp_hdr kvp_hdr; + uint32_t error; } hdr; union { - struct hv_kvp_msg_get kvp_get; - struct hv_kvp_msg_set kvp_set; + struct hv_kvp_msg_get kvp_get; + struct hv_kvp_msg_set kvp_set; struct hv_kvp_msg_delete kvp_delete; struct hv_kvp_msg_enumerate kvp_enum_data; - struct hv_kvp_ipaddr_value kvp_ip_val; - struct hv_kvp_register kvp_register; + struct hv_kvp_ipaddr_value kvp_ip_val; + struct hv_kvp_register kvp_register; } body; } __attribute__((packed)); @@ -248,38 +238,17 @@ struct hv_kvp_ip_msg { struct hv_kvp_ipaddr_value kvp_ip_val; } __attribute__((packed)); -#define BSD_SOC_PATH "/etc/hyperv/socket" - -#define HV_SHUT_DOWN 0 -#define HV_TIME_SYNCH 1 -#define HV_HEART_BEAT 2 -#define HV_KVP 3 -#define HV_MAX_UTIL_SERVICES 4 - -#define HV_WLTIMEDELTA 116444736000000000L /* in 100ns unit */ -#define HV_ICTIMESYNCFLAG_PROBE 0 -#define HV_ICTIMESYNCFLAG_SYNC 1 -#define HV_ICTIMESYNCFLAG_SAMPLE 2 -#define HV_NANO_SEC_PER_SEC 1000000000 - -typedef struct hv_vmbus_service { - hv_guid guid; /* Hyper-V GUID */ - char* name; /* name of service */ - boolean_t enabled; /* service enabled */ - hv_work_queue* work_queue; /* background work queue */ - - // - // function to initialize service - // - int (*init)(struct hv_vmbus_service *); - - // - // function to process Hyper-V messages - // - void (*callback)(void *); -} hv_vmbus_service; - -extern uint8_t* receive_buffer[]; -extern hv_vmbus_service service_table[]; + +#define HV_SHUT_DOWN 0 +#define HV_TIME_SYNCH 1 +#define HV_HEART_BEAT 2 +#define HV_KVP 3 +#define HV_MAX_UTIL_SERVICES 4 + +#define HV_WLTIMEDELTA 116444736000000000L /* in 100ns unit */ +#define HV_ICTIMESYNCFLAG_PROBE 0 +#define HV_ICTIMESYNCFLAG_SYNC 1 +#define HV_ICTIMESYNCFLAG_SAMPLE 2 +#define HV_NANO_SEC_PER_SEC 1000000000 #endif /* _KVP_H */ diff --git a/sys/dev/hyperv/utilities/hv_util.c b/sys/dev/hyperv/utilities/hv_util.c index e86cbd784..91bbc5345 100644 --- a/sys/dev/hyperv/utilities/hv_util.c +++ b/sys/dev/hyperv/utilities/hv_util.c @@ -1,7 +1,5 @@ /*- - * Copyright (c) 2009-2012 Microsoft Corp. - * Copyright (c) 2012 NetApp Inc. - * Copyright (c) 2012 Citrix Inc. + * Copyright (c) 2014 Microsoft Corp. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,9 +22,11 @@ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ */ -/** +/* * A common driver for all hyper-V util services. */ @@ -53,7 +53,7 @@ static void hv_timesync_cb(void *context); static int hv_timesync_init(hv_vmbus_service *serv); -/** +/* * Note: GUID codes below are predefined by the host hypervisor * (Hyper-V and Azure)interface and required for correct operation. */ @@ -80,7 +80,16 @@ hv_vmbus_service service_table[] = { 0xab, 0x55, 0x38, 0x2f, 0x3b, 0xd5, 0x42, 0x2d}, .name = "Hyper-V Heartbeat Service\n", .enabled = TRUE, - .callback = hv_heartbeat_cb, + .callback = hv_heartbeat_cb, + }, + + /* KVP (Key Value Pair) Service */ + { .guid.data = {0xe7, 0xf4, 0xa0, 0xa9, 0x45, 0x5a, 0x96, 0x4d, + 0xb8, 0x27, 0x8a, 0x84, 0x1e, 0x8c, 0x3, 0xe6}, + .name = "Hyper-V KVP Service\n", + .enabled = TRUE, + .init = hv_kvp_init, + .callback = hv_kvp_callback, }, }; @@ -90,6 +99,8 @@ hv_vmbus_service service_table[] = { */ uint8_t *receive_buffer[HV_MAX_UTIL_SERVICES]; +static boolean_t destroyed_kvp = FALSE; + struct hv_ictimesync_data { uint64_t parenttime; uint64_t childtime; @@ -143,7 +154,7 @@ hv_negotiate_version( static void hv_set_host_time(void *context) { - time_sync_data *time_msg = context; + time_sync_data* time_msg = (time_sync_data*) context; uint64_t hosttime = time_msg->data; struct timespec guest_ts, host_ts; uint64_t host_tns; @@ -253,12 +264,12 @@ hv_timesync_cb(void *context) static void hv_shutdown_cb(void *context) { - uint8_t* buf; + uint8_t* buf; hv_vmbus_channel* channel = context; - uint8_t execute_shutdown = 0; + uint8_t execute_shutdown = 0; hv_vmbus_icmsg_hdr* icmsghdrp; - uint32_t recv_len; - uint64_t request_id; + uint32_t recv_len; + uint64_t request_id; int ret; hv_vmbus_shutdown_msg_data* shutdown_msg; @@ -421,6 +432,11 @@ hv_util_detach(device_t dev) struct hv_vmbus_service* service; size_t receive_buffer_offset; + if (!destroyed_kvp) { + hv_kvp_deinit(); + destroyed_kvp = TRUE; + } + hv_dev = vmbus_get_devctx(dev); hv_vmbus_channel_close(hv_dev->channel); @@ -432,21 +448,22 @@ hv_util_detach(device_t dev) free(receive_buffer[receive_buffer_offset], M_DEVBUF); receive_buffer[receive_buffer_offset] = NULL; - return (0); } -static void hv_util_init(void) +static void +hv_util_init(void) { } -static int hv_util_modevent(module_t mod, int event, void *arg) +static int +hv_util_modevent(module_t mod, int event, void *arg) { switch (event) { case MOD_LOAD: break; case MOD_UNLOAD: - break; + break; default: break; } diff --git a/sys/dev/hyperv/utilities/unicode.h b/sys/dev/hyperv/utilities/unicode.h new file mode 100644 index 000000000..696777cbb --- /dev/null +++ b/sys/dev/hyperv/utilities/unicode.h @@ -0,0 +1,201 @@ +/* $NetBSD: unicode.h,v 1.1.1.1 2007/03/06 00:10:39 dillo Exp $ */ + +/*- + * Copyright (c) 2007 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include + +#define UNICODE_DECOMPOSE 0x01 +#define UNICODE_PRECOMPOSE 0x02 +#define UNICODE_UTF8_LATIN1_FALLBACK 0x03 + +size_t utf8_to_utf16(uint16_t *, size_t, const char *, size_t, int, int *); +size_t utf16_to_utf8(char *, size_t, const uint16_t *, size_t, int, int *); + +size_t +utf8_to_utf16(uint16_t *dst, size_t dst_len, + const char *src, size_t src_len, + int flags, int *errp) +{ + const unsigned char *s; + size_t spos, dpos; + int error; + uint16_t c; + +#define IS_CONT(c) (((c)&0xc0) == 0x80) + + error = 0; + s = (const unsigned char *)src; + spos = dpos = 0; + while (spos= src_len || !IS_CONT(s[spos+1])) + && s[spos]>=0xa0) { + /* not valid UTF-8, assume ISO 8859-1 */ + c = s[spos++]; + } + else if (s[spos] < 0xc0 || s[spos] >= 0xf5) { + /* continuation byte without lead byte + or lead byte for codepoint above 0x10ffff */ + error++; + spos++; + continue; + } + else if (s[spos] < 0xe0) { + if (spos >= src_len || !IS_CONT(s[spos+1])) { + spos++; + error++; + continue; + } + c = ((s[spos] & 0x3f) << 6) | (s[spos+1] & 0x3f); + spos += 2; + if (c < 0x80) { + /* overlong encoding */ + error++; + continue; + } + } + else if (s[spos] < 0xf0) { + if (spos >= src_len-2 + || !IS_CONT(s[spos+1]) || !IS_CONT(s[spos+2])) { + spos++; + error++; + continue; + } + c = ((s[spos] & 0x0f) << 12) | ((s[spos+1] & 0x3f) << 6) + | (s[spos+2] & 0x3f); + spos += 3; + if (c < 0x800 || (c & 0xdf00) == 0xd800 ) { + /* overlong encoding or encoded surrogate */ + error++; + continue; + } + } + else { + uint32_t cc; + /* UTF-16 surrogate pair */ + + if (spos >= src_len-3 || !IS_CONT(s[spos+1]) + || !IS_CONT(s[spos+2]) || !IS_CONT(s[spos+3])) { + spos++; + error++; + + continue; + } + cc = ((s[spos] & 0x03) << 18) | ((s[spos+1] & 0x3f) << 12) + | ((s[spos+2] & 0x3f) << 6) | (s[spos+3] & 0x3f); + spos += 4; + if (cc < 0x10000) { + /* overlong encoding */ + error++; + continue; + } + if (dst && dpos < dst_len) + dst[dpos] = (0xd800 | ((cc-0x10000)>>10)); + dpos++; + c = 0xdc00 | ((cc-0x10000) & 0x3ffff); + } + + if (dst && dpos < dst_len) + dst[dpos] = c; + dpos++; + } + + if (errp) + *errp = error; + + return dpos; + +#undef IS_CONT +} + + +size_t +utf16_to_utf8(char *dst, size_t dst_len, + const uint16_t *src, size_t src_len, + int flags, int *errp) +{ + uint16_t spos, dpos; + int error; + +#define CHECK_LENGTH(l) (dpos > dst_len-(l) ? dst=NULL : NULL) +#define ADD_BYTE(b) (dst ? dst[dpos] = (b) : 0, dpos++) + + error = 0; + dpos = 0; + for (spos=0; spos>6)); + ADD_BYTE(0x80 | (src[spos] & 0x3f)); + } + else if ((src[spos] & 0xdc00) == 0xd800) { + uint32_t c; + /* first surrogate */ + if (spos == src_len - 1 || (src[spos] & 0xdc00) != 0xdc00) { + /* no second surrogate present */ + error++; + continue; + } + spos++; + CHECK_LENGTH(4); + c = (((src[spos]&0x3ff) << 10) | (src[spos+1]&0x3ff)) + 0x10000; + ADD_BYTE(0xf0 | (c>>18)); + ADD_BYTE(0x80 | ((c>>12) & 0x3f)); + ADD_BYTE(0x80 | ((c>>6) & 0x3f)); + ADD_BYTE(0x80 | (c & 0x3f)); + } + else if ((src[spos] & 0xdc00) == 0xdc00) { + /* second surrogate without preceding first surrogate */ + error++; + } + else { + CHECK_LENGTH(3); + ADD_BYTE(0xe0 | src[spos]>>12); + ADD_BYTE(0x80 | ((src[spos]>>6) & 0x3f)); + ADD_BYTE(0x80 | (src[spos] & 0x3f)); + } + } + + if (errp) + *errp = error; + + return dpos; + +#undef ADD_BYTE +#undef CHECK_LENGTH +} diff --git a/sys/modules/hyperv/utilities/Makefile b/sys/modules/hyperv/utilities/Makefile index 57a242801..f94e4410a 100644 --- a/sys/modules/hyperv/utilities/Makefile +++ b/sys/modules/hyperv/utilities/Makefile @@ -3,7 +3,7 @@ .PATH: ${.CURDIR}/../../../dev/hyperv/utilities KMOD= hv_utils -SRCS= hv_util.c +SRCS= hv_util.c hv_kvp.c SRCS+= bus_if.h device_if.h CFLAGS+= -I${.CURDIR}/../../../dev/hyperv/include \ diff --git a/tools/build/mk/OptionalObsoleteFiles.inc b/tools/build/mk/OptionalObsoleteFiles.inc index 62542b711..cfa369b40 100644 --- a/tools/build/mk/OptionalObsoleteFiles.inc +++ b/tools/build/mk/OptionalObsoleteFiles.inc @@ -4670,3 +4670,11 @@ OLD_FILES+=usr/bin/svnserve OLD_FILES+=usr/bin/svnsync OLD_FILES+=usr/bin/svnversion .endif + +.if ${MK_HYPERV} == no +OLD_FILES+=etc/devd/hyperv.conf +OLD_FILES+=usr/libexec/hyperv/hv_set_ifconfig +OLD_FILES+=usr/libexec/hyperv/hv_get_dns_info +OLD_FILES+=usr/libexec/hyperv/hv_get_dhcp_info +OLD_FILES+=usr/sbin/hv_kvpd +.endif diff --git a/tools/build/options/WITHOUT_HYPERV b/tools/build/options/WITHOUT_HYPERV new file mode 100644 index 000000000..ef63f70ea --- /dev/null +++ b/tools/build/options/WITHOUT_HYPERV @@ -0,0 +1,2 @@ +.\" $FreeBSD$ +Set to not build or install HyperV utilities. diff --git a/tools/build/options/WITH_HYPERV b/tools/build/options/WITH_HYPERV new file mode 100644 index 000000000..a6bdf72ff --- /dev/null +++ b/tools/build/options/WITH_HYPERV @@ -0,0 +1,2 @@ +.\" $FreeBSD$ +Set to build and install HyperV utilities. diff --git a/usr.sbin/Makefile.amd64 b/usr.sbin/Makefile.amd64 index b82162580..2d1a3e8da 100644 --- a/usr.sbin/Makefile.amd64 +++ b/usr.sbin/Makefile.amd64 @@ -18,6 +18,9 @@ SUBDIR+= boot0cfg SUBDIR+= btxld .endif SUBDIR+= cpucontrol +.if ${MK_HYPERV} != "no" +SUBDIR+= hyperv +.endif SUBDIR+= kgmon SUBDIR+= lptcontrol SUBDIR+= mount_smbfs diff --git a/usr.sbin/Makefile.i386 b/usr.sbin/Makefile.i386 index 7d66f783f..de7bb464f 100644 --- a/usr.sbin/Makefile.i386 +++ b/usr.sbin/Makefile.i386 @@ -9,6 +9,9 @@ SUBDIR+= asf SUBDIR+= btxld .endif SUBDIR+= cpucontrol +.if ${MK_HYPERV} != "no" +SUBDIR+= hyperv +.endif SUBDIR+= kgmon SUBDIR+= kgzip SUBDIR+= lptcontrol diff --git a/usr.sbin/hyperv/Makefile b/usr.sbin/hyperv/Makefile new file mode 100644 index 000000000..c11b3417c --- /dev/null +++ b/usr.sbin/hyperv/Makefile @@ -0,0 +1,7 @@ +# $FreeBSD$ + +.include + +SUBDIR = tools + +.include diff --git a/usr.sbin/hyperv/Makefile.inc b/usr.sbin/hyperv/Makefile.inc new file mode 100644 index 000000000..265f86d1e --- /dev/null +++ b/usr.sbin/hyperv/Makefile.inc @@ -0,0 +1,3 @@ +# $FreeBSD$ + +.include "../Makefile.inc" diff --git a/usr.sbin/hyperv/tools/Makefile b/usr.sbin/hyperv/tools/Makefile new file mode 100644 index 000000000..3cfc0013c --- /dev/null +++ b/usr.sbin/hyperv/tools/Makefile @@ -0,0 +1,13 @@ +# $FreeBSD$ + +.include + +HV_KVP_DAEMON_DISTDIR?= ${.CURDIR}/../../../contrib/hyperv/tools +.PATH: ${HV_KVP_DAEMON_DISTDIR} + +PROG= hv_kvp_daemon +MAN= hv_kvp_daemon.8 + +CFLAGS+= -I${.CURDIR}/../../../sys/dev/hyperv/utilities + +.include -- 2.42.0