Blame SOURCES/0033-v2v-Copy-static-IP-address-information-over-for-Wind.patch

46b2f6
From 70e2215cd1f660dbad5a336bb611ad1b9cf1e60d Mon Sep 17 00:00:00 2001
46b2f6
From: "Richard W.M. Jones" <rjones@redhat.com>
46b2f6
Date: Tue, 4 Dec 2018 16:09:42 +0000
46b2f6
Subject: [PATCH] v2v: Copy static IP address information over for Windows
46b2f6
 guests (RHBZ#1626503).
46b2f6
46b2f6
For Linux the guest itself remembers the IP address associated with
46b2f6
each MAC address.  Thus it doesn't matter if the interface type
46b2f6
changes (ie. to virtio-net), because as long as we preserve the MAC
46b2f6
address the guest will use the same IP address or the same DHCP
46b2f6
configuration.
46b2f6
46b2f6
However on Windows this association is not maintained by MAC address.
46b2f6
In fact the MAC address isn't saved anywhere in the guest registry.
46b2f6
(It seems instead this is likely done through PCI device type and
46b2f6
address which we don't record at the moment and is almost impossible
46b2f6
to preserve.)  When a guest which doesn't use DHCP is migrated, the
46b2f6
guest sees the brand new virtio-net devices and doesn't know what to
46b2f6
do with them, and meanwhile the right static IPs are still associated
46b2f6
with the old and now-defunct interfaces in the registry.
46b2f6
46b2f6
We cannot collect the required information from within the guest.
46b2f6
However we can collect it outside the tool by some other means
46b2f6
(eg. using VMware Tools APIs) and present this information to virt-v2v
46b2f6
which then writes it into the Windows guest at firstboot time.
46b2f6
46b2f6
This commit adds the --mac ..:ip:.. sub-option which creates a
46b2f6
Powershell script to set network adapters at firstboot.  An option
46b2f6
such as:
46b2f6
46b2f6
  --mac 00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254
46b2f6
46b2f6
approximately turns into this script:
46b2f6
46b2f6
  # Wait for the netkvm (virtio-net) driver to become active.
46b2f6
  $adapters = @()
46b2f6
  While (-Not $adapters) {
46b2f6
      Start-Sleep -Seconds 5
46b2f6
      $adapters = Get-NetAdapter -Physical |
46b2f6
                     Where DriverFileName -eq "netkvm.sys"
46b2f6
  }
46b2f6
  $mac_address = '00-0c-29-e6-3d-9d'
46b2f6
  $ifindex = (Get-NetAdapter -Physical |
46b2f6
                 Where MacAddress -eq $mac_address).ifIndex
46b2f6
  if ($ifindex) {
46b2f6
      New-NetIPAddress -InterfaceIndex $ifindex
46b2f6
                       -IPAddress '192.168.0.89'
46b2f6
                       -DefaultGateway '192.168.0.1'
46b2f6
                       -PrefixLength 24
46b2f6
      Set-DnsClientServerAddress -InterfaceIndex $ifindex
46b2f6
                       -ServerAddresses ('192.168.0.254')
46b2f6
  }
46b2f6
46b2f6
Thanks: Brett Thurber for diagnosing the problem and suggesting paths
46b2f6
towards a fix.
46b2f6
46b2f6
(cherry picked from commit dfd9fac7435cf2f9293961b6cc1fe316af4feebc)
46b2f6
---
46b2f6
 v2v/cmdline.ml         | 41 ++++++++++++++++++-----
46b2f6
 v2v/cmdline.mli        |  1 +
46b2f6
 v2v/convert_linux.ml   |  2 +-
46b2f6
 v2v/convert_windows.ml | 76 +++++++++++++++++++++++++++++++++++++++++-
46b2f6
 v2v/modules_list.ml    |  2 +-
46b2f6
 v2v/modules_list.mli   |  2 +-
46b2f6
 v2v/types.ml           |  8 +++++
46b2f6
 v2v/types.mli          |  9 +++++
46b2f6
 v2v/v2v.ml             |  7 ++--
46b2f6
 v2v/virt-v2v.pod       | 18 ++++++++++
46b2f6
 10 files changed, 150 insertions(+), 16 deletions(-)
46b2f6
46b2f6
diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml
46b2f6
index 4d390f249..686631271 100644
46b2f6
--- a/v2v/cmdline.ml
46b2f6
+++ b/v2v/cmdline.ml
46b2f6
@@ -40,11 +40,12 @@ type cmdline = {
46b2f6
   print_estimate : bool;
46b2f6
   print_source : bool;
46b2f6
   root_choice : root_choice;
46b2f6
+  static_ips : static_ip list;
46b2f6
   ks : Tools_utils.key_store;
46b2f6
 }
46b2f6
 
46b2f6
 (* Matches --mac command line parameters. *)
46b2f6
-let mac_re = PCRE.compile ~anchored:true "([[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}):(network|bridge):(.*)"
46b2f6
+let mac_re = PCRE.compile ~anchored:true "([[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}):(network|bridge|ip):(.*)"
46b2f6
 
46b2f6
 let parse_cmdline () =
46b2f6
   let compressed = ref false in
46b2f6
@@ -97,6 +98,7 @@ let parse_cmdline () =
46b2f6
   in
46b2f6
 
46b2f6
   let network_map = Networks.create () in
46b2f6
+  let static_ips = ref [] in
46b2f6
   let add_network str =
46b2f6
     match String.split ":" str with
46b2f6
     | "", "" ->
46b2f6
@@ -119,11 +121,30 @@ let parse_cmdline () =
46b2f6
     if not (PCRE.matches mac_re str) then
46b2f6
       error (f_"cannot parse --mac \"%s\" parameter") str;
46b2f6
     let mac = PCRE.sub 1 and out = PCRE.sub 3 in
46b2f6
-    let vnet_type =
46b2f6
-      match PCRE.sub 2 with
46b2f6
-      | "network" -> Network | "bridge" -> Bridge
46b2f6
-      | _ -> assert false in
46b2f6
-    Networks.add_mac network_map mac vnet_type out
46b2f6
+    match PCRE.sub 2 with
46b2f6
+    | "network" ->
46b2f6
+       Networks.add_mac network_map mac Network out
46b2f6
+    | "bridge" ->
46b2f6
+       Networks.add_mac network_map mac Bridge out
46b2f6
+    | "ip" ->
46b2f6
+       let add if_mac_addr if_ip_address if_default_gateway
46b2f6
+               if_prefix_length if_nameservers =
46b2f6
+         List.push_back static_ips
46b2f6
+                        { if_mac_addr; if_ip_address; if_default_gateway;
46b2f6
+                          if_prefix_length; if_nameservers }
46b2f6
+       in
46b2f6
+       (match String.nsplit "," out with
46b2f6
+        | [] ->
46b2f6
+           error (f_"invalid --mac ip option")
46b2f6
+        | [ip] -> add mac ip None None []
46b2f6
+        | [ip; gw] -> add mac ip (Some gw) None []
46b2f6
+        | ip :: gw :: len :: nameservers ->
46b2f6
+           let len =
46b2f6
+             try int_of_string len with
46b2f6
+             | Failure _ -> error (f_"cannot parse --mac ip prefix length field as an integer: %s") len in
46b2f6
+           add mac ip (Some gw) (Some len) nameservers
46b2f6
+       );
46b2f6
+    | _ -> assert false
46b2f6
   in
46b2f6
 
46b2f6
   let no_trim_warning _ =
46b2f6
@@ -211,8 +232,8 @@ let parse_cmdline () =
46b2f6
                                     s_"Input transport";
46b2f6
     [ L"in-place" ], Getopt.Set in_place,
46b2f6
                                     s_"Only tune the guest in the input VM";
46b2f6
-    [ L"mac" ],      Getopt.String ("mac:network|bridge:out", add_mac),
46b2f6
-                                    s_"Map NIC to network or bridge";
46b2f6
+    [ L"mac" ],      Getopt.String ("mac:network|bridge|ip:out", add_mac),
46b2f6
+                                    s_"Map NIC to network or bridge or assign static IP";
46b2f6
     [ S 'n'; L"network" ], Getopt.String ("in:out", add_network),
46b2f6
                                     s_"Map network ‘in’ to ‘out’";
46b2f6
     [ L"no-copy" ],  Getopt.Clear do_copy,
46b2f6
@@ -335,6 +356,7 @@ read the man page virt-v2v(1).
46b2f6
   let print_source = !print_source in
46b2f6
   let qemu_boot = !qemu_boot in
46b2f6
   let root_choice = !root_choice in
46b2f6
+  let static_ips = !static_ips in
46b2f6
 
46b2f6
   (* No arguments and machine-readable mode?  Print out some facts
46b2f6
    * about what this binary supports.
46b2f6
@@ -351,6 +373,7 @@ read the man page virt-v2v(1).
46b2f6
     pr "in-place\n";
46b2f6
     pr "io/oo\n";
46b2f6
     pr "mac-option\n";
46b2f6
+    pr "mac-ip-option\n";
46b2f6
     List.iter (pr "input:%s\n") (Modules_list.input_modules ());
46b2f6
     List.iter (pr "output:%s\n") (Modules_list.output_modules ());
46b2f6
     List.iter (pr "convert:%s\n") (Modules_list.convert_modules ());
46b2f6
@@ -685,7 +708,7 @@ read the man page virt-v2v(1).
46b2f6
   {
46b2f6
     compressed; debug_overlays; do_copy; in_place; network_map;
46b2f6
     output_alloc; output_format; output_name;
46b2f6
-    print_estimate; print_source; root_choice;
46b2f6
+    print_estimate; print_source; root_choice; static_ips;
46b2f6
     ks = opthandle.ks;
46b2f6
   },
46b2f6
   input, output
46b2f6
diff --git a/v2v/cmdline.mli b/v2v/cmdline.mli
46b2f6
index 78601e191..a009e9888 100644
46b2f6
--- a/v2v/cmdline.mli
46b2f6
+++ b/v2v/cmdline.mli
46b2f6
@@ -30,6 +30,7 @@ type cmdline = {
46b2f6
   print_estimate : bool;
46b2f6
   print_source : bool;
46b2f6
   root_choice : Types.root_choice;
46b2f6
+  static_ips : Types.static_ip list;
46b2f6
   ks : Tools_utils.key_store;
46b2f6
 }
46b2f6
 
46b2f6
diff --git a/v2v/convert_linux.ml b/v2v/convert_linux.ml
46b2f6
index f9e811c8d..1ada36115 100644
46b2f6
--- a/v2v/convert_linux.ml
46b2f6
+++ b/v2v/convert_linux.ml
46b2f6
@@ -34,7 +34,7 @@ open Linux_kernels
46b2f6
 module G = Guestfs
46b2f6
 
46b2f6
 (* The conversion function. *)
46b2f6
-let convert (g : G.guestfs) inspect source output rcaps =
46b2f6
+let convert (g : G.guestfs) inspect source output rcaps _ =
46b2f6
   (*----------------------------------------------------------------------*)
46b2f6
   (* Inspect the guest first.  We already did some basic inspection in
46b2f6
    * the common v2v.ml code, but that has to deal with generic guests
46b2f6
diff --git a/v2v/convert_windows.ml b/v2v/convert_windows.ml
46b2f6
index 1db3c0ea6..75e609d61 100644
46b2f6
--- a/v2v/convert_windows.ml
46b2f6
+++ b/v2v/convert_windows.ml
46b2f6
@@ -38,7 +38,7 @@ module G = Guestfs
46b2f6
  * time the Windows VM is booted on KVM.
46b2f6
  *)
46b2f6
 
46b2f6
-let convert (g : G.guestfs) inspect source output rcaps =
46b2f6
+let convert (g : G.guestfs) inspect source output rcaps static_ips =
46b2f6
   (*----------------------------------------------------------------------*)
46b2f6
   (* Inspect the Windows guest. *)
46b2f6
 
46b2f6
@@ -228,6 +228,8 @@ let convert (g : G.guestfs) inspect source output rcaps =
46b2f6
     Registry.with_hive_write g inspect.i_windows_software_hive
46b2f6
                              update_software_hive;
46b2f6
 
46b2f6
+    configure_network_interfaces net_driver;
46b2f6
+
46b2f6
     fix_ntfs_heads ();
46b2f6
 
46b2f6
     fix_win_esp ();
46b2f6
@@ -603,6 +605,78 @@ if errorlevel 3010 exit /b 0
46b2f6
     | None ->
46b2f6
        warning (f_"could not find registry key HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion")
46b2f6
 
46b2f6
+  and configure_network_interfaces net_driver =
46b2f6
+    (* If we were asked to force network interfaces to have particular
46b2f6
+     * static IP addresses then it is done here by installing a
46b2f6
+     * Powershell script which runs at boot.
46b2f6
+     *)
46b2f6
+    if static_ips <> [] then (
46b2f6
+      let psh_filename = "v2vnetcf.ps1" in
46b2f6
+      let psh = ref [] in
46b2f6
+      let add = List.push_back psh in
46b2f6
+
46b2f6
+      add "# Uncomment this line for lots of debug output.";
46b2f6
+      add "# Set-PSDebug -Trace 1";
46b2f6
+      add "";
46b2f6
+
46b2f6
+      (* If virtio-net was added to the registry, we must wait for
46b2f6
+       * it to be installed at runtime.
46b2f6
+       *)
46b2f6
+      if net_driver = Virtio_net then (
46b2f6
+        add "# Wait for the netkvm (virtio-net) driver to become active.";
46b2f6
+        add "$adapters = @()";
46b2f6
+        add "While (-Not $adapters) {";
46b2f6
+        add "    Start-Sleep -Seconds 5";
46b2f6
+        add "    $adapters = Get-NetAdapter -Physical | Where DriverFileName -eq \"netkvm.sys\"";
46b2f6
+        add "    Write-Host \"adapters = '$adapters'\"";
46b2f6
+        add "}";
46b2f6
+        add ""
46b2f6
+      );
46b2f6
+
46b2f6
+      List.iter (
46b2f6
+        fun { if_mac_addr; if_ip_address; if_default_gateway;
46b2f6
+              if_prefix_length; if_nameservers } ->
46b2f6
+          add (sprintf "$mac_address = '%s'"
46b2f6
+                       (String.replace if_mac_addr ":" "-"));
46b2f6
+          add "$ifindex = (Get-NetAdapter -Physical | Where MacAddress -eq $mac_address).ifIndex";
46b2f6
+          add "if ($ifindex) {";
46b2f6
+
46b2f6
+          add "    Write-Host \"setting IP address of adapter at $ifindex\"";
46b2f6
+
46b2f6
+          (* New-NetIPAddress command *)
46b2f6
+          let args = ref [] in
46b2f6
+          List.push_back args "-InterfaceIndex";
46b2f6
+          List.push_back args "$ifindex";
46b2f6
+          List.push_back args "-IPAddress";
46b2f6
+          List.push_back args (sprintf "'%s'" if_ip_address);
46b2f6
+          (match if_default_gateway with
46b2f6
+           | None -> ()
46b2f6
+           | Some gw ->
46b2f6
+              List.push_back args "-DefaultGateway";
46b2f6
+              List.push_back args (sprintf "'%s'" gw)
46b2f6
+          );
46b2f6
+          (match if_prefix_length with
46b2f6
+           | None -> ()
46b2f6
+           | Some len ->
46b2f6
+              List.push_back args "-PrefixLength";
46b2f6
+              List.push_back args (string_of_int len)
46b2f6
+          );
46b2f6
+          let cmd1 = "New-NetIPAddress " ^ String.concat " " !args in
46b2f6
+          add ("    " ^ cmd1);
46b2f6
+
46b2f6
+          (* Set-DnsClientServerAddress command *)
46b2f6
+          if if_nameservers <> [] then (
46b2f6
+            add (sprintf "    Set-DnsClientServerAddress -InterfaceIndex $ifindex -ServerAddresses (%s)"
46b2f6
+                         (String.concat "," (List.map (sprintf "'%s'") if_nameservers)))
46b2f6
+          );
46b2f6
+          add "}";
46b2f6
+          add ""
46b2f6
+      ) static_ips;
46b2f6
+
46b2f6
+      (* Install the Powershell script to run at firstboot. *)
46b2f6
+      Windows.install_firstboot_powershell g inspect psh_filename !psh
46b2f6
+    ) (* static_ips <> [] *)
46b2f6
+
46b2f6
   and fix_ntfs_heads () =
46b2f6
     (* NTFS hardcodes the number of heads on the drive which created
46b2f6
        it in the filesystem header. Modern versions of Windows
46b2f6
diff --git a/v2v/modules_list.ml b/v2v/modules_list.ml
46b2f6
index a0a74aaf2..76b3def5d 100644
46b2f6
--- a/v2v/modules_list.ml
46b2f6
+++ b/v2v/modules_list.ml
46b2f6
@@ -38,7 +38,7 @@ type inspection_fn = Types.inspect -> bool
46b2f6
 
46b2f6
 type conversion_fn =
46b2f6
   Guestfs.guestfs -> Types.inspect -> Types.source -> Types.output_settings ->
46b2f6
-  Types.requested_guestcaps -> Types.guestcaps
46b2f6
+  Types.requested_guestcaps -> Types.static_ip list -> Types.guestcaps
46b2f6
 
46b2f6
 let convert_modules = ref []
46b2f6
 
46b2f6
diff --git a/v2v/modules_list.mli b/v2v/modules_list.mli
46b2f6
index 3e80d3e23..ad2024755 100644
46b2f6
--- a/v2v/modules_list.mli
46b2f6
+++ b/v2v/modules_list.mli
46b2f6
@@ -34,7 +34,7 @@ type inspection_fn = Types.inspect -> bool
46b2f6
 
46b2f6
 type conversion_fn =
46b2f6
   Guestfs.guestfs -> Types.inspect -> Types.source -> Types.output_settings ->
46b2f6
-  Types.requested_guestcaps -> Types.guestcaps
46b2f6
+  Types.requested_guestcaps -> Types.static_ip list -> Types.guestcaps
46b2f6
 
46b2f6
 val register_convert_module : inspection_fn -> string -> conversion_fn -> unit
46b2f6
 (** [register_convert_module inspect_fn name fn] registers a
46b2f6
diff --git a/v2v/types.ml b/v2v/types.ml
46b2f6
index 714b30014..4ba6117fd 100644
46b2f6
--- a/v2v/types.ml
46b2f6
+++ b/v2v/types.ml
46b2f6
@@ -506,6 +506,14 @@ type root_choice = AskRoot | SingleRoot | FirstRoot | RootDev of string
46b2f6
 
46b2f6
 type output_allocation = Sparse | Preallocated
46b2f6
 
46b2f6
+type static_ip = {
46b2f6
+  if_mac_addr : string;
46b2f6
+  if_ip_address : string;
46b2f6
+  if_default_gateway : string option;
46b2f6
+  if_prefix_length : int option;
46b2f6
+  if_nameservers : string list;
46b2f6
+}
46b2f6
+
46b2f6
 class virtual input = object
46b2f6
   method precheck () = ()
46b2f6
   method virtual as_options : string
46b2f6
diff --git a/v2v/types.mli b/v2v/types.mli
46b2f6
index f595ab0ef..528d77965 100644
46b2f6
--- a/v2v/types.mli
46b2f6
+++ b/v2v/types.mli
46b2f6
@@ -361,6 +361,15 @@ type root_choice = AskRoot | SingleRoot | FirstRoot | RootDev of string
46b2f6
 type output_allocation = Sparse | Preallocated
46b2f6
 (** Type of [-oa] (output allocation) option. *)
46b2f6
 
46b2f6
+type static_ip = {
46b2f6
+  if_mac_addr : string;
46b2f6
+  if_ip_address : string;
46b2f6
+  if_default_gateway : string option;
46b2f6
+  if_prefix_length : int option;
46b2f6
+  if_nameservers : string list;
46b2f6
+}
46b2f6
+(** [--mac ..:ip:..] option. *)
46b2f6
+
46b2f6
 (** {2 Input object}
46b2f6
 
46b2f6
     This is subclassed for the various input [-i] options.
46b2f6
diff --git a/v2v/v2v.ml b/v2v/v2v.ml
46b2f6
index 63e809030..d7a868659 100644
46b2f6
--- a/v2v/v2v.ml
46b2f6
+++ b/v2v/v2v.ml
46b2f6
@@ -133,7 +133,7 @@ let rec main () =
46b2f6
       | In_place ->
46b2f6
          rcaps_from_source source in
46b2f6
 
46b2f6
-    do_convert g inspect source output rcaps in
46b2f6
+    do_convert g inspect source output rcaps cmdline.static_ips in
46b2f6
 
46b2f6
   g#umount_all ();
46b2f6
 
46b2f6
@@ -556,7 +556,7 @@ and estimate_target_size mpstats overlays =
46b2f6
   )
46b2f6
 
46b2f6
 (* Conversion. *)
46b2f6
-and do_convert g inspect source output rcaps =
46b2f6
+and do_convert g inspect source output rcaps interfaces =
46b2f6
   (match inspect.i_product_name with
46b2f6
   | "unknown" ->
46b2f6
     message (f_"Converting the guest to run on KVM")
46b2f6
@@ -572,7 +572,8 @@ and do_convert g inspect source output rcaps =
46b2f6
   debug "picked conversion module %s" conversion_name;
46b2f6
   debug "requested caps: %s" (string_of_requested_guestcaps rcaps);
46b2f6
   let guestcaps =
46b2f6
-    convert g inspect source (output :> Types.output_settings) rcaps in
46b2f6
+    convert g inspect source (output :> Types.output_settings) rcaps
46b2f6
+            interfaces in
46b2f6
   debug "%s" (string_of_guestcaps guestcaps);
46b2f6
 
46b2f6
   (* Did we manage to install virtio drivers? *)
46b2f6
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
46b2f6
index 9a555c3be..0642d158f 100644
46b2f6
--- a/v2v/virt-v2v.pod
46b2f6
+++ b/v2v/virt-v2v.pod
46b2f6
@@ -368,6 +368,24 @@ Map source NIC MAC address to a network or bridge.
46b2f6
 
46b2f6
 See L</Networks and bridges> below.
46b2f6
 
46b2f6
+=item B<--mac> aa:bb:cc:dd:ee:ffB<:ip:>ipaddr[,gw[,len[,ns,ns,...]]]
46b2f6
+
46b2f6
+Force a particular interface (controlled by its MAC address) to have a
46b2f6
+static IP address after boot.
46b2f6
+
46b2f6
+The fields in the parameter are: C<ipaddr> is the IP address.  C<gw>
46b2f6
+is the optional gateway IP address.  C<len> is the subnet mask length
46b2f6
+(an integer).  The final parameters are zero or more nameserver IP
46b2f6
+addresses.
46b2f6
+
46b2f6
+This option can be supplied zero or more times.
46b2f6
+
46b2f6
+You only need to use this option for certain broken guests such as
46b2f6
+Windows which are unable to preserve MAC to static IP address mappings
46b2f6
+automatically.  You don't need to use it if Windows is using DHCP.  It
46b2f6
+is currently ignored for Linux guests since they do not have this
46b2f6
+problem.
46b2f6
+
46b2f6
 =item B<--machine-readable>
46b2f6
 
46b2f6
 =item B<--machine-readable>=format
46b2f6
-- 
b155d0
2.26.2
46b2f6