Skip to content

Commit 9f2592c

Browse files
committed
Add VirtualBox 7.1.x Support
Fixes #12953 Signed-off-by: Wong Hoi Sing Edison <[email protected]>
1 parent ae6a175 commit 9f2592c

File tree

5 files changed

+1164
-1
lines changed

5 files changed

+1164
-1
lines changed

plugins/providers/virtualbox/driver/meta.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def initialize(uuid=nil)
6969
"6.0" => Version_6_0,
7070
"6.1" => Version_6_1,
7171
"7.0" => Version_7_0,
72+
"7.1" => Version_7_1,
7273
}
7374

7475
if @@version.start_with?("4.2.14")
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: BUSL-1.1
3+
4+
require "rexml"
5+
require File.expand_path("../version_7_0", __FILE__)
6+
7+
module VagrantPlugins
8+
module ProviderVirtualBox
9+
module Driver
10+
# Driver for VirtualBox 7.1.x
11+
class Version_7_1 < Version_7_0
12+
# VirtualBox version requirement for using host only networks
13+
# instead of host only interfaces
14+
HOSTONLY_NET_REQUIREMENT=Gem::Requirement.new(">= 7")
15+
# Prefix of name used for host only networks
16+
HOSTONLY_NAME_PREFIX="vagrantnet-vbox"
17+
DEFAULT_NETMASK="255.255.255.0"
18+
19+
def initialize(uuid)
20+
super
21+
22+
@logger = Log4r::Logger.new("vagrant::provider::virtualbox_7_1")
23+
end
24+
25+
def read_bridged_interfaces
26+
ifaces = super
27+
return ifaces if !use_host_only_nets?
28+
29+
# Get a list of all subnets which are in use for hostonly networks
30+
hostonly_ifaces = read_host_only_networks.map do |net|
31+
IPAddr.new(net[:lowerip]).mask(net[:networkmask])
32+
end
33+
34+
# Prune any hostonly interfaces in the list
35+
ifaces.delete_if { |i|
36+
addr = begin
37+
IPAddr.new(i[:ip]).mask(i[:netmask])
38+
rescue IPAddr::Error => err
39+
@logger.warn("skipping bridged interface due to parse error #{err} (#{i}) ")
40+
nil
41+
end
42+
addr.nil? ||
43+
hostonly_ifaces.include?(addr)
44+
}
45+
46+
ifaces
47+
end
48+
49+
def delete_unused_host_only_networks
50+
return super if !use_host_only_nets?
51+
52+
# First get the list of existing host only network names
53+
network_names = read_host_only_networks.map { |net| net[:name] }
54+
# Prune the network names to only include ones we manage
55+
network_names.delete_if { |name| !name.start_with?(HOSTONLY_NAME_PREFIX) }
56+
57+
@logger.debug("managed host only network names: #{network_names}")
58+
59+
return if network_names.empty?
60+
61+
# Next get the list of host only networks currently in use
62+
inuse_names = []
63+
execute("list", "vms", retryable: true).split("\n").each do |line|
64+
match = line.match(/^".+?"\s+\{(?<vmid>.+?)\}$/)
65+
next if match.nil?
66+
begin
67+
info = execute("showvminfo", match[:vmid].to_s, "--machinereadable", retryable: true)
68+
info.split("\n").each do |vmline|
69+
if vmline.start_with?("hostonly-network")
70+
net_name = vmline.split("=", 2).last.to_s.gsub('"', "")
71+
inuse_names << net_name
72+
end
73+
end
74+
rescue Vagrant::Errors::VBoxManageError => err
75+
raise if !err.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
76+
end
77+
end
78+
79+
@logger.debug("currently in use network names: #{inuse_names}")
80+
81+
# Now remove all the networks not in use
82+
(network_names - inuse_names).each do |name|
83+
execute("hostonlynet", "remove", "--name", name, retryable: true)
84+
end
85+
end
86+
87+
def enable_adapters(adapters)
88+
return super if !use_host_only_nets?
89+
90+
hostonly_adapters = adapters.find_all { |adapter| adapter[:hostonly] }
91+
other_adapters = adapters - hostonly_adapters
92+
super(other_adapters) if !other_adapters.empty?
93+
94+
if !hostonly_adapters.empty?
95+
args = []
96+
hostonly_adapters.each do |adapter|
97+
args.concat(["--nic#{adapter[:adapter]}", "hostonlynet"])
98+
args.concat(["--host-only-net#{adapter[:adapter]}", adapter[:hostonly],
99+
"--cableconnected#{adapter[:adapter]}", "on"])
100+
end
101+
102+
execute("modifyvm", @uuid, *args, retryable: true)
103+
end
104+
end
105+
106+
def create_host_only_network(options)
107+
# If we are not on macOS, just setup the hostonly interface
108+
return super if !use_host_only_nets?
109+
110+
opts = {
111+
netmask: options.fetch(:netmask, DEFAULT_NETMASK),
112+
}
113+
114+
if options[:type] == :dhcp
115+
opts[:lower] = options[:dhcp_lower]
116+
opts[:upper] = options[:dhcp_upper]
117+
else
118+
addr = IPAddr.new(options[:adapter_ip])
119+
opts[:upper] = opts[:lower] = addr.mask(opts[:netmask]).to_range.first.to_s
120+
end
121+
122+
name_idx = read_host_only_networks.map { |hn|
123+
next if !hn[:name].start_with?(HOSTONLY_NAME_PREFIX)
124+
hn[:name].sub(HOSTONLY_NAME_PREFIX, "").to_i
125+
}.compact.max.to_i + 1
126+
opts[:name] = HOSTONLY_NAME_PREFIX + name_idx.to_s
127+
128+
execute("hostonlynet", "add",
129+
"--name", opts[:name],
130+
"--netmask", opts[:netmask],
131+
"--lower-ip", opts[:lower],
132+
"--upper-ip", opts[:upper],
133+
retryable: true)
134+
135+
{
136+
name: opts[:name],
137+
ip: options[:adapter_ip],
138+
netmask: opts[:netmask],
139+
}
140+
end
141+
142+
# Disabled when host only nets are in use
143+
def reconfig_host_only(options)
144+
return super if !use_host_only_nets?
145+
end
146+
147+
# Disabled when host only nets are in use since
148+
# the host only nets will provide the dhcp server
149+
def remove_dhcp_server(*_, **_)
150+
super if !use_host_only_nets?
151+
end
152+
153+
# Disabled when host only nets are in use since
154+
# the host only nets will provide the dhcp server
155+
def create_dhcp_server(*_, **_)
156+
super if !use_host_only_nets?
157+
end
158+
159+
def read_host_only_interfaces
160+
return super if !use_host_only_nets?
161+
162+
# When host only nets are in use, read them and
163+
# reformat the information to line up with how
164+
# the interfaces is structured
165+
read_host_only_networks.map do |net|
166+
addr = begin
167+
IPAddr.new(net[:lowerip])
168+
rescue IPAddr::Error => err
169+
@logger.warn("invalid host only network lower IP encountered: #{err} (#{net})")
170+
next
171+
end
172+
# Address of the interface will be the lower bound of the range or
173+
# the first available address in the subnet
174+
if addr == addr.mask(net[:networkmask])
175+
addr = addr.succ
176+
end
177+
178+
net[:netmask] = net[:networkmask]
179+
if addr.ipv4?
180+
net[:ip] = addr.to_s
181+
net[:ipv6] = ""
182+
else
183+
net[:ip] = ""
184+
net[:ipv6] = addr.to_s
185+
net[:ipv6_prefix] = net[:netmask]
186+
end
187+
188+
net[:status] = net[:state] == "Enabled" ? "Up" : "Down"
189+
190+
net
191+
end.compact
192+
end
193+
194+
def read_network_interfaces
195+
return super if !use_host_only_nets?
196+
197+
{}.tap do |nics|
198+
execute("showvminfo", @uuid, "--machinereadable", retryable: true).each_line do |line|
199+
if m = line.match(/nic(?<adapter>\d+)="(?<type>.+?)"$/)
200+
nics[m[:adapter].to_i] ||= {}
201+
if m[:type] == "hostonlynetwork"
202+
nics[m[:adapter].to_i][:type] = :hostonly
203+
else
204+
nics[m[:adapter].to_i][:type] = m[:type].to_sym
205+
end
206+
elsif m = line.match(/^bridgeadapter(?<adapter>\d+)="(?<network>.+?)"$/)
207+
nics[m[:adapter].to_i] ||= {}
208+
nics[m[:adapter].to_i][:bridge] = m[:network]
209+
elsif m = line.match(/^hostonly-network(?<adapter>\d+)="(?<network>.+?)"$/)
210+
nics[m[:adapter].to_i] ||= {}
211+
nics[m[:adapter].to_i][:hostonly] = m[:network]
212+
end
213+
end
214+
end
215+
end
216+
217+
# Generate list of host only networks
218+
def read_host_only_networks
219+
networks = []
220+
current = nil
221+
execute("list", "hostonlynets", retryable: true).split("\n").each do |line|
222+
line.chomp!
223+
next if line.empty?
224+
key, value = line.split(":", 2).map(&:strip)
225+
key = key.downcase
226+
if key == "name"
227+
networks.push(current) if !current.nil?
228+
current = Vagrant::Util::HashWithIndifferentAccess.new
229+
end
230+
current[key] = value
231+
end
232+
networks.push(current) if !current.nil?
233+
234+
networks
235+
end
236+
237+
# The initial VirtualBox 7.1 release has an issue with displaying port
238+
# forward information. When a single port forward is defined, the forwarding
239+
# information can be found in the `showvminfo` output. Once more than a
240+
# single port forward is defined, no forwarding information is provided
241+
# in the `showvminfo` output. To work around this we grab the VM configuration
242+
# file from the `showvminfo` output and extract the port forward information
243+
# from there instead.
244+
def read_forwarded_ports(uuid=nil, active_only=false)
245+
# Only use this override for the 7.1.0 release.
246+
return super if get_version.to_s != "7.1.0"
247+
248+
uuid ||= @uuid
249+
250+
@logger.debug("read_forward_ports: uuid=#{uuid} active_only=#{active_only}")
251+
252+
results = []
253+
254+
info = execute("showvminfo", uuid, "--machinereadable", retryable: true)
255+
result = info.match(/CfgFile="(?<path>.+?)"/)
256+
if result.nil?
257+
raise Vagrant::Errors::VirtualBoxConfigNotFound,
258+
uuid: uuid
259+
end
260+
261+
File.open(result[:path], "r") do |f|
262+
doc = REXML::Document.new(f)
263+
networks = REXML::XPath.each(doc.root, "Machine/Hardware/Network/Adapter")
264+
networks.each do |net|
265+
REXML::XPath.each(doc.root, net.xpath + "/NAT/Forwarding") do |fwd|
266+
# Result Array values:
267+
# [NIC Slot, Name, Host Port, Guest Port, Host IP]
268+
result = [
269+
net.attribute("slot").value.to_i + 1,
270+
fwd.attribute("name")&.value.to_s,
271+
fwd.attribute("hostport")&.value.to_i,
272+
fwd.attribute("guestport")&.value.to_i,
273+
fwd.attribute("hostip")&.value.to_s
274+
]
275+
@logger.debug(" - #{result.inspect}")
276+
results << result
277+
end
278+
end
279+
end
280+
281+
results
282+
end
283+
284+
private
285+
286+
# Returns if hostonlynets are enabled on the current
287+
# host platform
288+
#
289+
# @return [Boolean]
290+
def use_host_only_nets?
291+
Vagrant::Util::Platform.darwin? &&
292+
HOSTONLY_NET_REQUIREMENT.satisfied_by?(get_version)
293+
end
294+
295+
# VirtualBox version in use
296+
#
297+
# @return [Gem::Version]
298+
def get_version
299+
return @version if @version
300+
@version = Gem::Version.new(Meta.new.version)
301+
end
302+
end
303+
end
304+
end
305+
end

plugins/providers/virtualbox/plugin.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ module Driver
104104
autoload :Version_6_0, File.expand_path("../driver/version_6_0", __FILE__)
105105
autoload :Version_6_1, File.expand_path("../driver/version_6_1", __FILE__)
106106
autoload :Version_7_0, File.expand_path("../driver/version_7_0", __FILE__)
107+
autoload :Version_7_1, File.expand_path("../driver/version_7_1", __FILE__)
107108
end
108109

109110
module Model

0 commit comments

Comments
 (0)