From 9b5957803cb444a99275355eb2309b6fecc63c5f Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Mon, 13 Aug 2012 10:06:51 -0700 Subject: Drivers: hv: Add KVP definitions for IP address injection Add the necessary definitions for supporting the IP injection functionality. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Reviewed-by: Olaf Hering Reviewed-by: Ben Hutchings Signed-off-by: Greg Kroah-Hartman --- include/linux/hyperv.h | 76 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 2 deletions(-) (limited to 'include/linux/hyperv.h') diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 68ed7f7e1fc9..11afc4e0849a 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -122,12 +122,53 @@ #define REG_U32 4 #define REG_U64 8 +/* + * As we look at expanding the KVP functionality to include + * IP injection functionality, we need to maintain binary + * compatibility with older daemons. + * + * The KVP opcodes are defined by the host and it was unfortunate + * that I chose to treat the registration operation as part of the + * KVP operations defined by the host. + * Here is the level of compatibility + * (between the user level daemon and the kernel KVP driver) that we + * will implement: + * + * An older daemon will always be supported on a newer driver. + * A given user level daemon will require a minimal version of the + * kernel driver. + * If we cannot handle the version differences, we will fail gracefully + * (this can happen when we have a user level daemon that is more + * advanced than the KVP driver. + * + * We will use values used in this handshake for determining if we have + * workable user level daemon and the kernel driver. We begin by taking the + * registration opcode out of the KVP opcode namespace. We will however, + * maintain compatibility with the existing user-level daemon code. + */ + +/* + * Daemon code not supporting IP injection (legacy daemon). + */ + +#define 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 KVP_OP_REGISTER1 100 + enum hv_kvp_exchg_op { KVP_OP_GET = 0, KVP_OP_SET, KVP_OP_DELETE, KVP_OP_ENUMERATE, - KVP_OP_REGISTER, + KVP_OP_GET_IP_INFO, + KVP_OP_SET_IP_INFO, KVP_OP_COUNT /* Number of operations, must be last. */ }; @@ -140,6 +181,26 @@ enum hv_kvp_exchg_pool { KVP_POOL_COUNT /* Number of pools, must be last. */ }; +#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 + + +struct hv_kvp_ipaddr_value { + __u16 adapter_id[MAX_ADAPTER_ID_SIZE]; + __u8 addr_family; + __u8 dhcp_enabled; + __u16 ip_addr[MAX_IP_ADDR_SIZE]; + __u16 sub_net[MAX_IP_ADDR_SIZE]; + __u16 gate_way[MAX_GATEWAY_SIZE]; + __u16 dns_addr[MAX_IP_ADDR_SIZE]; +} __attribute__((packed)); + + struct hv_kvp_hdr { __u8 operation; __u8 pool; @@ -181,16 +242,26 @@ struct hv_kvp_register { }; struct hv_kvp_msg { - struct hv_kvp_hdr kvp_hdr; + union { + struct hv_kvp_hdr kvp_hdr; + int error; + }; union { 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; } body; } __attribute__((packed)); +struct hv_kvp_ip_msg { + __u8 operation; + __u8 pool; + struct hv_kvp_ipaddr_value kvp_ip_val; +} __attribute__((packed)); + #ifdef __KERNEL__ #include #include @@ -982,6 +1053,7 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver); #define HV_S_CONT 0x80070103 #define HV_ERROR_NOT_SUPPORTED 0x80070032 #define HV_ERROR_MACHINE_LOCKED 0x800704F7 +#define HV_ERROR_DEVICE_NOT_CONNECTED 0x8007048F /* * While we want to handle util services as regular devices, -- cgit v1.2.3 From b47a81dcc5a806efb6d970608299129771588289 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Mon, 13 Aug 2012 10:06:52 -0700 Subject: Drivers: hv: kvp: Cleanup error handling in KVP In preparation to implementing IP injection, cleanup the way we propagate and handle errors both in the driver as well as in the user level daemon. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Reviewed-by: Olaf Hering Reviewed-by: Ben Hutchings Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_kvp.c | 114 +++++++++++++++++++++++++++++++++++++---------- include/linux/hyperv.h | 17 ++++--- tools/hv/hv_kvp_daemon.c | 66 +++++++++++++-------------- 3 files changed, 135 insertions(+), 62 deletions(-) (limited to 'include/linux/hyperv.h') diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c index 0012eed6d872..eb4d0730f44d 100644 --- a/drivers/hv/hv_kvp.c +++ b/drivers/hv/hv_kvp.c @@ -48,13 +48,24 @@ static struct { void *kvp_context; /* for the channel callback */ } kvp_transaction; +/* + * Before we can accept KVP messages from the host, we need + * to handshake with the user level daemon. This state tracks + * if we are in the handshake phase. + */ +static bool in_hand_shake = true; + +/* + * This state maintains the version number registered by the daemon. + */ +static int dm_reg_value; + static void kvp_send_key(struct work_struct *dummy); -#define TIMEOUT_FIRED 1 static void kvp_respond_to_host(char *key, char *value, int error); static void kvp_work_func(struct work_struct *dummy); -static void kvp_register(void); +static void kvp_register(int); static DECLARE_DELAYED_WORK(kvp_work, kvp_work_func); static DECLARE_WORK(kvp_sendkey_work, kvp_send_key); @@ -68,7 +79,7 @@ static u8 *recv_buffer; */ static void -kvp_register(void) +kvp_register(int reg_value) { struct cn_msg *msg; @@ -83,7 +94,7 @@ kvp_register(void) msg->id.idx = CN_KVP_IDX; msg->id.val = CN_KVP_VAL; - kvp_msg->kvp_hdr.operation = KVP_OP_REGISTER; + kvp_msg->kvp_hdr.operation = reg_value; strcpy(version, HV_DRV_VERSION); msg->len = sizeof(struct hv_kvp_msg); cn_netlink_send(msg, 0, GFP_ATOMIC); @@ -97,9 +108,43 @@ kvp_work_func(struct work_struct *dummy) * If the timer fires, the user-mode component has not responded; * process the pending transaction. */ - kvp_respond_to_host("Unknown key", "Guest timed out", TIMEOUT_FIRED); + kvp_respond_to_host("Unknown key", "Guest timed out", HV_E_FAIL); +} + +static int kvp_handle_handshake(struct hv_kvp_msg *msg) +{ + int ret = 1; + + switch (msg->kvp_hdr.operation) { + case KVP_OP_REGISTER: + dm_reg_value = KVP_OP_REGISTER; + pr_info("KVP: IP injection functionality not available\n"); + pr_info("KVP: Upgrade the KVP daemon\n"); + break; + case KVP_OP_REGISTER1: + dm_reg_value = KVP_OP_REGISTER1; + break; + default: + pr_info("KVP: incompatible daemon\n"); + pr_info("KVP: KVP version: %d, Daemon version: %d\n", + KVP_OP_REGISTER1, msg->kvp_hdr.operation); + ret = 0; + } + + if (ret) { + /* + * We have a compatible daemon; complete the handshake. + */ + pr_info("KVP: user-mode registering done.\n"); + kvp_register(dm_reg_value); + kvp_transaction.active = false; + if (kvp_transaction.kvp_context) + hv_kvp_onchannelcallback(kvp_transaction.kvp_context); + } + return ret; } + /* * Callback when data is received from user mode. */ @@ -109,27 +154,52 @@ kvp_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) { struct hv_kvp_msg *message; struct hv_kvp_msg_enumerate *data; + int error = 0; message = (struct hv_kvp_msg *)msg->data; - switch (message->kvp_hdr.operation) { + + /* + * If we are negotiating the version information + * with the daemon; handle that first. + */ + + if (in_hand_shake) { + if (kvp_handle_handshake(message)) + in_hand_shake = false; + return; + } + + /* + * Based on the version of the daemon, we propagate errors from the + * daemon differently. + */ + + data = &message->body.kvp_enum_data; + + switch (dm_reg_value) { case KVP_OP_REGISTER: - pr_info("KVP: user-mode registering done.\n"); - kvp_register(); - kvp_transaction.active = false; - hv_kvp_onchannelcallback(kvp_transaction.kvp_context); + /* + * Null string is used to pass back error condition. + */ + if (data->data.key[0] == 0) + error = HV_S_CONT; break; - default: - data = &message->body.kvp_enum_data; + case KVP_OP_REGISTER1: /* - * Complete the transaction by forwarding the key value - * to the host. But first, cancel the timeout. + * We use the message header information from + * the user level daemon to transmit errors. */ - if (cancel_delayed_work_sync(&kvp_work)) - kvp_respond_to_host(data->data.key, - data->data.value, - !strlen(data->data.key)); + error = message->error; + break; } + + /* + * Complete the transaction by forwarding the key value + * to the host. But first, cancel the timeout. + */ + if (cancel_delayed_work_sync(&kvp_work)) + kvp_respond_to_host(data->data.key, data->data.value, error); } static void @@ -287,6 +357,7 @@ kvp_respond_to_host(char *key, char *value, int error) */ return; + icmsghdrp->status = error; /* * If the error parameter is set, terminate the host's enumeration @@ -294,15 +365,12 @@ kvp_respond_to_host(char *key, char *value, int error) */ if (error) { /* - * Something failed or the we have timedout; - * terminate the current host-side iteration. + * Something failed or we have timedout; + * terminate the current host-side iteration. */ - icmsghdrp->status = HV_S_CONT; goto response_done; } - icmsghdrp->status = HV_S_OK; - kvp_msg = (struct hv_kvp_msg *) &recv_buffer[sizeof(struct vmbuspipe_hdr) + sizeof(struct icmsg_hdr)]; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 11afc4e0849a..b587c448e8ab 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -181,6 +181,17 @@ enum hv_kvp_exchg_pool { KVP_POOL_COUNT /* Number of pools, must be last. */ }; +/* + * Some Hyper-V status codes. + */ + +#define HV_S_OK 0x00000000 +#define HV_E_FAIL 0x80004005 +#define HV_S_CONT 0x80070103 +#define HV_ERROR_NOT_SUPPORTED 0x80070032 +#define HV_ERROR_MACHINE_LOCKED 0x800704F7 +#define HV_ERROR_DEVICE_NOT_CONNECTED 0x8007048F + #define ADDR_FAMILY_NONE 0x00 #define ADDR_FAMILY_IPV4 0x01 #define ADDR_FAMILY_IPV6 0x02 @@ -1048,12 +1059,6 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver); #define ICMSGHDRFLAG_REQUEST 2 #define ICMSGHDRFLAG_RESPONSE 4 -#define HV_S_OK 0x00000000 -#define HV_E_FAIL 0x80004005 -#define HV_S_CONT 0x80070103 -#define HV_ERROR_NOT_SUPPORTED 0x80070032 -#define HV_ERROR_MACHINE_LOCKED 0x800704F7 -#define HV_ERROR_DEVICE_NOT_CONNECTED 0x8007048F /* * While we want to handle util services as regular devices, diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index 8fbcf7b3c69d..069e2b38decb 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c @@ -71,13 +71,14 @@ enum key_index { static char kvp_send_buffer[4096]; static char kvp_recv_buffer[4096 * 2]; static struct sockaddr_nl addr; +static int in_hand_shake = 1; static char *os_name = ""; static char *os_major = ""; static char *os_minor = ""; static char *processor_arch; static char *os_build; -static char *lic_version; +static char *lic_version = "Unknown version"; static struct utsname uts_buf; @@ -394,7 +395,7 @@ static int kvp_get_value(int pool, __u8 *key, int key_size, __u8 *value, return 1; } -static void kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, +static int kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, __u8 *value, int value_size) { struct kvp_record *record; @@ -406,16 +407,12 @@ static void kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, record = kvp_file_info[pool].records; if (index >= kvp_file_info[pool].num_records) { - /* - * This is an invalid index; terminate enumeration; - * - a NULL value will do the trick. - */ - strcpy(value, ""); - return; + return 1; } memcpy(key, record[index].key, key_size); memcpy(value, record[index].value, value_size); + return 0; } @@ -646,6 +643,8 @@ int main(void) char *p; char *key_value; char *key_name; + int op; + int pool; daemon(1, 0); openlog("KVP", 0, LOG_USER); @@ -687,7 +686,7 @@ int main(void) message->id.val = CN_KVP_VAL; hv_msg = (struct hv_kvp_msg *)message->data; - hv_msg->kvp_hdr.operation = KVP_OP_REGISTER; + hv_msg->kvp_hdr.operation = KVP_OP_REGISTER1; message->ack = 0; message->len = sizeof(struct hv_kvp_msg); @@ -721,12 +720,21 @@ int main(void) incoming_cn_msg = (struct cn_msg *)NLMSG_DATA(incoming_msg); hv_msg = (struct hv_kvp_msg *)incoming_cn_msg->data; - switch (hv_msg->kvp_hdr.operation) { - case KVP_OP_REGISTER: + /* + * 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) { @@ -737,44 +745,39 @@ int main(void) syslog(LOG_ERR, "malloc failed"); } continue; + } - /* - * The current protocol with the kernel component uses a - * NULL key name to pass an error condition. - * For the SET, GET and DELETE operations, - * use the existing protocol to pass back error. - */ - + switch (op) { case KVP_OP_SET: - if (kvp_key_add_or_modify(hv_msg->kvp_hdr.pool, + 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)) - strcpy(hv_msg->body.kvp_set.data.key, ""); + hv_msg->error = HV_S_CONT; break; case KVP_OP_GET: - if (kvp_get_value(hv_msg->kvp_hdr.pool, + 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)) - strcpy(hv_msg->body.kvp_set.data.key, ""); + hv_msg->error = HV_S_CONT; break; case KVP_OP_DELETE: - if (kvp_key_delete(hv_msg->kvp_hdr.pool, + if (kvp_key_delete(pool, hv_msg->body.kvp_delete.key, hv_msg->body.kvp_delete.key_size)) - strcpy(hv_msg->body.kvp_delete.key, ""); + hv_msg->error = HV_S_CONT; break; default: break; } - if (hv_msg->kvp_hdr.operation != KVP_OP_ENUMERATE) + if (op != KVP_OP_ENUMERATE) goto kvp_done; /* @@ -782,13 +785,14 @@ int main(void) * both the key and the value; if not read from the * appropriate pool. */ - if (hv_msg->kvp_hdr.pool != KVP_POOL_AUTO) { - kvp_pool_enumerate(hv_msg->kvp_hdr.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_KVP_EXCHANGE_MAX_VALUE_SIZE)) + hv_msg->error = HV_S_CONT; goto kvp_done; } @@ -841,11 +845,7 @@ int main(void) strcpy(key_name, "ProcessorArchitecture"); break; default: - strcpy(key_value, "Unknown Key"); - /* - * We use a null key name to terminate enumeration. - */ - strcpy(key_name, ""); + hv_msg->error = HV_S_CONT; break; } /* -- cgit v1.2.3 From 1508d8111f0e965ebe06c32dc4d176750eb53c3a Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Thu, 16 Aug 2012 08:23:20 -0700 Subject: Drivers: hv: Explicitly size elements of protocol structures Use explicitly sized types in data structures defining the host/guest protocol. Reported-by: Juan Sanchez-Agrelo Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- include/linux/hyperv.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'include/linux/hyperv.h') diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index b587c448e8ab..7585d5533e43 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -487,7 +487,7 @@ struct vmtransfer_page_range { struct vmtransfer_page_packet_header { struct vmpacket_descriptor d; u16 xfer_pageset_id; - bool sender_owns_set; + u8 sender_owns_set; u8 reserved; u32 range_cnt; struct vmtransfer_page_range ranges[1]; @@ -641,7 +641,7 @@ struct vmbus_channel_query_vmbus_version { /* VMBus Version Supported parameters */ struct vmbus_channel_version_supported { struct vmbus_channel_message_header header; - bool version_supported; + u8 version_supported; } __packed; /* Offer Channel parameters */ @@ -650,7 +650,7 @@ struct vmbus_channel_offer_channel { struct vmbus_channel_offer offer; u32 child_relid; u8 monitorid; - bool monitor_allocated; + u8 monitor_allocated; } __packed; /* Rescind Offer parameters */ @@ -786,7 +786,7 @@ struct vmbus_channel_initiate_contact { struct vmbus_channel_version_response { struct vmbus_channel_message_header header; - bool version_supported; + u8 version_supported; } __packed; enum vmbus_channel_state { -- cgit v1.2.3 From 32061b4d3830e61975ede409df389804507fd220 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 5 Sep 2012 13:50:13 -0700 Subject: Tools: hv: Implement the KVP verb - KVP_OP_SET_IP_INFO Implement the KVP verb - KVP_OP_SET_IP_INFO. This operation configures the specified interface based on the given configuration. Since configuring an interface is very distro specific, we invoke an external (Distro specific) script to configure the interface. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- include/linux/hyperv.h | 2 + tools/hv/hv_kvp_daemon.c | 443 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 445 insertions(+) (limited to 'include/linux/hyperv.h') diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 7585d5533e43..e73b852156b1 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -191,6 +191,8 @@ enum hv_kvp_exchg_pool { #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_GUID_NOTFOUND 0x80041002 #define ADDR_FAMILY_NONE 0x00 #define ADDR_FAMILY_IPV4 0x01 diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index 6fb2c1c6c32e..ac144b9da0db 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include /* * KVP protocol: The user mode component first registers with the @@ -68,6 +70,14 @@ enum key_index { ProcessorArchitecture }; + +enum { + IPADDR = 0, + NETMASK, + GATEWAY, + DNS +}; + static char kvp_send_buffer[4096]; static char kvp_recv_buffer[4096 * 2]; static struct sockaddr_nl addr; @@ -81,6 +91,11 @@ static char *os_build; static char *lic_version = "Unknown version"; static struct utsname uts_buf; +/* + * The location of the interface configuration file. + */ + +#define KVP_CONFIG_LOC "/var/opt/" #define MAX_FILE_NAME 100 #define ENTRIES_PER_BLOCK 50 @@ -490,6 +505,104 @@ done: 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, *q, *x; + char *if_name = NULL; + char buf[256]; + char *kvp_net_dir = "/sys/class/net/"; + char dev_id[256]; + + dir = opendir(kvp_net_dir); + if (dir == NULL) + return NULL; + + snprintf(dev_id, sizeof(dev_id), "%s", kvp_net_dir); + q = dev_id + strlen(kvp_net_dir); + + while ((entry = readdir(dir)) != NULL) { + /* + * Set the state for the next pass. + */ + *q = '\0'; + strcat(dev_id, entry->d_name); + strcat(dev_id, "/device/device_id"); + + 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[256]; + int i; + char *mac_addr = NULL; + + snprintf(addr_file, sizeof(addr_file), "%s%s%s", "/sys/class/net/", + 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, int len, int element_size, int offset) @@ -790,6 +903,315 @@ getaddr_done: } +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 seperated 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[10]; + 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 (i != 0) { + if (type != DNS) { + snprintf(sub_str, sizeof(sub_str), + "_%d", i++); + } else { + snprintf(sub_str, sizeof(sub_str), + "%d", ++i); + } + } else if (type == DNS) { + 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 ((j != 0) || (type == DNS)) { + if (type != DNS) { + snprintf(sub_str, sizeof(sub_str), + "_%d", j++); + } else { + snprintf(sub_str, sizeof(sub_str), + "%d", ++i); + } + } else if (type == DNS) { + snprintf(sub_str, sizeof(sub_str), + "%d", ++i); + } + } else { + return HV_INVALIDARG; + } + + error = kvp_write_file(f, str, sub_str, addr); + if (error) + return error; + memset(addr, 0, sizeof(addr)); + } + + 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; + + /* + * 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 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. + */ + + snprintf(if_file, sizeof(if_file), "%s%s%s", KVP_CONFIG_LOC, + "hyperv/ifcfg-", if_name); + + file = fopen(if_file, "w"); + + if (file == NULL) { + syslog(LOG_ERR, "Failed to open config 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(file, "HWADDR", "", mac_addr); + if (error) + goto setval_error; + + error = kvp_write_file(file, "IF_NAME", "", if_name); + if (error) + goto setval_error; + + if (new_val->dhcp_enabled) { + error = kvp_write_file(file, "DHCP", "", "yes"); + if (error) + goto setval_error; + + /* + * We are done!. + */ + goto setval_done; + } + + /* + * Write the configuration for ipaddress, netmask, gateway and + * name servers. + */ + + error = process_ip_string(file, (char *)new_val->ip_addr, IPADDR); + if (error) + goto setval_error; + + error = process_ip_string(file, (char *)new_val->sub_net, NETMASK); + if (error) + goto setval_error; + + error = process_ip_string(file, (char *)new_val->gate_way, GATEWAY); + if (error) + goto setval_error; + + error = process_ip_string(file, (char *)new_val->dns_addr, DNS); + if (error) + goto setval_error; + +setval_done: + free(mac_addr); + fclose(file); + + /* + * Now that we have populated the configuration file, + * invoke the external script to do its magic. + */ + + snprintf(cmd, sizeof(cmd), "%s %s", "hv_set_ifconfig", if_file); + system(cmd); + return 0; + +setval_error: + syslog(LOG_ERR, "Failed to write config file"); + free(mac_addr); + fclose(file); + return error; +} + + static int kvp_get_domain_name(char *buffer, int length) { @@ -859,6 +1281,8 @@ int main(void) char *key_name; int op; int pool; + char *if_name; + struct hv_kvp_ipaddr_value *kvp_ip_val; daemon(1, 0); openlog("KVP", 0, LOG_USER); @@ -962,6 +1386,25 @@ int main(void) } switch (op) { + 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, -- cgit v1.2.3