[Libguestfs] [PATCH FOR DISCUSSION ONLY 1/2] v2v: inspect: Collect Windows network interfaces.

Richard W.M. Jones rjones at redhat.com
Tue Dec 4 17:29:32 UTC 2018


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="Local Area Connection* 1",
    if_default_gateway=, if_ip_address=, if_enable_dhcp=true,
    if_subnet_mask= },
  { if_name="Ethernet",
    if_default_gateway=, if_ip_address=, if_enable_dhcp=true,
    if_subnet_mask= }
]

Another guest which had a static IP returned:

i_interfaces = [
  { if_name="Ethernet1",
    if_default_gateway=, if_ip_address=, if_enable_dhcp=true,
    if_subnet_mask= },
  { if_name="Ethernet",
    if_default_gateway=, if_ip_address=, if_enable_dhcp=true,
    if_subnet_mask= },
  { if_name="Local Area Connection* 1",
    if_default_gateway=, if_ip_address=, if_enable_dhcp=true,
    if_subnet_mask= },
  { if_name="Ethernet0", if_default_gateway=10.19.2.1,
    if_ip_address=10.19.2.99, if_enable_dhcp=false,
    if_subnet_mask=255.255.255.128 }
]
---
 common/mltools/registry.ml  |  19 ++++++
 common/mltools/registry.mli |   4 ++
 v2v/inspect_source.ml       | 113 +++++++++++++++++++++++++++++++++++-
 v2v/types.ml                |  32 ++++++++--
 v2v/types.mli               |  23 ++++++--
 5 files changed, 180 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..b05a5b5e2 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,108 @@ 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
+
+          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
+
+          { if_name; if_default_gateway; if_ip_address; if_enable_dhcp;
+            if_subnet_mask }
+      ) interfaces in
+
+      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..d94fad3cc 100644
--- a/v2v/types.ml
+++ b/v2v/types.ml
@@ -361,14 +361,23 @@ 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;
 }
 
-let string_of_inspect inspect =
+and i_interface = {
+  if_name : string;
+  if_default_gateway : string;
+  if_ip_address : string;
+  if_enable_dhcp : bool;
+  if_subnet_mask : string;
+}
+
+let rec string_of_inspect inspect =
   sprintf "\
 i_root = %s
 i_type = %s
@@ -381,11 +390,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 +407,23 @@ 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 } =
+  sprintf "{if_name=%s, if_default_gateway=%s, if_ip_address=%s, if_enable_dhcp=%b, if_subnet_mask=%s}"
+          if_name if_default_gateway if_ip_address if_enable_dhcp if_subnet_mask
 
 type guestcaps = {
   gcaps_block_bus : guestcaps_block_type;
diff --git a/v2v/types.mli b/v2v/types.mli
index 6f7a0b5d2..d6ff1b061 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,29 @@ 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 *)
+}
+
 val string_of_inspect : inspect -> string
 
 (** {2 Command line parameters} *)
-- 
2.19.0.rc0




More information about the Libguestfs mailing list