Nowadays, qemu has supported physical NIC hotplug for high network
throughput. but it's in conflict with live migration feature, to keep
network connectivity, we could to create bond device interface which
provides a mechanism for enslaving multiple network interfaces into a
single "bond" interface. the active-backup mode can be used for an
automatic switch. so this patch is adding a guest-network-set-interface
command for creating bond device. so the management can easy to create
a bond device dynamically when guest running.
Signed-off-by: Chen Fan <chen.fan.fnst@xxxxxxxxxxxxxx>
---
configure | 16 ++++
qga/commands-posix.c | 261 +++++++++++++++++++++++++++++++++++++++++++++++++++
qga/commands-win32.c | 7 ++
qga/qapi-schema.json | 54 +++++++++++
4 files changed, 338 insertions(+)
diff --git a/configure b/configure
index f185dd0..ebfcc6a 100755
--- a/configure
+++ b/configure
@@ -3618,6 +3618,18 @@ if test "$darwin" != "yes" -a "$mingw32" != "yes" -a "$solaris" != yes -a \
fi
##########################################
+# Do we need netcf
+netcf=no
+cat > $TMPC << EOF
+#include <netcf.h>
+int main(void) { return 0; }
+EOF
+if compile_prog "" "-lnetcf" ; then
+ netcf=yes
+ libs_qga="$libs_qga -lnetcf"
+fi
+
+##########################################
# spice probe
if test "$spice" != "no" ; then
cat > $TMPC << EOF
@@ -4697,6 +4709,10 @@ if test "$spice" = "yes" ; then
echo "CONFIG_SPICE=y" >> $config_host_mak
fi
+if test "$netcf" = "yes" ; then
+ echo "CONFIG_NETCF=y" >> $config_host_mak
+fi
+
if test "$smartcard_nss" = "yes" ; then
echo "CONFIG_SMARTCARD_NSS=y" >> $config_host_mak
echo "NSS_LIBS=$nss_libs" >> $config_host_mak
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index f6f3e3c..5ee7949 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -46,6 +46,10 @@ extern char **environ;
#include <sys/socket.h>
#include <net/if.h>
+#ifdef CONFIG_NETCF
+#include <netcf.h>
+#endif
+
#ifdef FIFREEZE
#define CONFIG_FSFREEZE
#endif
@@ -1719,6 +1723,263 @@ error:
return NULL;
}
+#ifdef CONFIG_NETCF
+static const char *interface_type_string[] = {
+ "bond",
+};
+
+static const char *ip_address_type_string[] = {
+ "ipv4",
+ "ipv6",
+};
+
+static char *parse_options(const char *str, const char *needle)
+{
+ char *start, *end, *buffer = NULL;
+ char *ret = NULL;
+
+ buffer = g_strdup(str);
+ start = buffer;
+ if ((start = strstr(start, needle))) {
+ start += strlen(needle);
+ end = strchr(start, ' ');
+ if (end) {
+ *end = '\0';
+ }
+ if (strlen(start) == 0) {
+ goto cleanup;
+ }
+ ret = g_strdup(start);
+ }
+
+cleanup:
+ g_free(buffer);
+ return ret;
+}
+
+/**
+ * @buffer: xml string data to be formatted
+ * @indent: indent number relative to first line
+ *
+ */
+static void adjust_indent(char **buffer, int indent)
+{
+ char spaces[1024];
+ int i;
+
+ if (!*buffer) {
+ return;
+ }
+
+ if (indent < 0 || indent >= 1024) {
+ return;
+ }
+ memset(spaces, 0, sizeof(spaces));
+ for (i = 0; i < indent; i++) {
+ spaces[i] = ' ';
+ }
+
+ sprintf(*buffer + strlen(*buffer), "%s", spaces);
+}
+
+static char *create_bond_interface(GuestNetworkInterface2 *interface)
+{
+ char *target_xml;
+
+ target_xml = g_malloc0(1024);
+ if (!target_xml) {
+ return NULL;
+ }
+
+ sprintf(target_xml, "<interface type='%s' name='%s'>\n",
+ interface_type_string[interface->type], interface->name);
+ adjust_indent(&target_xml, 2);
+ sprintf(target_xml + strlen(target_xml), "<start mode='%s'/>\n",
+ interface->has_onboot ? interface->onboot : "none");
+ if (interface->has_ip_address) {
+ GuestIpAddress *address_item = interface->ip_address;
+
+ adjust_indent(&target_xml, 2);
+ sprintf(target_xml + strlen(target_xml), "<protocol family='%s'>\n",
+ ip_address_type_string[address_item->ip_address_type]);
+ adjust_indent(&target_xml, 4);
+ sprintf(target_xml + strlen(target_xml), "<ip address='%s' prefix='%" PRId64 "'/>\n",
+ address_item->ip_address, address_item->prefix);
+ if (address_item->has_gateway) {
+ adjust_indent(&target_xml, 4);
+ sprintf(target_xml + strlen(target_xml), "<route gateway='%s'/>\n",
+ address_item->gateway);
+ }
+ adjust_indent(&target_xml, 2);
+ sprintf(target_xml + strlen(target_xml), "%s\n", "</protocol>");
+ }
+
+ adjust_indent(&target_xml, 2);
+ if (interface->has_options) {
+ char *value;
+
+ value = parse_options(interface->options, "mode=");
+ if (value) {
+ sprintf(target_xml + strlen(target_xml), "<bond mode='%s'>\n",
+ value);
+ g_free(value);
+ } else {
+ sprintf(target_xml + strlen(target_xml), "%s\n", "<bond>");
+ }
+
+ value = parse_options(interface->options, "miimon=");
+ if (value) {
+ adjust_indent(&target_xml, 4);
+ sprintf(target_xml + strlen(target_xml), "<miimon freq='%s'",
+ value);
+ g_free(value);
+
+ value = parse_options(interface->options, "updelay=");
+ if (value) {
+ sprintf(target_xml + strlen(target_xml), " updelay='%s'",
+ value);
+ g_free(value);
+ }
+ value = parse_options(interface->options, "downdelay=");
+ if (value) {
+ sprintf(target_xml + strlen(target_xml), " downdelay='%s'",
+ value);
+ g_free(value);
+ }
+ value = parse_options(interface->options, "use_carrier=");
+ if (value) {
+ sprintf(target_xml + strlen(target_xml), " carrier='%s'",
+ value);
+ g_free(value);
+ }
+
+ sprintf(target_xml + strlen(target_xml), "%s\n", "/>");
+ }
+
+ value = parse_options(interface->options, "arp_interval=");
+ if (value) {
+ adjust_indent(&target_xml, 4);
+ sprintf(target_xml + strlen(target_xml), "<arpmon interval='%s'",
+ value);
+ g_free(value);
+
+ value = parse_options(interface->options, "arp_ip_target=");
+ if (value) {
+ sprintf(target_xml + strlen(target_xml), " target='%s'",
+ value);
+ g_free(value);
+ }
+
+ value = parse_options(interface->options, "arp_validate=");
+ if (value) {
+ sprintf(target_xml + strlen(target_xml), " validate='%s'",
+ value);
+ g_free(value);
+ }
+
+ sprintf(target_xml + strlen(target_xml), "%s\n", "/>");
+ }
+ } else {
+ sprintf(target_xml + strlen(target_xml), "%s\n", "<bond>");
+ }
+
+ if (interface->has_subInterfaces) {
+ GuestNetworkInterfaceList *head = interface->subInterfaces;
+
+ for (; head; head = head->next) {
+ adjust_indent(&target_xml, 4);
+ sprintf(target_xml + strlen(target_xml),
+ "<interface type='ethernet' name='%s'/>\n",
+ head->value->name);
+ }
+ }
+
+ adjust_indent(&target_xml, 2);
+ sprintf(target_xml + strlen(target_xml), "%s\n", "</bond>");
+ sprintf(target_xml + strlen(target_xml), "%s\n", "</interface>");
+
+ return target_xml;
+}
+
+static struct netcf *netcf;
+
+static void create_interface(GuestNetworkInterface2 *interface, Error **errp)
+{
+ int ret = -1;
+ struct netcf_if *iface;
+ unsigned int flags = 0;
+ char *target_xml;
+
+ /* open netcf */
+ if (netcf == NULL) {
+ if (ncf_init(&netcf, NULL) != 0) {
+ error_setg(errp, "netcf init failed");
+ return;
+ }
+ }
+
+ if (interface->type != GUEST_INTERFACE_TYPE_BOND) {
+ error_setg(errp, "interface type is not supported, only support 'bond' type");
+ return;
+ }
+
+ target_xml = create_bond_interface(interface);
+ if (!target_xml) {
+ error_setg(errp, "no enough memory spaces");
+ return;
+ }
+
+ iface = ncf_define(netcf, target_xml);
+ if (!iface) {
+ error_setg(errp, "netcf interface define failed");
+ g_free(target_xml);
+ goto cleanup;
+ }
+
+ g_free(target_xml);
+
+ if (ncf_if_status(iface, &flags) < 0) {
+ error_setg(errp, "netcf interface get status failed");
+ goto cleanup;
+ }
+
+ if (flags & NETCF_IFACE_ACTIVE) {
+ error_setg(errp, "interface is already running");
+ goto cleanup;
+ }
+
+ ret = ncf_if_up(iface);
+ if (ret < 0) {
+ error_setg(errp, "netcf interface up failed");
+ goto cleanup;
+ }
+
+ cleanup:
+ ncf_if_free(iface);
+}
+
+int64_t qmp_guest_network_set_interface(GuestNetworkInterface2 *interface,
+ Error **errp)
+{
+ Error *local_err = NULL;
+
+ create_interface(interface, &local_err);
+ if (local_err != NULL) {
+ error_propagate(errp, local_err);
+ return -1;
+ }
+
+ return 0;
+}
+#else
+int64_t qmp_guest_network_set_interface(GuestNetworkInterface2 *interface,
+ Error **errp)
+{
+ error_set(errp, QERR_UNSUPPORTED);
+ return -1;
+}
+#endif
+
#define SYSCONF_EXACT(name, errp) sysconf_exact((name), #name, (errp))
static long sysconf_exact(int name, const char *name_str, Error **errp)
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
index 3bcbeae..4c14514 100644
--- a/qga/commands-win32.c
+++ b/qga/commands-win32.c
@@ -446,6 +446,13 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
return -1;
}
+int64_t qmp_guest_network_set_interface(GuestNetworkInterface2 *interface,
+ Error **errp)
+{
+ error_set(errp, QERR_UNSUPPORTED);
+ return -1;
+}
+
/* add unsupported commands to the blacklist */
GList *ga_command_blacklist_init(GList *blacklist)
{
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index 376e79f..77f499b 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -556,6 +556,7 @@
{ 'type': 'GuestIpAddress',
'data': {'ip-address': 'str',
'ip-address-type': 'GuestIpAddressType',
+ '*gateway': 'str',
'prefix': 'int'} }
##
@@ -575,6 +576,43 @@
'*ip-addresses': ['GuestIpAddress'] } }
##
+# @GuestInterfaceType:
+#
+# An enumeration of supported interface types
+#
+# @bond: bond device
+#
+# Since: 2.3
+##
+{ 'enum': 'GuestInterfaceType',
+ 'data': [ 'bond' ] }
+
+##
+# @GuestNetworkInterface2:
+#
+# @type: the interface type which supported in enum GuestInterfaceType.
+#
+# @name: the interface name.
+#
+# @onboot: the interface start model.
+#
+# @ip-address: IP address.
+#
+# @options: the options argument.
+#
+# @subInterfaces: the slave interfaces.
+#
+# Since: 2.3
+##
+{ 'type': 'GuestNetworkInterface2',
+ 'data': {'type': 'GuestInterfaceType',
+ 'name': 'str',
+ '*onboot': 'str',
+ '*ip-address': 'GuestIpAddress',
+ '*options': 'str',
+ '*subInterfaces': ['GuestNetworkInterface'] } }
+
+##
# @guest-network-get-interfaces:
#
# Get list of guest IP addresses, MAC addresses
@@ -588,6 +626,22 @@
'returns': ['GuestNetworkInterface'] }
##
+# @guest-network-set-interface:
+#
+# Set guest network interface
+#
+# return: 0: call successful.
+#
+# -1: call failed.
+#
+#
+# Since: 2.3
+##
+{ 'command': 'guest-network-set-interface',
+ 'data' : {'interface': 'GuestNetworkInterface2' },
+ 'returns': 'int' }