diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml index c6d7af09d..77700393f 100644 --- a/v2v/cmdline.ml +++ b/v2v/cmdline.ml @@ -41,11 +41,12 @@ type cmdline = { print_estimate : bool; print_source : bool; root_choice : root_choice; + static_ips : static_ip list; ks : Tools_utils.key_store; } (* Matches --mac command line parameters. *) -let mac_re = PCRE.compile ~anchored:true "([[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}):(network|bridge):(.*)" +let mac_re = PCRE.compile ~anchored:true "([[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}):(network|bridge|ip):(.*)" let parse_cmdline () = let bandwidth = ref None in @@ -100,6 +101,7 @@ let parse_cmdline () = in let network_map = Networks.create () in + let static_ips = ref [] in let add_network str = match String.split ":" str with | "", "" -> @@ -122,11 +124,30 @@ let parse_cmdline () = if not (PCRE.matches mac_re str) then error (f_"cannot parse --mac \"%s\" parameter") str; let mac = PCRE.sub 1 and out = PCRE.sub 3 in - let vnet_type = - match PCRE.sub 2 with - | "network" -> Network | "bridge" -> Bridge - | _ -> assert false in - Networks.add_mac network_map mac vnet_type out + match PCRE.sub 2 with + | "network" -> + Networks.add_mac network_map mac Network out + | "bridge" -> + Networks.add_mac network_map mac Bridge out + | "ip" -> + let add if_mac_addr if_ip_address if_default_gateway + if_prefix_length if_nameservers = + List.push_back static_ips + { if_mac_addr; if_ip_address; if_default_gateway; + if_prefix_length; if_nameservers } + in + (match String.nsplit "," out with + | [] -> + error (f_"invalid --mac ip option") + | [ip] -> add mac ip None None [] + | [ip; gw] -> add mac ip (Some gw) None [] + | ip :: gw :: len :: nameservers -> + let len = + try int_of_string len with + | Failure _ -> error (f_"cannot parse --mac ip prefix length field as an integer: %s") len in + add mac ip (Some gw) (Some len) nameservers + ); + | _ -> assert false in let no_trim_warning _ = @@ -218,8 +239,8 @@ let parse_cmdline () = s_"Input transport"; [ L"in-place" ], Getopt.Set in_place, s_"Only tune the guest in the input VM"; - [ L"mac" ], Getopt.String ("mac:network|bridge:out", add_mac), - s_"Map NIC to network or bridge"; + [ L"mac" ], Getopt.String ("mac:network|bridge|ip:out", add_mac), + s_"Map NIC to network or bridge or assign static IP"; [ S 'n'; L"network" ], Getopt.String ("in:out", add_network), s_"Map network ‘in’ to ‘out’"; [ L"no-copy" ], Getopt.Clear do_copy, @@ -347,6 +368,7 @@ read the man page virt-v2v(1). let print_source = !print_source in let qemu_boot = !qemu_boot in let root_choice = !root_choice in + let static_ips = !static_ips in (* No arguments and machine-readable mode? Print out some facts * about what this binary supports. @@ -364,6 +386,7 @@ read the man page virt-v2v(1). pr "io/oo\n"; pr "mac-option\n"; pr "bandwidth-option\n"; + pr "mac-ip-option\n"; List.iter (pr "input:%s\n") (Modules_list.input_modules ()); List.iter (pr "output:%s\n") (Modules_list.output_modules ()); List.iter (pr "convert:%s\n") (Modules_list.convert_modules ()); @@ -698,7 +721,7 @@ read the man page virt-v2v(1). { bandwidth; compressed; debug_overlays; do_copy; in_place; network_map; output_alloc; output_format; output_name; - print_estimate; print_source; root_choice; + print_estimate; print_source; root_choice; static_ips; ks = opthandle.ks; }, input, output diff --git a/v2v/cmdline.mli b/v2v/cmdline.mli index 1c9e6c258..e7a688359 100644 --- a/v2v/cmdline.mli +++ b/v2v/cmdline.mli @@ -31,6 +31,7 @@ type cmdline = { print_estimate : bool; print_source : bool; root_choice : Types.root_choice; + static_ips : Types.static_ip list; ks : Tools_utils.key_store; } diff --git a/v2v/convert_linux.ml b/v2v/convert_linux.ml index 484e387cc..739c407f7 100644 --- a/v2v/convert_linux.ml +++ b/v2v/convert_linux.ml @@ -34,7 +34,7 @@ open Linux_kernels module G = Guestfs (* The conversion function. *) -let convert (g : G.guestfs) inspect source output rcaps = +let convert (g : G.guestfs) inspect source output rcaps _ = (*----------------------------------------------------------------------*) (* Inspect the guest first. We already did some basic inspection in * the common v2v.ml code, but that has to deal with generic guests diff --git a/v2v/convert_windows.ml b/v2v/convert_windows.ml index 7ea56592c..43683ac09 100644 --- a/v2v/convert_windows.ml +++ b/v2v/convert_windows.ml @@ -38,7 +38,7 @@ module G = Guestfs * time the Windows VM is booted on KVM. *) -let convert (g : G.guestfs) inspect source output rcaps = +let convert (g : G.guestfs) inspect source output rcaps static_ips = (*----------------------------------------------------------------------*) (* Inspect the Windows guest. *) @@ -228,6 +228,8 @@ let convert (g : G.guestfs) inspect source output rcaps = Registry.with_hive_write g inspect.i_windows_software_hive update_software_hive; + configure_network_interfaces net_driver; + fix_ntfs_heads (); fix_win_esp (); @@ -603,6 +605,78 @@ if errorlevel 3010 exit /b 0 | None -> warning (f_"could not find registry key HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion") + and configure_network_interfaces net_driver = + (* If we were asked to force network interfaces to have particular + * static IP addresses then it is done here by installing a + * Powershell script which runs at boot. + *) + if static_ips <> [] then ( + let psh_filename = "v2vnetcf.ps1" in + let psh = ref [] in + let add = List.push_back psh in + + add "# Uncomment this line for lots of debug output."; + add "# Set-PSDebug -Trace 1"; + add ""; + + (* If virtio-net was added to the registry, we must wait for + * it to be installed at runtime. + *) + if net_driver = Virtio_net then ( + add "# Wait for the netkvm (virtio-net) driver to become active."; + add "$adapters = @()"; + add "While (-Not $adapters) {"; + add " Start-Sleep -Seconds 5"; + add " $adapters = Get-NetAdapter -Physical | Where DriverFileName -eq \"netkvm.sys\""; + add " Write-Host \"adapters = '$adapters'\""; + add "}"; + add "" + ); + + List.iter ( + fun { if_mac_addr; if_ip_address; if_default_gateway; + if_prefix_length; if_nameservers } -> + add (sprintf "$mac_address = '%s'" + (String.replace if_mac_addr ":" "-")); + add "$ifindex = (Get-NetAdapter -Physical | Where MacAddress -eq $mac_address).ifIndex"; + add "if ($ifindex) {"; + + add " Write-Host \"setting IP address of adapter at $ifindex\""; + + (* New-NetIPAddress command *) + let args = ref [] in + List.push_back args "-InterfaceIndex"; + List.push_back args "$ifindex"; + List.push_back args "-IPAddress"; + List.push_back args (sprintf "'%s'" if_ip_address); + (match if_default_gateway with + | None -> () + | Some gw -> + List.push_back args "-DefaultGateway"; + List.push_back args (sprintf "'%s'" gw) + ); + (match if_prefix_length with + | None -> () + | Some len -> + List.push_back args "-PrefixLength"; + List.push_back args (string_of_int len) + ); + let cmd1 = "New-NetIPAddress " ^ String.concat " " !args in + add (" " ^ cmd1); + + (* Set-DnsClientServerAddress command *) + if if_nameservers <> [] then ( + add (sprintf " Set-DnsClientServerAddress -InterfaceIndex $ifindex -ServerAddresses (%s)" + (String.concat "," (List.map (sprintf "'%s'") if_nameservers))) + ); + add "}"; + add "" + ) static_ips; + + (* Install the Powershell script to run at firstboot. *) + Windows.install_firstboot_powershell g inspect psh_filename !psh + ) (* static_ips <> [] *) + and fix_ntfs_heads () = (* NTFS hardcodes the number of heads on the drive which created it in the filesystem header. Modern versions of Windows diff --git a/v2v/modules_list.ml b/v2v/modules_list.ml index a0a74aaf2..76b3def5d 100644 --- a/v2v/modules_list.ml +++ b/v2v/modules_list.ml @@ -38,7 +38,7 @@ type inspection_fn = Types.inspect -> bool type conversion_fn = Guestfs.guestfs -> Types.inspect -> Types.source -> Types.output_settings -> - Types.requested_guestcaps -> Types.guestcaps + Types.requested_guestcaps -> Types.static_ip list -> Types.guestcaps let convert_modules = ref [] diff --git a/v2v/modules_list.mli b/v2v/modules_list.mli index 3e80d3e23..ad2024755 100644 --- a/v2v/modules_list.mli +++ b/v2v/modules_list.mli @@ -34,7 +34,7 @@ type inspection_fn = Types.inspect -> bool type conversion_fn = Guestfs.guestfs -> Types.inspect -> Types.source -> Types.output_settings -> - Types.requested_guestcaps -> Types.guestcaps + Types.requested_guestcaps -> Types.static_ip list -> Types.guestcaps val register_convert_module : inspection_fn -> string -> conversion_fn -> unit (** [register_convert_module inspect_fn name fn] registers a diff --git a/v2v/types.ml b/v2v/types.ml index d406caeb9..3c2d060b5 100644 --- a/v2v/types.ml +++ b/v2v/types.ml @@ -510,6 +510,14 @@ type bandwidth = | StaticBandwidth of string | DynamicBandwidth of string option * string +type static_ip = { + if_mac_addr : string; + if_ip_address : string; + if_default_gateway : string option; + if_prefix_length : int option; + if_nameservers : string list; +} + class virtual input = object method precheck () = () method virtual as_options : string diff --git a/v2v/types.mli b/v2v/types.mli index 556f6930f..6a6d81a12 100644 --- a/v2v/types.mli +++ b/v2v/types.mli @@ -366,6 +366,15 @@ type bandwidth = | DynamicBandwidth of string option * string (** [--bandwidth] and [--bandwidth-file] options. *) +type static_ip = { + if_mac_addr : string; + if_ip_address : string; + if_default_gateway : string option; + if_prefix_length : int option; + if_nameservers : string list; +} +(** [--mac ..:ip:..] option. *) + (** {2 Input object} This is subclassed for the various input [-i] options. diff --git a/v2v/v2v.ml b/v2v/v2v.ml index e4b4dfe37..26896cdae 100644 --- a/v2v/v2v.ml +++ b/v2v/v2v.ml @@ -127,7 +127,7 @@ let rec main () = | In_place -> rcaps_from_source source in - do_convert g inspect source output rcaps in + do_convert g inspect source output rcaps cmdline.static_ips in g#umount_all (); @@ -551,7 +551,7 @@ and estimate_target_size mpstats overlays = ) (* Conversion. *) -and do_convert g inspect source output rcaps = +and do_convert g inspect source output rcaps interfaces = (match inspect.i_product_name with | "unknown" -> message (f_"Converting the guest to run on KVM") @@ -567,7 +567,8 @@ and do_convert g inspect source output rcaps = debug "picked conversion module %s" conversion_name; debug "requested caps: %s" (string_of_requested_guestcaps rcaps); let guestcaps = - convert g inspect source (output :> Types.output_settings) rcaps in + convert g inspect source (output :> Types.output_settings) rcaps + interfaces in debug "%s" (string_of_guestcaps guestcaps); (* Did we manage to install virtio drivers? *) diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod index 8ba141be9..6d677a456 100644 --- a/v2v/virt-v2v.pod +++ b/v2v/virt-v2v.pod @@ -412,6 +412,24 @@ Map source NIC MAC address to a network or bridge. See L below. +=item B<--mac> aa:bb:cc:dd:ee:ffB<:ip:>ipaddr[,gw[,len[,ns,ns,...]]] + +Force a particular interface (controlled by its MAC address) to have a +static IP address after boot. + +The fields in the parameter are: C is the IP address. C +is the optional gateway IP address. C is the subnet mask length +(an integer). The final parameters are zero or more nameserver IP +addresses. + +This option can be supplied zero or more times. + +You only need to use this option for certain broken guests such as +Windows which are unable to preserve MAC to static IP address mappings +automatically. You don't need to use it if Windows is using DHCP. It +is currently ignored for Linux guests since they do not have this +problem. + =item B<--machine-readable> =item B<--machine-readable>=format