Index: include/libvirt/libvirt.h.in =================================================================== RCS file: /data/cvs/libvirt/include/libvirt/libvirt.h.in,v retrieving revision 1.42 diff -u -r1.42 libvirt.h.in --- include/libvirt/libvirt.h.in 20 Feb 2008 14:57:39 -0000 1.42 +++ include/libvirt/libvirt.h.in 25 Feb 2008 16:45:26 -0000 @@ -760,6 +760,27 @@ int virNetworkSetAutostart (virNetworkPtr network, int autostart); +/* + * DHCP host mappings + */ +int virNetworkNumOfDHCPHostMappings + (virNetworkPtr network); +int virNetworkListDHCPHostMappings + (virNetworkPtr network, + char **const hwaddrs, + char **const ipaddrs, + char **const hostnames, + int maxmappings); + +int virNetworkAddDHCPHostMapping + (virNetworkPtr network, + const char *hwaddr, + const char *ipaddr, + const char *hostname, + unsigned int flags); +int virNetworkDeleteDHCPHostMapping + (virNetworkPtr network, + const char *hwaddr); /** * virStoragePool: Index: qemud/remote.c =================================================================== RCS file: /data/cvs/libvirt/qemud/remote.c,v retrieving revision 1.24 diff -u -r1.24 remote.c --- qemud/remote.c 22 Feb 2008 16:26:13 -0000 1.24 +++ qemud/remote.c 25 Feb 2008 16:45:28 -0000 @@ -2058,6 +2058,173 @@ static int +remoteDispatchNetworkNumOfDhcpHostMappings (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client, + remote_message_header *req, + remote_network_num_of_dhcp_host_mappings_args *args, + remote_network_num_of_dhcp_host_mappings_ret *ret) +{ + virNetworkPtr net; + CHECK_CONN(client); + + net = get_nonnull_network (client->conn, args->net); + if (net == NULL) { + remoteDispatchError (client, req, "network not found"); + return -2; + } + + ret->num = virNetworkNumOfDHCPHostMappings (net); + if (ret->num == -1) { + virNetworkFree (net); + return -1; + } + + virNetworkFree (net); + return 0; +} + + +static int +remoteDispatchNetworkListDhcpHostMappings (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client, + remote_message_header *req, + remote_network_list_dhcp_host_mappings_args *args, + remote_network_list_dhcp_host_mappings_ret *ret) +{ + virNetworkPtr net; + char **hwaddrs, **ipaddrs, **hostnames; + int nr_mappings, i; + CHECK_CONN(client); + + net = get_nonnull_network (client->conn, args->net); + if (net == NULL) { + remoteDispatchError (client, req, "network not found"); + return -2; + } + + if (args->maxmappings > REMOTE_NETWORK_DHCP_HOST_MAPPINGS_LIST_MAX) { + remoteDispatchError (client, req, + "maxmappings > REMOTE_NETWORK_DHCP_HOST_MAPPINGS_LIST_MAX"); + return -2; + } + + hwaddrs = malloc (args->maxmappings * sizeof (char *)); + if (!hwaddrs) { + remoteDispatchSendError (client, req, VIR_ERR_NO_MEMORY, "hwaddrs"); + return -2; + } + ipaddrs = malloc (args->maxmappings * sizeof (char *)); + if (!ipaddrs) { + remoteDispatchSendError (client, req, VIR_ERR_NO_MEMORY, "ipaddrs"); + free (hwaddrs); + return -2; + } + hostnames = malloc (args->maxmappings * sizeof (char *)); + if (!hostnames) { + remoteDispatchSendError (client, req, VIR_ERR_NO_MEMORY, "hostnames"); + free (hwaddrs); + free (ipaddrs); + return -2; + } + + nr_mappings = virNetworkListDHCPHostMappings (net, + hwaddrs, ipaddrs, hostnames, + args->maxmappings); + if (nr_mappings == -1) { + virNetworkFree(net); + free (hwaddrs); + free (ipaddrs); + free (hostnames); + return -1; + } + + ret->hwaddrs.hwaddrs_len = nr_mappings; + ret->ipaddrs.ipaddrs_len = nr_mappings; + ret->hostnames.hostnames_len = nr_mappings; + + ret->hwaddrs.hwaddrs_val = + calloc (nr_mappings, sizeof (ret->hwaddrs.hwaddrs_val[0])); + ret->ipaddrs.ipaddrs_val = + calloc (nr_mappings, sizeof (ret->ipaddrs.ipaddrs_val[0])); + ret->hostnames.hostnames_val = + calloc (nr_mappings, sizeof (ret->hostnames.hostnames_val[0])); + for (i = 0; i < nr_mappings; ++i) { + ret->hwaddrs.hwaddrs_val[i] = hwaddrs[i]; + ret->ipaddrs.ipaddrs_val[i] = ipaddrs[i]; + /* See note in remote_protocol.x */ + ret->hostnames.hostnames_val[i] = + hostnames[i] == NULL ? strdup ("") : hostnames[i]; + } + + free (hwaddrs); /* NB: Just free the array, caller frees the rest. */ + free (ipaddrs); + free (hostnames); + + virNetworkFree(net); + return 0; +} + + +static int +remoteDispatchNetworkAddDhcpHostMapping (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client, + remote_message_header *req, + remote_network_add_dhcp_host_mapping_args *args, + void *ret ATTRIBUTE_UNUSED) +{ + virNetworkPtr net; + char *hwaddr, *ipaddr, *hostname; + CHECK_CONN(client); + + net = get_nonnull_network (client->conn, args->net); + if (net == NULL) { + remoteDispatchError (client, req, "network not found"); + return -2; + } + + hwaddr = args->hwaddr; + ipaddr = args->ipaddr; + hostname = args->hostname == NULL ? NULL : *args->hostname; + + if (virNetworkAddDHCPHostMapping (net, hwaddr, ipaddr, hostname, args->flags) == -1) { + virNetworkFree (net); + return -1; + } + + virNetworkFree (net); + return 0; +} + +static int +remoteDispatchNetworkDeleteDhcpHostMapping (struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client, + remote_message_header *req, + remote_network_delete_dhcp_host_mapping_args *args, + void *ret ATTRIBUTE_UNUSED) +{ + virNetworkPtr net; + char *hwaddr; + CHECK_CONN(client); + + net = get_nonnull_network (client->conn, args->net); + if (net == NULL) { + remoteDispatchError (client, req, "network not found"); + return -2; + } + + hwaddr = args->hwaddr; + + if (virNetworkDeleteDHCPHostMapping (net, hwaddr) == -1) { + virNetworkFree (net); + return -1; + } + + virNetworkFree (net); + return 0; +} + + +static int remoteDispatchAuthList (struct qemud_server *server ATTRIBUTE_UNUSED, struct qemud_client *client, remote_message_header *req ATTRIBUTE_UNUSED, Index: qemud/remote_protocol.x =================================================================== RCS file: /data/cvs/libvirt/qemud/remote_protocol.x,v retrieving revision 1.10 diff -u -r1.10 remote_protocol.x --- qemud/remote_protocol.x 20 Feb 2008 15:22:35 -0000 1.10 +++ qemud/remote_protocol.x 25 Feb 2008 16:45:29 -0000 @@ -78,6 +78,9 @@ /* Upper limit on lists of network names. */ const REMOTE_NETWORK_NAME_LIST_MAX = 256; +/* Upper limit on lists of DHCP host mappings. */ +const REMOTE_NETWORK_DHCP_HOST_MAPPINGS_LIST_MAX = 256; + /* Upper limit on lists of storage pool names. */ const REMOTE_STORAGE_POOL_NAME_LIST_MAX = 256; @@ -647,6 +650,42 @@ int autostart; }; +struct remote_network_num_of_dhcp_host_mappings_args { + remote_nonnull_network net; +}; + +struct remote_network_num_of_dhcp_host_mappings_ret { + int num; +}; + +struct remote_network_list_dhcp_host_mappings_args { + remote_nonnull_network net; + int maxmappings; +}; + +struct remote_network_list_dhcp_host_mappings_ret { + remote_nonnull_string hwaddrs; + remote_nonnull_string ipaddrs; + /* Technically these can be NULL, but XDR makes it very hard + * to implement remoteDispatchNetworkListDhcpHostMappings without + * using an ugly static variable, so instead just assume "" on the + * wire is the same as NULL. Hostnames can't be zero length anyway. + */ + remote_nonnull_string hostnames; +}; + +struct remote_network_add_dhcp_host_mapping_args { + remote_nonnull_network net; + remote_nonnull_string hwaddr; + remote_nonnull_string ipaddr; + remote_string hostname; + int flags; +}; + +struct remote_network_delete_dhcp_host_mapping_args { + remote_nonnull_network net; + remote_nonnull_string hwaddr; +}; struct remote_auth_list_ret { remote_auth_type types; @@ -1017,7 +1056,12 @@ REMOTE_PROC_STORAGE_VOL_LOOKUP_BY_PATH = 97, REMOTE_PROC_STORAGE_VOL_GET_INFO = 98, REMOTE_PROC_STORAGE_VOL_DUMP_XML = 99, - REMOTE_PROC_STORAGE_VOL_GET_PATH = 100 + REMOTE_PROC_STORAGE_VOL_GET_PATH = 100, + + REMOTE_PROC_NETWORK_ADD_DHCP_HOST_MAPPING = 101, + REMOTE_PROC_NETWORK_DELETE_DHCP_HOST_MAPPING = 102, + REMOTE_PROC_NETWORK_LIST_DHCP_HOST_MAPPINGS = 103, + REMOTE_PROC_NETWORK_NUM_OF_DHCP_HOST_MAPPINGS = 104 }; /* Custom RPC structure. */ Index: src/driver.h =================================================================== RCS file: /data/cvs/libvirt/src/driver.h,v retrieving revision 1.42 diff -u -r1.42 driver.h --- src/driver.h 20 Feb 2008 15:06:53 -0000 1.42 +++ src/driver.h 25 Feb 2008 16:45:29 -0000 @@ -377,6 +377,24 @@ (*virDrvNetworkSetAutostart) (virNetworkPtr network, int autostart); +typedef int + (*virDrvNetworkNumOfDHCPHostMappings) (virNetworkPtr network); +typedef int + (*virDrvNetworkListDHCPHostMappings) (virNetworkPtr network, + char **const hwaddrs, + char **const ipaddrs, + char **const hostnames, + int maxmappings); +typedef int + (*virDrvNetworkAddDHCPHostMapping) (virNetworkPtr network, + const char *hwaddr, + const char *ipaddr, + const char *hostname, + unsigned int flags); +typedef int + (*virDrvNetworkDeleteDHCPHostMapping) (virNetworkPtr network, + const char *hwaddr); + typedef struct _virNetworkDriver virNetworkDriver; typedef virNetworkDriver *virNetworkDriverPtr; @@ -410,6 +428,10 @@ virDrvNetworkGetBridgeName networkGetBridgeName; virDrvNetworkGetAutostart networkGetAutostart; virDrvNetworkSetAutostart networkSetAutostart; + virDrvNetworkNumOfDHCPHostMappings networkNumOfDHCPHostMappings; + virDrvNetworkListDHCPHostMappings networkListDHCPHostMappings; + virDrvNetworkAddDHCPHostMapping networkAddDHCPHostMapping; + virDrvNetworkDeleteDHCPHostMapping networkDeleteDHCPHostMapping; }; Index: src/libvirt.c =================================================================== RCS file: /data/cvs/libvirt/src/libvirt.c,v retrieving revision 1.123 diff -u -r1.123 libvirt.c --- src/libvirt.c 20 Feb 2008 16:54:36 -0000 1.123 +++ src/libvirt.c 25 Feb 2008 16:45:32 -0000 @@ -3683,6 +3683,175 @@ return -1; } +/** + * virNetworkNumOfDHCPHostMappings: + * @network: a network object + * + * Returns the number of DHCP host mappings, or -1 in case of error. + */ +int +virNetworkNumOfDHCPHostMappings (virNetworkPtr network) +{ + virConnectPtr conn; + DEBUG("network=%p", network); + + if (!VIR_IS_NETWORK(network)) { + virLibNetworkError (NULL, VIR_ERR_INVALID_NETWORK, __FUNCTION__); + return -1; + } + + conn = network->conn; + + if (conn->networkDriver && + conn->networkDriver->networkNumOfDHCPHostMappings) + return conn->networkDriver->networkNumOfDHCPHostMappings (network); + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); + return -1; +} + +/** + * virNetworkListDHCPHostMappings: + * @network: a network object + * @hwaddrs: an array of hardware addresses + * @ipaddrs: an array of IP addresses + * @hostnames: an array of hostnames (which may be NULL) + * @maxmappings: the length of the arrays + * + * This can be used to return a list of DHCP host mappings for the + * network. + * + * This function fills in three arrays of corresponding pointers, + * thus the first tuple returned is (hwaddrs[0], ipaddrs[0], + * hostnames[0]). The 'hwaddrs' array contains hardware (MAC) + * addresses. The 'ipaddrs' array contains IP addresses as strings + * in dotted-quad or IPv6 colon format. The 'hostnames' array + * contains optional hostnames, which may be NULL. + * + * The caller is responsible for freeing the strings. + * + * Returns the number of DHCP host mappings, or -1 in case of error. + */ +int +virNetworkListDHCPHostMappings (virNetworkPtr network, + char **const hwaddrs, + char **const ipaddrs, + char **const hostnames, + int maxmappings) +{ + virConnectPtr conn; + DEBUG("network=%p, hwaddrs=%p, ipaddrs=%p, hostnames=%p, maxmappings=%d", + network, hwaddrs, ipaddrs, hostnames, maxmappings); + + if (!VIR_IS_NETWORK(network)) { + virLibNetworkError (NULL, VIR_ERR_INVALID_NETWORK, __FUNCTION__); + return -1; + } + + conn = network->conn; + + if (!hwaddrs || !ipaddrs || !hostnames) { + virLibNetworkError (network, VIR_ERR_INVALID_ARG, __FUNCTION__); + return -1; + } + + if (maxmappings == 0) return 0; + + memset (hwaddrs, 0, sizeof (char *) * maxmappings); + memset (ipaddrs, 0, sizeof (char *) * maxmappings); + memset (hostnames, 0, sizeof (char *) * maxmappings); + + if (conn->networkDriver && + conn->networkDriver->networkListDHCPHostMappings) + return conn->networkDriver->networkListDHCPHostMappings (network, + hwaddrs, + ipaddrs, + hostnames, + maxmappings); + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); + return -1; +} + +/** + * virNetworkAddDHCPHostMapping: + * @network: a network object + * @hwaddr: hardware (MAC) address + * @ipaddr: IP address + * @hostname: hostname + * @flags: unused, pass 0 + * + * Add a DHCP hardware address to IP address mapping, and optionally + * a DHCP hostname for the IP address (if hostname is not NULL). + * + * The mappings are keyed on the hardware (MAC) address. If the hardware + * address already has a mapping, then this call replaces it. + * + * Return -1 on error or 0 on success. + */ +int +virNetworkAddDHCPHostMapping (virNetworkPtr network, + const char *hwaddr, + const char *ipaddr, + const char *hostname, + unsigned int flags) +{ + virConnectPtr conn; + DEBUG("network=%p, hwaddr=%s, ipaddr=%s, hostname=%s, flags=%d", + network, hwaddr, ipaddr, hostname, flags); + + if (!VIR_IS_NETWORK(network)) { + virLibNetworkError (NULL, VIR_ERR_INVALID_NETWORK, __FUNCTION__); + return -1; + } + + conn = network->conn; + + if (flags != 0) { + virLibNetworkError (NULL, VIR_ERR_INVALID_ARG, __FUNCTION__); + return -1; + } + + if (conn->networkDriver && + conn->networkDriver->networkAddDHCPHostMapping) + return conn->networkDriver->networkAddDHCPHostMapping + (network, hwaddr, ipaddr, hostname, flags); + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); + return -1; +} + +/** + * virNetworkDeleteDHCPHostMapping: + * @network: a network object + * @hwaddr: hardware (MAC) address + * + * Delete an existing DHCP host mapping. + * + * Return -1 on error or 0 on success. + */ +int +virNetworkDeleteDHCPHostMapping (virNetworkPtr network, + const char *hwaddr) +{ + virConnectPtr conn; + DEBUG("network=%p, hwaddr=%s", network, hwaddr); + + if (!VIR_IS_NETWORK(network)) { + virLibNetworkError (NULL, VIR_ERR_INVALID_NETWORK, __FUNCTION__); + return -1; + } + + conn = network->conn; + + if (conn->networkDriver && + conn->networkDriver->networkDeleteDHCPHostMapping) + return conn->networkDriver->networkDeleteDHCPHostMapping (network, + hwaddr); + + virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__); + return -1; +} /** * virStoragePoolGetConnect: Index: src/libvirt_sym.version =================================================================== RCS file: /data/cvs/libvirt/src/libvirt_sym.version,v retrieving revision 1.36 diff -u -r1.36 libvirt_sym.version --- src/libvirt_sym.version 21 Feb 2008 03:53:03 -0000 1.36 +++ src/libvirt_sym.version 25 Feb 2008 16:45:32 -0000 @@ -99,6 +99,11 @@ virNetworkGetBridgeName; virNetworkGetAutostart; virNetworkSetAutostart; + virNetworkNumOfDHCPHostMappings; + virNetworkListDHCPHostMappings; + virNetworkFreeDHCPHostMappings; + virNetworkAddDHCPHostMapping; + virNetworkDeleteDHCPHostMapping; virStoragePoolGetConnect; virConnectNumOfStoragePools; Index: src/qemu_driver.c =================================================================== RCS file: /data/cvs/libvirt/src/qemu_driver.c,v retrieving revision 1.54 diff -u -r1.54 qemu_driver.c --- src/qemu_driver.c 22 Feb 2008 16:26:13 -0000 1.54 +++ src/qemu_driver.c 25 Feb 2008 16:45:35 -0000 @@ -832,6 +832,7 @@ 2 + /* --except-interface lo */ 2 + /* --listen-address 10.0.0.1 */ 1 + /* --dhcp-leasefile=path */ + 1 + /* --dhcp-hostsfile=path */ (2 * network->def->nranges) + /* --dhcp-range 10.0.0.2,10.0.0.254 */ 1; /* NULL */ @@ -885,6 +886,10 @@ LOCAL_STATE_DIR, network->def->name); APPEND_ARG(*argv, i++, buf); + snprintf(buf, sizeof(buf), + "--dhcp-hostsfile=" DHCP_HOSTS_FORMAT, network->def->name); + APPEND_ARG(*argv, i++, buf); + range = network->def->ranges; while (range) { snprintf(buf, sizeof(buf), "%s,%s", @@ -915,6 +920,7 @@ dhcpStartDhcpDaemon(virConnectPtr conn, struct qemud_network *network) { + char buf[PATH_MAX]; char **argv; int ret, i; @@ -924,6 +930,15 @@ return -1; } + /* Touch the DHCP hosts file so it exists before dnsmasq starts up. */ + snprintf (buf, sizeof buf, DHCP_HOSTS_FORMAT, network->def->name); + ret = open (buf, O_CREAT|O_WRONLY, 0644); + if (ret == -1) { + qemudReportError (conn, NULL, NULL, VIR_ERR_SYSTEM_ERROR, + "%s: %s", buf, strerror (errno)); + return -1; + } + argv = NULL; if (qemudBuildDnsmasqArgv(conn, network, &argv) < 0) return -1; @@ -2855,6 +2870,382 @@ return 0; } +/* Free the strings but NOT the underlying arrays. */ +static void +qemudFreeDHCPHostMappings (char **hwaddrs, char **ipaddrs, char **hostnames, + int nr_mappings) +{ + int i; + + for (i = 0; i < nr_mappings; ++i) { + free (hwaddrs[i]); + free (ipaddrs[i]); + free (hostnames[i]); + } +} + +/* Parse the mappings file. + * + * If hwaddrs != NULL then this allocates all three arrays and + * stores (allocated) pointers to the strings in each one. + * Use qemudFreeDHCPHostMappings to free the strings, then free + * the arrays by hand. + * + * Returns the number of entries read from the file. + */ +static int +qemudParseDHCPHostMappingsFile (virNetworkPtr net, + struct qemud_network *network, + char ***hwaddrs, + char ***ipaddrs, + char ***hostnames) +{ + char buf[PATH_MAX]; + FILE *fp; + int col, len; + int lines = 0; + int allocated = 0; + char *str; + char *token; + char *saveptr = NULL; + + if (hwaddrs) { + *hwaddrs = NULL; + *ipaddrs = NULL; + *hostnames = NULL; + } + + snprintf (buf, sizeof buf, DHCP_HOSTS_FORMAT, network->def->name); + fp = fopen (buf, "r"); + if (fp == NULL) { + qemudReportError (net->conn, NULL, net, VIR_ERR_SYSTEM_ERROR, + "%s: %s", buf, strerror (errno)); + return -1; + } + + while (fgets (buf, sizeof buf, fp) != 0) { + char *hwaddr = 0, *ipaddr = 0, *hostname = 0; + + /* Dnsmasq manpage is fuzzy about what is accepted as a + * parameter for dhcp-host. Hopefully only we will be + * writing to this file, but be generous in what we + * accept anyway. + * + * NB: A possible improvement here: If we can't parse the + * line then just store the line as-is and write it back + * into the file. + */ + /* Chomp trailing \n character. */ + len = strlen (buf); + if (len > 0 && buf[len-1] == '\n') { + buf[len-1] = '\0'; + len--; + } + + /* Ignore blank lines and comments. */ + if (len == 0 || buf[0] == '#') + continue; + + for (str = buf, col = 0; + (token = strtok_r (str, ",", &saveptr)) != NULL; + str = NULL, col++) { + if (!STREQLEN (token, "net:", 4) && + !STREQLEN (token, "id:", 3) && + !STREQ (token, "ignore")) { + /* First column is the hardware address. */ + if (col == 0) hwaddr = token; + else { + if (isdigit (token[0])) { + if (!ipaddr) ipaddr = token; + } else { + if (!hostname) hostname = token; + } + } + } + } + + if (hwaddr && ipaddr) { + if (hwaddrs) { + if (lines >= allocated) { + char **old; + + allocated += 16; + + old = *hwaddrs; + *hwaddrs = realloc (*hwaddrs, allocated * sizeof (char *)); + if (!*hwaddrs) { + *hwaddrs = old; + goto mem_error; + } + old = *ipaddrs; + *ipaddrs = realloc (*ipaddrs, allocated * sizeof (char *)); + if (!*ipaddrs) { + *ipaddrs = old; + goto mem_error; + } + old = *hostnames; + *hostnames = realloc (*hostnames, allocated * sizeof (char *)); + if (!*hostnames) { + *hostnames = old; + goto mem_error; + } + } + + (*hwaddrs)[lines] = strdup (hwaddr); + if (!(*hwaddrs)[lines]) goto mem_error; + (*ipaddrs)[lines] = strdup (ipaddr); + if (!(*ipaddrs)[lines]) goto mem_error; + if (hostname) { + (*hostnames)[lines] = strdup (hostname); + if (!(*hostnames)[lines]) goto mem_error; + } else + (*hostnames)[lines] = NULL; + } + + lines++; + } + } + + fclose (fp); + + return lines; + + mem_error: + fclose (fp); + + if (hwaddrs) { + qemudFreeDHCPHostMappings (*hwaddrs, *ipaddrs, *hostnames, lines); + free (*hwaddrs); + free (*ipaddrs); + free (*hostnames); + } + + qemudReportError (net->conn, NULL, net, VIR_ERR_NO_MEMORY, + __FUNCTION__); + + return -1; +} + +static int +qemudNetworkNumOfDHCPHostMappings (virNetworkPtr net) +{ + struct qemud_driver *driver = + (struct qemud_driver *)net->conn->networkPrivateData; + struct qemud_network *network = + qemudFindNetworkByUUID(driver, net->uuid); + + if (!network) { + qemudReportError (net->conn, NULL, net, VIR_ERR_INVALID_DOMAIN, + "no network with matching uuid"); + return -1; + } + + return qemudParseDHCPHostMappingsFile (net, network, NULL, NULL, NULL); +} + +static int +qemudNetworkListDHCPHostMappings (virNetworkPtr net, + char **const hwaddrs_rtn, + char **const ipaddrs_rtn, + char **const hostnames_rtn, + int maxmappings) +{ + int nr_mappings, ret; + char **hwaddrs, **ipaddrs, **hostnames; + struct qemud_driver *driver = + (struct qemud_driver *)net->conn->networkPrivateData; + struct qemud_network *network = + qemudFindNetworkByUUID(driver, net->uuid); + + if (!network) { + qemudReportError (net->conn, NULL, net, VIR_ERR_INVALID_DOMAIN, + "no network with matching uuid"); + return -1; + } + + nr_mappings = + qemudParseDHCPHostMappingsFile (net, network, + &hwaddrs, &ipaddrs, &hostnames); + if (nr_mappings == -1) return -1; + + if (maxmappings >= nr_mappings) { + /* The return array is large enough. */ + memcpy (hwaddrs_rtn, hwaddrs, nr_mappings * sizeof (char *)); + memcpy (ipaddrs_rtn, ipaddrs, nr_mappings * sizeof (char *)); + memcpy (hostnames_rtn, hostnames, nr_mappings * sizeof (char *)); + ret = nr_mappings; + } else { + /* Partially copy the array, free the unused mappings that + * we didn't copy. + */ + memcpy (hwaddrs_rtn, hwaddrs, maxmappings * sizeof (char *)); + memcpy (ipaddrs_rtn, ipaddrs, maxmappings * sizeof (char *)); + memcpy (hostnames_rtn, hostnames, maxmappings * sizeof (char *)); + qemudFreeDHCPHostMappings (hwaddrs + maxmappings, + ipaddrs + maxmappings, + hostnames + maxmappings, + nr_mappings - maxmappings); + ret = maxmappings; + } + + free (hwaddrs); /* NB: Just free the arrays themselves. */ + free (ipaddrs); + free (hostnames); + + return ret; +} + +static int +qemudNetworkAddDHCPHostMapping (virNetworkPtr net, + const char *hwaddr, + const char *ipaddr, + const char *hostname, + unsigned int flags ATTRIBUTE_UNUSED) +{ + char buf[PATH_MAX]; + char **hwaddrs, **ipaddrs, **hostnames; + int nr_mappings, i, written = 0; + FILE *fp; + struct qemud_driver *driver = + (struct qemud_driver *)net->conn->networkPrivateData; + struct qemud_network *network = + qemudFindNetworkByUUID(driver, net->uuid); + + if (!network) { + qemudReportError (net->conn, NULL, net, VIR_ERR_INVALID_DOMAIN, + "no network with matching uuid"); + return -1; + } + + nr_mappings = + qemudParseDHCPHostMappingsFile (net, network, + &hwaddrs, &ipaddrs, &hostnames); + if (nr_mappings == -1) return -1; + + snprintf (buf, sizeof buf, DHCP_HOSTS_FORMAT, network->def->name); + fp = fopen (buf, "w"); + if (fp == NULL) { + qemudReportError (net->conn, NULL, net, VIR_ERR_SYSTEM_ERROR, + "%s: %s", buf, strerror (errno)); + return -1; + } + + for (i = 0; i < nr_mappings; ++i) { + if (STREQ (hwaddrs[i], hwaddr)) { + /* Replace this entry. */ + fputs (hwaddr, fp); + fputc (',', fp); + fputs (ipaddr, fp); + if (hostname) { + fputc (',', fp); + fputs (hostname, fp); + } + fputc ('\n', fp); + written = 1; + } else { + /* Write this entry again. */ + fputs (hwaddrs[i], fp); + fputc (',', fp); + fputs (ipaddrs[i], fp); + if (hostnames[i]) { + fputc (',', fp); + fputs (hostnames[i], fp); + } + fputc ('\n', fp); + } + } + + if (!written) { + fputs (hwaddr, fp); + fputc (',', fp); + fputs (ipaddr, fp); + if (hostname) { + fputc (',', fp); + fputs (hostname, fp); + } + fputc ('\n', fp); + } + + qemudFreeDHCPHostMappings (hwaddrs, ipaddrs, hostnames, nr_mappings); + free (hwaddrs); + free (ipaddrs); + free (hostnames); + + if (fclose (fp) == EOF) { + qemudReportError (net->conn, NULL, net, VIR_ERR_SYSTEM_ERROR, + "%s", strerror (errno)); + return -1; + } + + /* Tell dnsmasq to reread the configuration file. */ + kill (network->dnsmasqPid, SIGHUP); + + return 0; +} + +static int +qemudNetworkDeleteDHCPHostMapping (virNetworkPtr net, + const char *hwaddr) +{ + char buf[PATH_MAX]; + char **hwaddrs, **ipaddrs, **hostnames; + int nr_mappings, i; + FILE *fp; + struct qemud_driver *driver = + (struct qemud_driver *)net->conn->networkPrivateData; + struct qemud_network *network = + qemudFindNetworkByUUID(driver, net->uuid); + + if (!network) { + qemudReportError (net->conn, NULL, net, VIR_ERR_INVALID_DOMAIN, + "no network with matching uuid"); + return -1; + } + + nr_mappings = + qemudParseDHCPHostMappingsFile (net, network, + &hwaddrs, &ipaddrs, &hostnames); + if (nr_mappings == -1) return -1; + + snprintf (buf, sizeof buf, DHCP_HOSTS_FORMAT, network->def->name); + fp = fopen (buf, "w"); + if (fp == NULL) { + qemudReportError (net->conn, NULL, net, VIR_ERR_SYSTEM_ERROR, + "%s: %s", buf, strerror (errno)); + return -1; + } + + for (i = 0; i < nr_mappings; ++i) { + if (!STREQ (hwaddrs[i], hwaddr)) { + /* Write this entry again. */ + fputs (hwaddrs[i], fp); + fputc (',', fp); + fputs (ipaddrs[i], fp); + if (hostnames[i]) { + fputc (',', fp); + fputs (hostnames[i], fp); + } + fputc ('\n', fp); + } + } + + qemudFreeDHCPHostMappings (hwaddrs, ipaddrs, hostnames, nr_mappings); + free (hwaddrs); + free (ipaddrs); + free (hostnames); + + if (fclose (fp) == EOF) { + qemudReportError (net->conn, NULL, net, VIR_ERR_SYSTEM_ERROR, + "%s", strerror (errno)); + return -1; + } + + /* Tell dnsmasq to reread the configuration file. */ + kill (network->dnsmasqPid, SIGHUP); + + return 0; +} + static virDriver qemuDriver = { VIR_DRV_QEMU, "QEMU", @@ -2933,6 +3324,10 @@ qemudNetworkGetBridgeName, /* networkGetBridgeName */ qemudNetworkGetAutostart, /* networkGetAutostart */ qemudNetworkSetAutostart, /* networkSetAutostart */ + qemudNetworkNumOfDHCPHostMappings, /* networkNumOfDHCPHostMappings */ + qemudNetworkListDHCPHostMappings, /* networkListDHCPHostMappings */ + qemudNetworkAddDHCPHostMapping, /* networkAddDHCPHostMapping */ + qemudNetworkDeleteDHCPHostMapping, /* networkDeleteDHCPHostMapping */ }; static virStateDriver qemuStateDriver = { Index: src/qemu_driver.h =================================================================== RCS file: /data/cvs/libvirt/src/qemu_driver.h,v retrieving revision 1.4 diff -u -r1.4 qemu_driver.h --- src/qemu_driver.h 29 Jan 2008 18:15:54 -0000 1.4 +++ src/qemu_driver.h 25 Feb 2008 16:45:35 -0000 @@ -31,6 +31,9 @@ #include "internal.h" +/* %s is replaced with the network name. */ +#define DHCP_HOSTS_FORMAT LOCAL_STATE_DIR "/lib/libvirt/hosts-%s.conf" + int qemudRegister(void); #endif /* WITH_QEMU */ Index: src/remote_internal.c =================================================================== RCS file: /data/cvs/libvirt/src/remote_internal.c,v retrieving revision 1.60 diff -u -r1.60 remote_internal.c --- src/remote_internal.c 20 Feb 2008 15:23:36 -0000 1.60 +++ src/remote_internal.c 25 Feb 2008 16:45:38 -0000 @@ -2681,8 +2681,115 @@ return 0; } +static int +remoteNetworkNumOfDHCPHostMappings (virNetworkPtr network) +{ + remote_network_num_of_dhcp_host_mappings_args args; + remote_network_num_of_dhcp_host_mappings_ret ret; + GET_NETWORK_PRIVATE (network->conn, -1); + + make_nonnull_network (&args.net, network); + + if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_NUM_OF_DHCP_HOST_MAPPINGS, + (xdrproc_t) xdr_remote_network_num_of_dhcp_host_mappings_args, (char *) &args, + (xdrproc_t) xdr_remote_network_num_of_dhcp_host_mappings_ret, (char *) &ret) == -1) + return -1; + + return ret.num; +} + + +static int +remoteNetworkListDHCPHostMappings (virNetworkPtr network, + char **const hwaddrs, + char **const ipaddrs, + char **const hostnames, + int maxmappings) +{ + remote_network_list_dhcp_host_mappings_args args; + remote_network_list_dhcp_host_mappings_ret ret; + int i; + GET_NETWORK_PRIVATE (network->conn, -1); + + make_nonnull_network (&args.net, network); + args.maxmappings = maxmappings; + + if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_LIST_DHCP_HOST_MAPPINGS, + (xdrproc_t) xdr_remote_network_list_dhcp_host_mappings_args, (char *) &args, + (xdrproc_t) xdr_remote_network_list_dhcp_host_mappings_ret, (char *) &ret) == -1) + return -1; + + if (ret.hwaddrs.hwaddrs_len > maxmappings || + ret.ipaddrs.ipaddrs_len > maxmappings || + ret.hostnames.hostnames_len > maxmappings) { + error (network->conn, VIR_ERR_INTERNAL_ERROR, + _("remote end returned more mappings than we asked for")); + return -1; + } + + if (ret.hwaddrs.hwaddrs_len != ret.ipaddrs.ipaddrs_len || + ret.ipaddrs.ipaddrs_len != ret.hostnames.hostnames_len) { + error (network->conn, VIR_ERR_INTERNAL_ERROR, + _("incoherent numbers of mappings from remote end")); + return -1; + } + + /* Caller frees. Since xdr_free will free up the strings we + * have to strdup them here. + */ + for (i = 0; i < ret.hwaddrs.hwaddrs_len; ++i) { + hwaddrs[i] = strdup (ret.hwaddrs.hwaddrs_val[i]); + ipaddrs[i] = strdup (ret.ipaddrs.ipaddrs_val[i]); + /* See note in remote_protocol.x */ + hostnames[i] = + STREQ (ret.hostnames.hostnames_val[i], "") ? + NULL : strdup (ret.hostnames.hostnames_val[i]); + } + + return ret.hwaddrs.hwaddrs_len; +} + +static int +remoteNetworkAddDHCPHostMapping (virNetworkPtr network, + const char *hwaddr, + const char *ipaddr, + const char *hostname, + unsigned int flags) +{ + remote_network_add_dhcp_host_mapping_args args; + GET_NETWORK_PRIVATE (network->conn, -1); + + make_nonnull_network (&args.net, network); + args.hwaddr = (char *) hwaddr; + args.ipaddr = (char *) ipaddr; + args.hostname = hostname == NULL ? NULL : (char **) &hostname; + args.flags = flags; + if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_ADD_DHCP_HOST_MAPPING, + (xdrproc_t) xdr_remote_network_add_dhcp_host_mapping_args, (char *) &args, + (xdrproc_t) xdr_void, (char *) NULL) == -1) + return -1; + return 0; +} + +static int +remoteNetworkDeleteDHCPHostMapping (virNetworkPtr network, + const char *hwaddr) +{ + remote_network_add_dhcp_host_mapping_args args; + GET_NETWORK_PRIVATE (network->conn, -1); + + make_nonnull_network (&args.net, network); + args.hwaddr = (char *) hwaddr; + + if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_DELETE_DHCP_HOST_MAPPING, + (xdrproc_t) xdr_remote_network_delete_dhcp_host_mapping_args, (char *) &args, + (xdrproc_t) xdr_void, (char *) NULL) == -1) + return -1; + + return 0; +} /*----------------------------------------------------------------------*/ @@ -4671,6 +4778,10 @@ .networkGetBridgeName = remoteNetworkGetBridgeName, .networkGetAutostart = remoteNetworkGetAutostart, .networkSetAutostart = remoteNetworkSetAutostart, + .networkNumOfDHCPHostMappings = remoteNetworkNumOfDHCPHostMappings, + .networkListDHCPHostMappings = remoteNetworkListDHCPHostMappings, + .networkAddDHCPHostMapping = remoteNetworkAddDHCPHostMapping, + .networkDeleteDHCPHostMapping = remoteNetworkDeleteDHCPHostMapping, }; static virStorageDriver storage_driver = { Index: src/test.c =================================================================== RCS file: /data/cvs/libvirt/src/test.c,v retrieving revision 1.66 diff -u -r1.66 test.c --- src/test.c 22 Feb 2008 15:55:04 -0000 1.66 +++ src/test.c 25 Feb 2008 16:45:39 -0000 @@ -2013,6 +2013,10 @@ testNetworkGetBridgeName, /* networkGetBridgeName */ testNetworkGetAutostart, /* networkGetAutostart */ testNetworkSetAutostart, /* networkSetAutostart */ + NULL, /* networkNumOfDHCPHostMappings */ + NULL, /* networkListDHCPHostMappings */ + NULL, /* networkAddDHCPHostMapping */ + NULL, /* networkDeleteDHCPHostMapping */ }; Index: src/virsh.c =================================================================== RCS file: /data/cvs/libvirt/src/virsh.c,v retrieving revision 1.133 diff -u -r1.133 virsh.c --- src/virsh.c 22 Feb 2008 15:55:04 -0000 1.133 +++ src/virsh.c 25 Feb 2008 16:45:43 -0000 @@ -2438,6 +2438,181 @@ /* + * "net-dhcp-host-list" command + */ +static vshCmdInfo info_network_dhcp_host_list[] = { + {"syntax", "net-dhcp-host-list "}, + {"help", gettext_noop("list DHCP host mappings on a network")}, + {"desc", gettext_noop("Returns list of DHCP host mappings on a network.")}, + {NULL, NULL} +}; + +static vshCmdOptDef opts_network_dhcp_host_list[] = { + {"network", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("network name, id or uuid")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdNetworkDHCPHostList (vshControl *ctl, vshCmd *cmd) +{ + virNetworkPtr network; + int i, ret; + char *name; + char **hwaddrs, **ipaddrs, **hostnames; + int nr_mappings; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(network = vshCommandOptNetwork(ctl, cmd, "network", &name))) + return FALSE; + + nr_mappings = virNetworkNumOfDHCPHostMappings (network); + if (nr_mappings == -1) { + virNetworkFree (network); + return FALSE; + } + + ipaddrs = vshMalloc (ctl, nr_mappings * sizeof (char *)); + hwaddrs = vshMalloc (ctl, nr_mappings * sizeof (char *)); + hostnames = vshMalloc (ctl, nr_mappings * sizeof (char *)); + ret = + virNetworkListDHCPHostMappings (network, + hwaddrs, ipaddrs, hostnames, + nr_mappings); + + if (ret == -1) { + virNetworkFree (network); + free (ipaddrs); + free (hwaddrs); + free (hostnames); + return FALSE; + } + + for (i = 0; i < ret; ++i) { + printf ("%-20s %-20s ", hwaddrs[i], ipaddrs[i]); + if (hostnames[i]) printf ("%s", hostnames[i]); + printf ("\n"); + } + + for (i = 0; i < ret; ++i) { + free (ipaddrs[i]); + free (hwaddrs[i]); + free (hostnames[i]); + } + free (ipaddrs); + free (hwaddrs); + free (hostnames); + + virNetworkFree(network); + + return TRUE; +} + + +/* + * "net-dhcp-host-add" command + */ +static vshCmdInfo info_network_dhcp_host_add[] = { + {"syntax", "net-dhcp-host-list []"}, + {"help", gettext_noop("add a DHCP host mappings")}, + {"desc", gettext_noop("Adds a DHCP host mapping to the given network.")}, + {NULL, NULL} +}; + +static vshCmdOptDef opts_network_dhcp_host_add[] = { + {"network", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("network name, id or uuid")}, + {"hwaddr", VSH_OT_STRING, VSH_OFLAG_REQ, gettext_noop("hardware (MAC) address")}, + {"ipaddr", VSH_OT_STRING, VSH_OFLAG_REQ, gettext_noop("IP address")}, + {"hostname", VSH_OT_STRING, VSH_OFLAG_NONE, gettext_noop("optional hostname to assign over DHCP")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdNetworkDHCPHostAdd (vshControl *ctl, vshCmd *cmd) +{ + virNetworkPtr network; + int ret; + char *name; + char *hwaddr, *ipaddr, *hostname; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(network = vshCommandOptNetwork(ctl, cmd, "network", &name))) + return FALSE; + + if (!(hwaddr = vshCommandOptString (cmd, "hwaddr", NULL))) { + virNetworkFree (network); + return FALSE; + } + if (!(ipaddr = vshCommandOptString (cmd, "ipaddr", NULL))) { + virNetworkFree (network); + return FALSE; + } + + hostname = vshCommandOptString (cmd, "hostname", NULL); + + ret = virNetworkAddDHCPHostMapping (network, hwaddr, ipaddr, hostname, 0); + if (ret == -1) { + virNetworkFree (network); + return FALSE; + } + + virNetworkFree(network); + + return TRUE; +} + + +/* + * "net-dhcp-host-delete" command + */ +static vshCmdInfo info_network_dhcp_host_delete[] = { + {"syntax", "net-dhcp-host-list "}, + {"help", gettext_noop("delete a DHCP host mappings")}, + {"desc", gettext_noop("Deletes a DHCP host mapping from the given network.")}, + {NULL, NULL} +}; + +static vshCmdOptDef opts_network_dhcp_host_delete[] = { + {"network", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("network name, id or uuid")}, + {"hwaddr", VSH_OT_STRING, VSH_OFLAG_REQ, gettext_noop("hardware (MAC) address")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdNetworkDHCPHostDelete (vshControl *ctl, vshCmd *cmd) +{ + virNetworkPtr network; + int ret; + char *name; + char *hwaddr; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(network = vshCommandOptNetwork(ctl, cmd, "network", &name))) + return FALSE; + + if (!(hwaddr = vshCommandOptString (cmd, "hwaddr", NULL))) { + virNetworkFree (network); + return FALSE; + } + + ret = virNetworkDeleteDHCPHostMapping (network, hwaddr); + if (ret == -1) { + virNetworkFree (network); + return FALSE; + } + + virNetworkFree(network); + + return TRUE; +} + + +/* * "net-dumpxml" command */ static vshCmdInfo info_network_dumpxml[] = { @@ -5110,6 +5285,9 @@ {"net-create", cmdNetworkCreate, opts_network_create, info_network_create}, {"net-define", cmdNetworkDefine, opts_network_define, info_network_define}, {"net-destroy", cmdNetworkDestroy, opts_network_destroy, info_network_destroy}, + {"net-dhcp-host-add", cmdNetworkDHCPHostAdd, opts_network_dhcp_host_add, info_network_dhcp_host_add}, + {"net-dhcp-host-delete", cmdNetworkDHCPHostDelete, opts_network_dhcp_host_delete, info_network_dhcp_host_delete}, + {"net-dhcp-host-list", cmdNetworkDHCPHostList, opts_network_dhcp_host_list, info_network_dhcp_host_list}, {"net-dumpxml", cmdNetworkDumpXML, opts_network_dumpxml, info_network_dumpxml}, {"net-list", cmdNetworkList, opts_network_list, info_network_list}, {"net-name", cmdNetworkName, opts_network_name, info_network_name},