Richard W.M. Jones
2018-Dec-11 15:06 UTC
[Libguestfs] [PATCH v2 2/2] v2v: Copy static IP address information over for Windows guests (RHBZ#1626503).
v1 was here with much discussion: https://www.redhat.com/archives/libguestfs/2018-December/msg00048.html v2: - Fix the case where there are multiple interfaces. Note this does not preserve order correctly (see patch for comment on why that is a hard problem). - Preserve name servers. This patch is still for discussion only. I'd like to see what might be done to get this upstream in a way that will not change existing cloud-related uses of virt-v2v. Perhaps this should only be operational with a command line flag, or triggered only for particular input and output modes. For those interested, this is what the Powershell script ends up looking like: ---------------------------------------------------------------------- Set-PSDebug -Trace 1 # Wait for the netkvm (virtio-net) driver to become active. $adapters = @() While (-Not $adapters) { Start-Sleep -Seconds 5 $adapters = (Get-NetAdapter | Where DriverFileName -eq "netkvm.sys").InterfaceAlias | Sort-Object Write-Host "adapters = '$adapters'" } New-NetIPAddress -InterfaceAlias $adapters[0] -IPAddress "10.0.2.15" -DefaultGateway "10.0.2.2" -PrefixLength 8 Set-DnsClientServerAddress -InterfaceAlias $adapters[0] -ServerAddresses ("10.0.2.3") New-NetIPAddress -InterfaceAlias $adapters[1] -IPAddress "10.0.2.16" -DefaultGateway "10.0.2.2" -PrefixLength 8 Set-DnsClientServerAddress -InterfaceAlias $adapters[1] -ServerAddresses ("10.0.2.3") ---------------------------------------------------------------------- Rich.
Richard W.M. Jones
2018-Dec-11 15:06 UTC
[Libguestfs] [PATCH v2 1/2] v2v: inspect: Collect Windows network interfaces.
Extend the inspection data to include information about source guest network interfaces. Collect network interface information for Windows guests (only). For a test guest using DHCP this returned [my formatting added]: i_interfaces = [ { if_name="Ethernet", if_default_gateway=, if_ip_address=, if_enable_dhcp=true, if_subnet_mask=, if_nameserver=[10.0.2.3] }, { if_name="Local Area Connection* 1", if_default_gateway=, if_ip_address=, if_enable_dhcp=true, if_subnet_mask=, if_nameserver=[] }, ] Another guest which had two static IPs before conversion: i_interfaces = [ { if_name="Ethernet", if_default_gateway=10.0.2.2, if_ip_address=10.0.2.15, if_enable_dhcp=false, if_subnet_mask=255.0.0.0, if_nameserver=[10.0.2.3] }, { if_name="Ethernet 2", if_default_gateway=10.0.2.2, if_ip_address=10.0.2.16, if_enable_dhcp=false, if_subnet_mask=255.0.0.0, if_nameserver=[10.0.2.3] }, { if_name="Local Area Connection* 9", if_default_gateway=, if_ip_address=, if_enable_dhcp=true, if_subnet_mask=, if_nameserver=[] }, ] --- common/mltools/registry.ml | 19 +++++ common/mltools/registry.mli | 4 ++ v2v/inspect_source.ml | 140 +++++++++++++++++++++++++++++++++++- v2v/types.ml | 35 +++++++-- v2v/types.mli | 24 +++++-- 5 files changed, 211 insertions(+), 11 deletions(-) diff --git a/common/mltools/registry.ml b/common/mltools/registry.ml index 1ed465910..141917db4 100644 --- a/common/mltools/registry.ml +++ b/common/mltools/registry.ml @@ -96,3 +96,22 @@ let decode_utf16le str Bytes.unsafe_set copy i cl done; Bytes.to_string copy + +let rec split_multi_sz ss + let n = String.length ss in + List.rev (_split_multi_sz ss 0 n []) + +and _split_multi_sz ss i n acc + let lenbytes = _utf16_length_in_bytes ss i n in + if lenbytes = 0 then acc (* base case *) + else ( + (* Get the next string without \0\0. *) + let r = String.sub ss i (lenbytes-2) in + _split_multi_sz ss (i+lenbytes) n (r::acc) + ) + +(* Find the length of the string in bytes including terminating \0\0. *) +and _utf16_length_in_bytes ss i n + if i+1 >= n then 0 + else if ss.[i] = '\000' && ss.[i+1] = '\000' then 2 + else 2 + _utf16_length_in_bytes ss (i+2) n diff --git a/common/mltools/registry.mli b/common/mltools/registry.mli index 5cbcacf5e..226552809 100644 --- a/common/mltools/registry.mli +++ b/common/mltools/registry.mli @@ -52,3 +52,7 @@ val encode_utf16le : string -> string val decode_utf16le : string -> string (** Helper: Take a UTF-16LE string and decode it to UTF-8. *) + +val split_multi_sz : string -> string list +(** Helper: Split up a multiple string (type = REG_MULTI_SZ = 7). + This does {i not} decode the strings. *) diff --git a/v2v/inspect_source.ml b/v2v/inspect_source.ml index c1a7e5737..b04bf21ee 100644 --- a/v2v/inspect_source.ml +++ b/v2v/inspect_source.ml @@ -98,6 +98,14 @@ let rec inspect_source root_choice g | _ -> "", "", "", "" in + (* For Windows only get the network interfaces of the source. *) + let ifs + match typ with + | "windows" -> + get_network_interfaces g root system_hive current_cs + | _ -> + [] in + let inspect = { i_root = root; i_type = typ; @@ -113,11 +121,12 @@ let rec inspect_source root_choice g i_mountpoints = mps; i_apps = apps; i_apps_map = apps_map; - i_firmware = get_firmware_bootable_device g; i_windows_systemroot = systemroot; i_windows_software_hive = software_hive; i_windows_system_hive = system_hive; i_windows_current_control_set = current_cs; + i_firmware = get_firmware_bootable_device g; + i_interfaces = ifs; } in debug "%s" (string_of_inspect inspect); @@ -218,6 +227,135 @@ and get_firmware_bootable_device g | [] -> I_BIOS | partitions -> I_UEFI partitions +(* For Windows only get the network interfaces of the source. + * + * We start at \CurrentControlSet\Control\Network. Under this + * node is the Network Adapter class (with the specific class GUID + * defined below). Under here is a list of network adapter GUIDs + * which we can cross reference with + * \CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{GUID} + * to find the DHCP configuration, IP address etc. + *) +and get_network_interfaces g root system_hive current_cs + with_return (fun {return} -> + Registry.with_hive_readonly g system_hive (fun reg -> + (* https://docs.microsoft.com/en-us/windows-hardware/drivers/install/system-defined-device-setup-classes-available-to-vendors *) + let network_adapter_guid = "{4D36E972-E325-11CE-BFC1-08002BE10318}" in + let path = [ current_cs; "Control"; "Network"; + network_adapter_guid ] in + let node + match Registry.get_node reg path with + | Some node -> node + | None -> return [] in + + let interfaces = g#hivex_node_children node in + let interfaces = Array.to_list interfaces in + + (* Get the node name (GUID). If it exists then the + * <node>\Connection\Name key is the interface name such + * as "Ethernet0". + * + * The GUID can also be cross-referenced under + * CurrentControlSet\Services\Tcpip\Parameters\Interfaces + * (see below). + *) + let interfaces = List.filter_map ( + fun { G.hivex_node_h = node } -> + try + let guid = g#hivex_node_name node in + let connection_node = g#hivex_node_get_child node "Connection" in + let if_name_v = g#hivex_node_get_value connection_node "Name" in + let if_name = g#hivex_value_string if_name_v in + Some (guid, if_name) + with + Not_found | G.Error _ -> None + ) interfaces in + + (* Cross reference GUID. *) + let interfaces = List.filter_map ( + fun (guid, if_name) -> + let path = [ current_cs; "Services"; "Tcpip"; + "Parameters"; "Interfaces"; guid ] in + match Registry.get_node reg path with + | Some node -> Some (node, guid, if_name) + | None -> None + ) interfaces in + + (* Get the fields we are interested in. *) + let interfaces = List.map ( + fun (node, guid, if_name) -> + let values = g#hivex_node_values node in + let values = Array.to_list values in + + (* Convert to list of pairs (key, value). *) + let values + List.map (fun { G.hivex_value_h = v } -> + String.lowercase_ascii (g#hivex_value_key v), v) + values in + + (* Some of the registry fields are REG_MULTI_SZ (= 7) and we + * only want the first string. + *) + let first_string_of_multi_sz v + let t = g#hivex_value_type v in + if t <> 7_L then raise Not_found; + let data = g#hivex_value_value v in + let strs = Registry.split_multi_sz data in + if strs = [] then raise Not_found; + let str = List.hd strs in + Registry.decode_utf16le str + in + (* The NameServer field is str(1) with an extra \0 and using + * whitespace to separate members of the list. + *) + let strings_of_nameserver v + let t = g#hivex_value_type v in + if t <> 1_L then raise Not_found; + let data = g#hivex_value_value v in + let str = Registry.decode_utf16le data in + let len = String.length str in + let str + if len > 0 && str.[len-1] = '\000' then + String.sub str 0 (len-1) + else + str in + String.nsplit " " str + in + + let if_default_gateway + try first_string_of_multi_sz (List.assoc "defaultgateway" values) + with Not_found -> "" in + let if_ip_address + try first_string_of_multi_sz (List.assoc "ipaddress" values) + with Not_found -> "" in + let if_enable_dhcp + try + let v = List.assoc "enabledhcp" values in + int_of_le32 (g#hivex_value_value v) <> 0_L + with Not_found -> false in + let if_subnet_mask + try first_string_of_multi_sz (List.assoc "subnetmask" values) + with Not_found -> "" in + let if_nameserver + try strings_of_nameserver (List.assoc "nameserver" values) + with Not_found -> [] in + + { if_name; if_default_gateway; if_ip_address; if_enable_dhcp; + if_subnet_mask; if_nameserver } + ) interfaces in + + (* XXX What order should the interfaces be returned in? + * Because we currently lack MAC address information + * (but we should look again at Windows 10) the best we + * can do here is to order them by name, as they seem + * to be named "Ethernet", "Ethernet 2", etc. on the + * source. + *) + List.sort (fun { if_name = a } { if_name = b } -> compare a b) + interfaces + ) + ) + (* If some inspection fields are "unknown", then that indicates a * failure in inspection, and we shouldn't continue. For an example * of this, see RHBZ#1278371. However don't "assert" here, since diff --git a/v2v/types.ml b/v2v/types.ml index 5c4f3c8ec..d043dda80 100644 --- a/v2v/types.ml +++ b/v2v/types.ml @@ -361,14 +361,24 @@ type inspect = { i_mountpoints : (string * string) list; i_apps : Guestfs.application2 list; i_apps_map : Guestfs.application2 list StringMap.t; - i_firmware : i_firmware; i_windows_systemroot : string; i_windows_software_hive : string; i_windows_system_hive : string; i_windows_current_control_set : string; + i_firmware : i_firmware; + i_interfaces : i_interface list; +} + +and i_interface = { + if_name : string; + if_default_gateway : string; + if_ip_address : string; + if_enable_dhcp : bool; + if_subnet_mask : string; + if_nameserver : string list; } -let string_of_inspect inspect +let rec string_of_inspect inspect sprintf "\ i_root = %s i_type = %s @@ -381,11 +391,12 @@ i_package_format = %s i_package_management = %s i_product_name = %s i_product_variant = %s -i_firmware = %s i_windows_systemroot = %s i_windows_software_hive = %s i_windows_system_hive = %s i_windows_current_control_set = %s +i_firmware = %s +i_interfaces = [%s] " inspect.i_root inspect.i_type inspect.i_distro @@ -397,13 +408,25 @@ i_windows_current_control_set = %s inspect.i_package_management inspect.i_product_name inspect.i_product_variant - (match inspect.i_firmware with - | I_BIOS -> "BIOS" - | I_UEFI devices -> sprintf "UEFI [%s]" (String.concat ", " devices)) inspect.i_windows_systemroot inspect.i_windows_software_hive inspect.i_windows_system_hive inspect.i_windows_current_control_set + (match inspect.i_firmware with + | I_BIOS -> "BIOS" + | I_UEFI devices -> sprintf "UEFI [%s]" (String.concat ", " devices)) + (string_of_inspect_interfaces inspect.i_interfaces) + +and string_of_inspect_interfaces ifs + String.concat ", " (List.map string_of_inspect_interface ifs) + +and string_of_inspect_interface { if_name; if_default_gateway; + if_ip_address; if_enable_dhcp; + if_subnet_mask; if_nameserver } + sprintf "{if_name=%s, if_default_gateway=%s, if_ip_address=%s, if_enable_dhcp=%b, if_subnet_mask=%s, if_nameserver=[%s]}" + if_name + if_default_gateway if_ip_address if_enable_dhcp if_subnet_mask + (String.concat "," if_nameserver) type guestcaps = { gcaps_block_bus : guestcaps_block_type; diff --git a/v2v/types.mli b/v2v/types.mli index 6f7a0b5d2..f37cec1e4 100644 --- a/v2v/types.mli +++ b/v2v/types.mli @@ -325,7 +325,9 @@ val string_of_target_buses : target_buses -> string type inspect = { i_root : string; (** Root device. *) - i_type : string; (** Usual inspection fields. *) + + (** Usual guestfs inspection fields. *) + i_type : string; i_distro : string; i_osinfo : string; i_arch : string; @@ -341,16 +343,30 @@ type inspect = { (** This is a map from the app name to the application object. Since RPM allows multiple packages with the same name to be installed, the value is a list. *) - i_firmware : i_firmware; - (** The list of EFI system partitions for the guest with UEFI, - otherwise the BIOS identifier. *) + i_windows_systemroot : string; i_windows_software_hive : string; i_windows_system_hive : string; i_windows_current_control_set : string; + + (** The following fields are the result of additional guest + inspection done by virt-v2v. *) + i_firmware : i_firmware; (** The list of EFI system partitions for the + guest with UEFI, otherwise [BIOS]. *) + i_interfaces : i_interface list; (** List of network interfaces, + currently Windows only. *) } (** Inspection information. *) +and i_interface = { + if_name : string; + if_default_gateway : string; (* "" = no data *) + if_ip_address : string; (* "" = no data *) + if_enable_dhcp : bool; + if_subnet_mask : string; (* "" = no data *) + if_nameserver : string list; +} + val string_of_inspect : inspect -> string (** {2 Command line parameters} *) -- 2.19.1
Richard W.M. Jones
2018-Dec-11 15:06 UTC
[Libguestfs] [PATCH v2 2/2] v2v: Copy static IP address information over for Windows guests (RHBZ#1626503).
For Linux the guest itself remembers the IP address associated with each MAC address. Thus it doesn't matter if the interface type changes (ie. to virtio-net), because as long as we preserve the MAC address the guest will use the same IP address or the same DHCP configuration. However on Windows this association is not maintained by MAC address. In fact the MAC address isn't saved anywhere in the guest registry. (It seems instead this is likely done through PCI device type and address which we don't record at the moment and is almost impossible to preserve.) When a guest which doesn't use DHCP is migrated, the guest sees the brand new virtio-net devices and doesn't know what to do with them, and meanwhile the right static IPs are still associated with the old and now-defunct interfaces in the registry. This commit is an interim fix which, in limited situations described below, copies the static IP address of the old interface to the new virtio-net (netkvm.sys) interface. The installation of the old IP address on the new interface is done using a Powershell script. - Only works for IPv4 addresses. - Likely only works properly if the guest has a single physical interface. - Only works if the guest is getting virtio drivers. - Only works for Windows >= 7 (because of the Powershell dependency). A longer term fix for this will likely involve trying to decode the PCI address information (assuming my guess above is even correct) and associate that with source hypervisor devices, get the same information as now from the registry, and use a similar technique as this to copy to the new interface, but do it based on target MAC address. Thanks: Brett Thurber for diagnosing the problem and suggesting paths towards a fix. --- common/mlstdutils/std_utils.ml | 9 +++ common/mlstdutils/std_utils.mli | 10 +++ v2v/convert_windows.ml | 131 ++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+) diff --git a/common/mlstdutils/std_utils.ml b/common/mlstdutils/std_utils.ml index df443058f..6b58c1de3 100644 --- a/common/mlstdutils/std_utils.ml +++ b/common/mlstdutils/std_utils.ml @@ -269,6 +269,8 @@ module String = struct else loop (i+1) in loop 0 + + let unix2dos str = replace str "\n" "\r\n" end module List = struct @@ -836,3 +838,10 @@ let read_first_line_from_file filename let is_regular_file path = (* NB: follows symlinks. *) try (Unix.stat path).Unix.st_kind = Unix.S_REG with Unix.Unix_error _ -> false + +let rec ipv4_prefix_length mask + if mask = 0_l then 0 + else if mask >= 0x8000_0000_l (* top bit set *) then + 1 + ipv4_prefix_length (Int32.shift_left mask 1) + else (* mask is invalid if top bit is not set but other bits are set *) + invalid_arg "ipv4_prefix_length" diff --git a/common/mlstdutils/std_utils.mli b/common/mlstdutils/std_utils.mli index 62cb8e9ff..bc2d52fc6 100644 --- a/common/mlstdutils/std_utils.mli +++ b/common/mlstdutils/std_utils.mli @@ -131,6 +131,11 @@ module String : sig segment of [str] which contains only bytes {!i not} in [reject]. These work exactly like the C functions [strspn] and [strcspn]. *) + val unix2dos : string -> string + (** Convert string with ordinary Unix-style line-endings to + CRLF DOS-style line-endings. + + The same as {!String.replace} [str "\n" "\r\n"]. *) end (** Override the String module from stdlib. *) @@ -447,3 +452,8 @@ val read_first_line_from_file : string -> string val is_regular_file : string -> bool (** Checks whether the file is a regular file. *) + +val ipv4_prefix_length : int32 -> int +(** Calculate the prefix length of the given IPv4 network mask + (given in host byte order). Raises [Invalid_argument _] if + not a network mask. *) diff --git a/v2v/convert_windows.ml b/v2v/convert_windows.ml index 2d2b6adfe..610af5793 100644 --- a/v2v/convert_windows.ml +++ b/v2v/convert_windows.ml @@ -38,10 +38,23 @@ module G = Guestfs * time the Windows VM is booted on KVM. *) +let ipv4_re = PCRE.compile "^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$" + let convert (g : G.guestfs) inspect source output rcaps (*----------------------------------------------------------------------*) (* Inspect the Windows guest. *) + (* All physical network interfaces. *) + let all_physical_interfaces + List.filter (fun { if_name } -> String.is_prefix if_name "Ethernet") + inspect.i_interfaces in + + (* All physical network interfaces have static IP addresses? *) + let all_physical_interfaces_are_static + all_physical_interfaces <> [] && + List.for_all (fun { if_enable_dhcp } -> if_enable_dhcp = false) + all_physical_interfaces in + (* If the Windows guest appears to be using group policy. *) let has_group_policy Registry.with_hive_readonly g inspect.i_windows_software_hive @@ -220,6 +233,16 @@ let convert (g : G.guestfs) inspect source output rcaps Registry.with_hive_write g inspect.i_windows_software_hive update_software_hive; + (* If we have only static interfaces then we need to copy the + * IP addresses from the old interfaces to the new virtio-net + * interfaces. Only works for Win7 and above. (RHBZ#1626503) + *) + if net_driver = Virtio_net && + all_physical_interfaces_are_static && + (inspect.i_major_version, inspect.i_minor_version) >= (6, 1) + then + configure_ip_address_transfer_at_firstboot (); + fix_ntfs_heads (); fix_win_esp (); @@ -595,6 +618,114 @@ if errorlevel 3010 exit /b 0 | None -> warning (f_"could not find registry key HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion") + and configure_ip_address_transfer_at_firstboot () + (* For guests which don't use DHCP we will need to copy network + * information from the first of the old interfaces to the + * first of the virtio-net interfaces. Unfortunately Windows + * doesn't save information about MAC address so we have no way + * to associate which original interface corresponds to which + * target interface. Instead we simply apply the settings to + * the adapters in sorted name order. XXX (RHBZ#1626503) + *) + + assert (all_physical_interfaces <> []); (* checked by caller *) + + let tempdir = sprintf "%s/Temp" inspect.i_windows_systemroot in + let psh_filename = "v2v-ip-address-transfer.ps1" in + let psh_prologue = "\ +Set-PSDebug -Trace 1 + +# Wait for the netkvm (virtio-net) driver to become active. +$adapters = @() +While (-Not $adapters) { + Start-Sleep -Seconds 5 + $adapters = (Get-NetAdapter | Where DriverFileName -eq \"netkvm.sys\").InterfaceAlias | Sort-Object + Write-Host \"adapters = '$adapters'\" +} + +" in + + (* Append the command(s) to set up each interface. *) + let psh_cmds + List.mapi ( + fun i { if_default_gateway; if_ip_address; if_subnet_mask; + if_nameserver } -> + let cmd = "New-NetIPAddress" in + let args = ref [] in + List.push_back args "-InterfaceAlias"; + List.push_back args (sprintf "$adapters[%d]" i); + + (* Parameters of New-NetIPAddress. + * https://docs.microsoft.com/en-us/powershell/module/nettcpip/new-netipaddress?view=win10-ps + * Only works for IPv4 right now. We need to find an example + * of a Windows guest using IPv6 to see what the registry + * contains. XXX + *) + if if_ip_address <> "" then ( + List.push_back args "-IPAddress"; + List.push_back args ("\"" ^ if_ip_address ^ "\"") + ); + if if_default_gateway <> "" then ( + List.push_back args "-DefaultGateway"; + List.push_back args ("\"" ^ if_default_gateway ^ "\"") + ); + if PCRE.matches ipv4_re if_subnet_mask then ( + let a = Int32.of_string (PCRE.sub 1) + and b = Int32.of_string (PCRE.sub 2) + and c = Int32.of_string (PCRE.sub 3) + and d = Int32.of_string (PCRE.sub 4) in + (* Can we calculate a mask? *) + if a >= 0_l && a <= 255_l && b >= 0_l && b <= 255_l && + c >= 0_l && c <= 255_l && d >= 0_l && d <= 255_l then ( + let a = Int32.shift_left a 24 + and b = Int32.shift_left b 16 + and c = Int32.shift_left c 8 in + let mask = Int32.logor (Int32.logor (Int32.logor a b) c) d in + try + let prefix_length = ipv4_prefix_length mask in + List.push_back args "-PrefixLength"; + List.push_back args (string_of_int prefix_length) + with Invalid_argument _ -> () + ) + ); + let cmd1 = cmd ^ " " ^ String.concat " " !args in + + let cmd2 + if if_nameserver <> [] then ( + let nslist = List.map (sprintf "\"%s\"") if_nameserver in + let nslist = "(" ^ String.concat "," nslist ^ ")" in + + let cmd = "Set-DnsClientServerAddress" in + let args = ref [] in + List.push_back args "-InterfaceAlias"; + List.push_back args (sprintf "$adapters[%d]" i); + List.push_back args "-ServerAddresses"; + List.push_back args nslist; + cmd ^ " " ^ String.concat " " !args + ) + else "" in + + cmd1 ^ "\n" ^ cmd2 ^ "\n" + ) all_physical_interfaces in + + let psh = psh_prologue ^ String.concat "\n" psh_cmds in + + (* Powershell scripts fail completely unless they have DOS line endings. *) + let psh = String.unix2dos psh in + g#mkdir_p tempdir; + g#write (tempdir ^ "/" ^ psh_filename) psh; + + (* Unfortunately Powershell scripts cannot be directly executed + * (unless some system config changes are made which for other + * reasons we don't want to do) and so we have to run this via + * a regular batch file. + *) + let fb + sprintf "%s\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -ExecutionPolicy ByPass -file C:%s\\%s" + inspect.i_windows_systemroot + (String.replace_char tempdir '/' '\\') psh_filename in + Firstboot.add_firstboot_script g inspect.i_root "ip address transfer" fb + and fix_ntfs_heads () (* NTFS hardcodes the number of heads on the drive which created it in the filesystem header. Modern versions of Windows -- 2.19.1
Reasonably Related Threads
- [PATCH FOR DISCUSSION ONLY 0/2] v2v: Copy static IP address information over for Windows guests (RHBZ#1626503).
- [PATCH v2v 1/2] v2v: windows: Add a helper function for installing Powershell firstboot scripts.
- [PATCH v2v v2 2/2] v2v: Copy static IP address information.
- [PATCH v3 0/2] v2v: Copy static IP address information over for Windows guests
- [PATCH v2v 2/2] v2v: Copy static IP address information over for Windows guests (RHBZ#1626503).