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-...
*)
+ 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