Skip to content

Commit 7f27dd1

Browse files
authored
[az] Implement Linux networking (#4071)
Adds subnets to AZs and assigns them to VMs made in that zone. MULTI-1942
2 parents 6c91f0b + bf74556 commit 7f27dd1

File tree

11 files changed

+358
-172
lines changed

11 files changed

+358
-172
lines changed

src/platform/backends/qemu/linux/dnsmasq_process_spec.cpp

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,33 @@
2424
namespace mp = multipass;
2525
namespace mpu = multipass::utils;
2626

27+
namespace
28+
{
29+
[[nodiscard]] QStringList make_dnsmasq_subnet_args(const mp::SubnetList& subnets)
30+
{
31+
QStringList out{};
32+
for (const auto& [bridge_name, subnet] : subnets)
33+
{
34+
const auto bridge_addr = mp::IPAddress{fmt::format("{}.1", subnet)};
35+
const auto start_ip = mp::IPAddress{fmt::format("{}.2", subnet)};
36+
const auto end_ip = mp::IPAddress{fmt::format("{}.254", subnet)};
37+
38+
out << QString("--interface=%1").arg(bridge_name)
39+
<< QString("--listen-address=%1").arg(QString::fromStdString(bridge_addr.as_string()))
40+
<< "--dhcp-range"
41+
<< QString("%1,%2,infinite")
42+
.arg(QString::fromStdString(start_ip.as_string()))
43+
.arg(QString::fromStdString(end_ip.as_string()));
44+
}
45+
46+
return out;
47+
}
48+
} // namespace
49+
2750
mp::DNSMasqProcessSpec::DNSMasqProcessSpec(const mp::Path& data_dir,
28-
const QString& bridge_name,
29-
const std::string& subnet,
51+
const SubnetList& subnets,
3052
const QString& conf_file_path)
31-
: data_dir(data_dir), bridge_name(bridge_name), subnet{subnet}, conf_file_path{conf_file_path}
53+
: data_dir(data_dir), subnets(subnets), conf_file_path{conf_file_path}
3254
{
3355
}
3456

@@ -39,26 +61,20 @@ QString mp::DNSMasqProcessSpec::program() const
3961

4062
QStringList mp::DNSMasqProcessSpec::arguments() const
4163
{
42-
const auto bridge_addr = mp::IPAddress{fmt::format("{}.1", subnet)};
43-
const auto start_ip = mp::IPAddress{fmt::format("{}.2", subnet)};
44-
const auto end_ip = mp::IPAddress{fmt::format("{}.254", subnet)};
45-
46-
return QStringList()
47-
<< "--keep-in-foreground"
48-
<< "--strict-order"
49-
<< "--bind-interfaces" << QString("--pid-file") << "--domain=multipass"
50-
<< "--local=/multipass/"
51-
<< "--except-interface=lo" << QString("--interface=%1").arg(bridge_name)
52-
<< QString("--listen-address=%1").arg(QString::fromStdString(bridge_addr.as_string()))
53-
<< "--dhcp-no-override"
54-
<< "--dhcp-ignore-clid"
55-
<< "--dhcp-authoritative" << QString("--dhcp-leasefile=%1/dnsmasq.leases").arg(data_dir)
56-
<< QString("--dhcp-hostsfile=%1/dnsmasq.hosts").arg(data_dir) << "--dhcp-range"
57-
<< QString("%1,%2,infinite")
58-
.arg(QString::fromStdString(start_ip.as_string()))
59-
.arg(QString::fromStdString(end_ip.as_string()))
60-
// This is to prevent it trying to read /etc/dnsmasq.conf
61-
<< QString("--conf-file=%1").arg(conf_file_path);
64+
auto out = QStringList() << "--keep-in-foreground"
65+
<< "--strict-order"
66+
<< "--bind-interfaces" << QString("--pid-file") << "--domain=multipass"
67+
<< "--local=/multipass/"
68+
<< "--except-interface=lo"
69+
<< "--dhcp-no-override"
70+
<< "--dhcp-ignore-clid"
71+
<< "--dhcp-authoritative"
72+
<< QString("--dhcp-leasefile=%1/dnsmasq.leases").arg(data_dir)
73+
<< QString("--dhcp-hostsfile=%1/dnsmasq.hosts").arg(data_dir)
74+
// This is to prevent it trying to read /etc/dnsmasq.conf
75+
<< QString("--conf-file=%1").arg(conf_file_path);
76+
77+
return out << make_dnsmasq_subnet_args(subnets);
6278
}
6379

6480
mp::logging::Level mp::DNSMasqProcessSpec::error_log_level() const

src/platform/backends/qemu/linux/dnsmasq_process_spec.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
#pragma once
1919

20-
#include <multipass/ip_address.h>
20+
#include "dnsmasq_server.h"
21+
2122
#include <multipass/path.h>
2223
#include <multipass/process/process_spec.h>
2324

@@ -30,8 +31,7 @@ class DNSMasqProcessSpec : public ProcessSpec
3031
{
3132
public:
3233
explicit DNSMasqProcessSpec(const Path& data_dir,
33-
const QString& bridge_name,
34-
const std::string& subnet,
34+
const SubnetList& subnets,
3535
const QString& conf_file_path);
3636

3737
QString program() const override;
@@ -42,8 +42,7 @@ class DNSMasqProcessSpec : public ProcessSpec
4242

4343
private:
4444
const Path data_dir;
45-
const QString bridge_name;
46-
const std::string subnet;
45+
const SubnetList subnets;
4746
const QString conf_file_path;
4847
};
4948

src/platform/backends/qemu/linux/dnsmasq_server.cpp

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,16 @@ namespace
3636
constexpr auto immediate_wait = 100; // period to wait for immediate dnsmasq failures, in ms
3737

3838
auto make_dnsmasq_process(const mp::Path& data_dir,
39-
const QString& bridge_name,
40-
const std::string& subnet,
39+
const mp::SubnetList& subnets,
4140
const QString& conf_file_path)
4241
{
43-
auto process_spec =
44-
std::make_unique<mp::DNSMasqProcessSpec>(data_dir, bridge_name, subnet, conf_file_path);
42+
auto process_spec = std::make_unique<mp::DNSMasqProcessSpec>(data_dir, subnets, conf_file_path);
4543
return MP_PROCFACTORY.create_process(std::move(process_spec));
4644
}
4745
} // namespace
4846

49-
mp::DNSMasqServer::DNSMasqServer(const Path& data_dir,
50-
const QString& bridge_name,
51-
const std::string& subnet)
52-
: data_dir{data_dir},
53-
bridge_name{bridge_name},
54-
subnet{subnet},
55-
conf_file{QDir(data_dir).absoluteFilePath("dnsmasq-XXXXXX.conf")}
47+
mp::DNSMasqServer::DNSMasqServer(const Path& data_dir, const SubnetList& subnets)
48+
: data_dir{data_dir}, conf_file{QDir(data_dir).absoluteFilePath("dnsmasq-XXXXXX.conf")}
5649
{
5750
conf_file.open();
5851
conf_file.close();
@@ -63,7 +56,7 @@ mp::DNSMasqServer::DNSMasqServer(const Path& data_dir,
6356
dnsmasq_hosts.open(QIODevice::WriteOnly);
6457
}
6558

66-
dnsmasq_cmd = make_dnsmasq_process(data_dir, bridge_name, subnet, conf_file.fileName());
59+
dnsmasq_cmd = make_dnsmasq_process(data_dir, subnets, conf_file.fileName());
6760
start_dnsmasq();
6861
}
6962

@@ -106,7 +99,7 @@ std::optional<mp::IPAddress> mp::DNSMasqServer::get_ip_for(const std::string& hw
10699
return std::nullopt;
107100
}
108101

109-
void mp::DNSMasqServer::release_mac(const std::string& hw_addr)
102+
void mp::DNSMasqServer::release_mac(const std::string& hw_addr, const QString& bridge_name)
110103
{
111104
auto ip = get_ip_for(hw_addr);
112105
if (!ip)
@@ -207,8 +200,7 @@ void mp::DNSMasqServer::start_dnsmasq()
207200

208201
mp::DNSMasqServer::UPtr mp::DNSMasqServerFactory::make_dnsmasq_server(
209202
const mp::Path& network_dir,
210-
const QString& bridge_name,
211-
const std::string& subnet) const
203+
const SubnetList& subnets) const
212204
{
213-
return std::make_unique<mp::DNSMasqServer>(network_dir, bridge_name, subnet);
205+
return std::make_unique<mp::DNSMasqServer>(network_dir, subnets);
214206
}

src/platform/backends/qemu/linux/dnsmasq_server.h

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,18 @@ namespace multipass
3232
{
3333
class Process;
3434

35+
using SubnetList = std::vector<std::pair<QString, std::string>>;
36+
3537
class DNSMasqServer : private DisabledCopyMove
3638
{
3739
public:
3840
using UPtr = std::unique_ptr<DNSMasqServer>;
3941

40-
DNSMasqServer(const Path& data_dir, const QString& bridge_name, const std::string& subnet);
42+
DNSMasqServer(const Path& data_dir, const SubnetList& subnets);
4143
virtual ~DNSMasqServer(); // inherited by mock for testing
4244

4345
virtual std::optional<IPAddress> get_ip_for(const std::string& hw_addr);
44-
virtual void release_mac(const std::string& hw_addr);
46+
virtual void release_mac(const std::string& hw_addr, const QString& bridge_name);
4547
virtual void check_dnsmasq_running();
4648

4749
protected:
@@ -51,8 +53,6 @@ class DNSMasqServer : private DisabledCopyMove
5153
void start_dnsmasq();
5254

5355
const QString data_dir;
54-
const QString bridge_name;
55-
const std::string subnet;
5656
std::unique_ptr<Process> dnsmasq_cmd;
5757
QMetaObject::Connection finish_connection;
5858
QTemporaryFile conf_file;
@@ -67,7 +67,6 @@ class DNSMasqServerFactory : public Singleton<DNSMasqServerFactory>
6767
: Singleton<DNSMasqServerFactory>::Singleton{pass} {};
6868

6969
virtual DNSMasqServer::UPtr make_dnsmasq_server(const Path& network_dir,
70-
const QString& bridge_name,
71-
const std::string& subnet) const;
70+
const SubnetList& subnets) const;
7271
};
7372
} // namespace multipass

src/platform/backends/qemu/linux/qemu_platform_detail.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,27 @@ class QemuPlatformDetail : public QemuPlatform
4646
void set_authorization(std::vector<NetworkInterfaceInfo>& networks) override;
4747

4848
private:
49-
const QString bridge_name;
49+
// explicitly naming DisabledCopyMove since the private one derived from QemuPlatform takes
50+
// precedence in lookup
51+
struct Subnet : private multipass::DisabledCopyMove
52+
{
53+
const QString bridge_name;
54+
const std::string subnet;
55+
FirewallConfig::UPtr firewall_config;
56+
57+
Subnet(const Path& network_dir, const std::string& name);
58+
~Subnet();
59+
};
60+
using Subnets = std::unordered_map<std::string, Subnet>;
61+
62+
[[nodiscard]] static Subnets get_subnets(const Path& network_dir);
63+
64+
[[nodiscard]] static SubnetList get_subnets_list(const Subnets&);
65+
5066
const Path network_dir;
51-
const std::string subnet;
67+
const Subnets subnets;
5268
DNSMasqServer::UPtr dnsmasq_server;
53-
FirewallConfig::UPtr firewall_config;
54-
std::unordered_map<std::string, std::pair<QString, std::string>> name_to_net_device_map;
69+
std::unordered_map<std::string, std::tuple<QString, std::string, QString>>
70+
name_to_net_device_map;
5571
};
5672
} // namespace multipass

src/platform/backends/qemu/linux/qemu_platform_detail_linux.cpp

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*
1616
*/
1717

18+
#include "multipass/constants.h"
1819
#include "qemu_platform_detail.h"
1920

2021
#include <multipass/file_ops.h>
@@ -35,7 +36,7 @@ namespace mpu = multipass::utils;
3536
namespace
3637
{
3738
constexpr auto category = "qemu platform";
38-
const QString multipass_bridge_name{"mpqemubr0"};
39+
const QString multipass_bridge_name{"mpqemubr%1"};
3940

4041
// An interface name can only be 15 characters, so this generates a hash of the
4142
// VM instance name with a "tap-" prefix and then truncates it.
@@ -103,14 +104,10 @@ void set_ip_forward()
103104
}
104105
}
105106

106-
mp::DNSMasqServer::UPtr init_nat_network(const mp::Path& network_dir,
107-
const QString& bridge_name,
108-
const std::string& subnet)
107+
mp::DNSMasqServer::UPtr init_nat_network(const mp::Path& network_dir, const mp::SubnetList& subnets)
109108
{
110-
create_virtual_switch(subnet, bridge_name);
111109
set_ip_forward();
112-
113-
return MP_DNSMASQ_SERVER_FACTORY.make_dnsmasq_server(network_dir, bridge_name, subnet);
110+
return MP_DNSMASQ_SERVER_FACTORY.make_dnsmasq_server(network_dir, subnets);
114111
}
115112

116113
void delete_virtual_switch(const QString& bridge_name)
@@ -122,24 +119,62 @@ void delete_virtual_switch(const QString& bridge_name)
122119
}
123120
} // namespace
124121

125-
mp::QemuPlatformDetail::QemuPlatformDetail(const mp::Path& data_dir)
126-
: bridge_name{multipass_bridge_name},
127-
network_dir{MP_UTILS.make_dir(QDir(data_dir), "network")},
122+
mp::QemuPlatformDetail::Subnet::Subnet(const Path& network_dir, const std::string& name)
123+
: bridge_name{multipass_bridge_name.arg(name.c_str())},
128124
subnet{MP_BACKEND.get_subnet(network_dir, bridge_name)},
129-
dnsmasq_server{init_nat_network(network_dir, bridge_name, subnet)},
130125
firewall_config{MP_FIREWALL_CONFIG_FACTORY.make_firewall_config(bridge_name, subnet)}
126+
{
127+
create_virtual_switch(subnet, bridge_name);
128+
}
129+
130+
mp::QemuPlatformDetail::Subnet::~Subnet()
131+
{
132+
delete_virtual_switch(bridge_name);
133+
}
134+
135+
[[nodiscard]] mp::QemuPlatformDetail::Subnets mp::QemuPlatformDetail::get_subnets(
136+
const Path& network_dir)
137+
{
138+
Subnets subnets{};
139+
subnets.reserve(default_zone_names.size());
140+
141+
for (const auto& zone : default_zone_names)
142+
{
143+
subnets.emplace(std::piecewise_construct,
144+
std::forward_as_tuple(zone),
145+
std::forward_as_tuple(network_dir, zone));
146+
}
147+
148+
return subnets;
149+
}
150+
151+
[[nodiscard]] mp::SubnetList mp::QemuPlatformDetail::get_subnets_list(const Subnets& subnets)
152+
{
153+
SubnetList out{};
154+
out.reserve(subnets.size());
155+
156+
for (const auto& [_, subnet] : subnets)
157+
{
158+
out.emplace_back(subnet.bridge_name, subnet.subnet);
159+
}
160+
161+
return out;
162+
}
163+
164+
mp::QemuPlatformDetail::QemuPlatformDetail(const mp::Path& data_dir)
165+
: network_dir{MP_UTILS.make_dir(QDir(data_dir), "network")},
166+
subnets{get_subnets(network_dir)},
167+
dnsmasq_server{init_nat_network(network_dir, get_subnets_list(subnets))}
131168
{
132169
}
133170

134171
mp::QemuPlatformDetail::~QemuPlatformDetail()
135172
{
136173
for (const auto& it : name_to_net_device_map)
137174
{
138-
const auto& [tap_device_name, hw_addr] = it.second;
175+
const auto& [tap_device_name, hw_addr, _] = it.second;
139176
remove_tap_device(tap_device_name);
140177
}
141-
142-
delete_virtual_switch(bridge_name);
143178
}
144179

145180
std::optional<mp::IPAddress> mp::QemuPlatformDetail::get_ip_for(const std::string& hw_addr)
@@ -152,8 +187,8 @@ void mp::QemuPlatformDetail::remove_resources_for(const std::string& name)
152187
auto it = name_to_net_device_map.find(name);
153188
if (it != name_to_net_device_map.end())
154189
{
155-
const auto& [tap_device_name, hw_addr] = it->second;
156-
dnsmasq_server->release_mac(hw_addr);
190+
const auto& [tap_device_name, hw_addr, bridge_name] = it->second;
191+
dnsmasq_server->release_mac(hw_addr, bridge_name);
157192
remove_tap_device(tap_device_name);
158193

159194
name_to_net_device_map.erase(name);
@@ -166,17 +201,22 @@ void mp::QemuPlatformDetail::platform_health_check()
166201
MP_BACKEND.check_if_kvm_is_in_use();
167202

168203
dnsmasq_server->check_dnsmasq_running();
169-
firewall_config->verify_firewall_rules();
204+
for (const auto& [_, subnet] : subnets)
205+
{
206+
subnet.firewall_config->verify_firewall_rules();
207+
}
170208
}
171209

172210
QStringList mp::QemuPlatformDetail::vm_platform_args(const VirtualMachineDescription& vm_desc)
173211
{
174212
// Configure and generate the args for the default network interface
175213
auto tap_device_name = generate_tap_device_name(vm_desc.vm_name);
214+
const QString& bridge_name = subnets.at(vm_desc.zone).bridge_name;
176215
create_tap_device(tap_device_name, bridge_name);
177216

178-
name_to_net_device_map.emplace(vm_desc.vm_name,
179-
std::make_pair(tap_device_name, vm_desc.default_mac_address));
217+
name_to_net_device_map.emplace(
218+
vm_desc.vm_name,
219+
std::make_tuple(tap_device_name, vm_desc.default_mac_address, bridge_name));
180220

181221
QStringList opts;
182222

0 commit comments

Comments
 (0)