-
Notifications
You must be signed in to change notification settings - Fork 764
[az] Fix qemu virtual switch creation and improve Subnet API #4650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: availability-zones
Are you sure you want to change the base?
Conversation
be82107 to
efbe974
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## availability-zones #4650 +/- ##
======================================================
+ Coverage 87.91% 87.92% +0.01%
======================================================
Files 263 263
Lines 14710 14715 +5
======================================================
+ Hits 12931 12936 +5
Misses 1779 1779 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
f3aa8f4 to
fb6090e
Compare
ricab
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @jimporter, great that you spotted, fixed, and properly tested this. I have some concerns about the underlying subnet representation that tie directly into the approach here. Let me know what you think.
| template <class T> | ||
| explicit PrefixLengthOutOfRange(const T& value) | ||
| : FormattedExceptionBase{ | ||
| "Subnet prefix length must be non-negative and less than 31: {}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Off-topic, but shouldn't this say "less than 32"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's correct, though that's just because the Subnet class doesn't handle the special cases for subnet masks of /31 or /32. From Wikipedia:
[Normally,] a /31 network, with one binary digit in the host identifier, is unusable, as such a subnet would provide no available host addresses after this reduction. RFC 3021 creates an exception to the "host all ones" and "host all zeros" rules to make /31 networks usable for point-to-point links. /32 addresses (single-host network) must be accessed by explicit routing rules, as there is no address available for a gateway.
Since we don't implement any of that logic (it probably wouldn't be useful for us), we just don't support /31 or /32 at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some comments explaining that /31 and /32 are special cases that we don't handle.
include/multipass/subnet.h
Outdated
| IPAddress address; | ||
| PrefixLength prefix; | ||
| IPAddress address_; | ||
| PrefixLength prefix_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New convention for fields? Private fields? I don't mind, but conventions like this aren't worth much unless they're meaning is understood and always followed. Otherwise, readers can't assume that variables without trailing underscores are not fields, for example. So I'd prefer if we discussed in the team and established a convention first. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just fallout from adding an address() member function, and I went with the most obvious alternative at the time. I saw that this style was already used in a couple places, e.g. src/client/gui/windows/runner/win32_window.h. I'll just rename this to something more "normal" for now, and we can discuss with the team whether this would be a useful convention.
src/network/subnet.cpp
Outdated
| ip.octets[i] &= mask.octets[i]; | ||
| } | ||
| return ip; | ||
| return mp::IPAddress(ip.as_uint32() & mask.as_uint32()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We normally prefer brace-initialization, except when we want a different meaning (e.g. vector(100)). See https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es23-prefer-the--initializer-syntax as well as the first note in https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es23-prefer-the--initializer-syntax
src/network/subnet.cpp
Outdated
|
|
||
| mp::Subnet::Subnet(IPAddress ip, PrefixLength prefix_length) | ||
| : address(apply_mask(ip, prefix_length)), prefix(prefix_length) | ||
| mp::Subnet::Subnet(IPAddress ip, PrefixLength prefix_length) : address_(ip), prefix_(prefix_length) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idem: brace init
| [[nodiscard]] IPAddress address() const; | ||
| [[nodiscard]] IPAddress network_address() const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference between these two is not obvious from the names or signatures. Do we really need them both? If so, could you clarify with a comment please? But I see address() being used only in a test.
But really, shouldn't we sanitize the IP we get when constructing? Don't we expect 1.2.3.4/16 to be the same as 1.2.0.0/16 and 1.2.21.32/16? Why does the actual address matter here? I am thinking we should zero out the suffix at construction to have a single underlying object representation for each possible subnet. I.e. only ever create canonical objects (what canonical() returns today), to have a 1 to 1 mapping between objects and values in our problem domain. That would simplify the interface (fewer methods) and the implementation (e.g. ordering).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference between these two is not obvious from the names or signatures. Do we really need them both? If so, could you clarify with a comment please? But I see address() being used only in a test.
I suppose we don't strictly need address() today, but for completeness of the Subnet interface, I think it's useful to be able to get the original IP address back out. network_address() is also only useful internally, so we could make that private (external users could still get the network_address via .canonical().address()). However, both of these correspond to members in boost::asio::ip::network_v4 (and also the proposed ISO standard std::net::ip::network_v4), and based on our prior discussion, I tried to nudge this class closer to the Boost/ISO interface.
But really, shouldn't we sanitize the IP we get when constructing? Don't we expect 1.2.3.4/16 to be the same as 1.2.0.0/16 and 1.2.21.32/16? Why does the actual address matter here?
This behavior (somehow) is necessary, since we need the following line in qemu_platform_detail_linux.cpp to return something like 1.2.3.4/16. It needs to be an IPv4 host plus subnet mask for the ip(8) command:
const auto cidr = mp::Subnet{subnet.min_address(), subnet.prefix_length()}.to_cidr();This also matches the network_v4 behavior:
jim@home ~
$ cat network_v4.cpp
#include <iostream>
#include <boost/asio.hpp>
int main() {
auto net = boost::asio::ip::make_network_v4("1.2.3.4/24");
std::cout << "net = " << net.to_string() << "\n"
<< "net.address() = " << net.address().to_string() << "\n";
}
jim@home ~
$ g++ network_v4.cpp -pthread -o network_v4
jim@home ~
$ ./network_v4
net = 1.2.3.4/24
net.address() = 1.2.3.4I think there's a strong argument that Subnet isn't the right name for this class anymore, but I'm not sure what the right name is. CIDR is an option as mentioned above, but I don't know if that's quite right either.
I'd thought about a few other options for how to do this, for example, I could have retained the old Subnet behavior where we applied the mask to canonicalize the IP address, and then just constructed the CIDR string for ip address add manually. However, I settled on this one because it will make it easier to migrate to Boost.ASIO (and eventually to a future ISO standard networking API) later on.
src/network/subnet.cpp
Outdated
| if (const auto ip_res = address_ <=> other.address_; ip_res != 0) | ||
| return ip_res; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same concern regarding canonical vs spurious values. Why should 1.2.3.4/24 be ordered after 1.2.3.0/24?
If we used a canonical representation, we could just return the order of maked IPs (assuming 1.2.0.0/16 < 1.2.3.0/24)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also meant to match Boost.ASIO:
jim@home ~
$ cat network_v4.cpp
#include <iostream>
#include <boost/asio.hpp>
int main() {
auto net1 = boost::asio::ip::make_network_v4("1.2.3.4/24");
auto net2 = boost::asio::ip::make_network_v4("1.2.3.0/24");
std::cout << "net1 == net2: " << (net1 == net2) << "\n";
}
jim@home ~
$ g++ network_v4.cpp -pthread -o network_v4
jim@home ~
$ ./network_v4
net1 == net2: 0However, boost::asio::ip::network_v4 doesn't support less/greater-than. I'm not sure we really need ordering though, so we could just support (in)equality operators here.
This is important because `ip address add` expects a full host address and subnet mask in CIDR notation. Previously, we'd apply the mask immediately to the IP address, resulting in us passing an invalid value, e.g. "192.168.1.0/24", instead of the proper value, "192.168.1.1/24".
Previously, this test wasn't able to catch that we had set the interface CIDR incorrectly because it was tautologically running the same instructions as what we were trying to test. Now, we hardcode the expected command, so any future regressions here should cause a test failure.
fb6090e to
9e2e835
Compare
Description
With the introduction of the
Subnetclass in the AZ branch, the qemu backend had a regression (possibly only in some cases) where creating the virtual switch failed. This happened because we incorrectly masked the host IP before we passed it toip address add. To fix this,Subnetnow retains the original IP address, but there's a newcanonical()method that returns the canonical form with the IP address masked.In addition, to help provide more-semantic usage of
Subnetand move us closer to thenetwork_v4class defined in Boost.ASIO and the ISO C++ Networking TS, I added abroadcast_address()method (this matchesbroadcast()in the specs). In the medium term, it would be nice to replace some of this common code with Boost.ASIO or (eventually) standard C++ (thanks @ricab for the suggestion).I'm not sure if we should rename the
Subnetclass now. I think there's an argument that it should be calledCIDR(classless inter-domain routing), since that's where the IP address + subnet mask concept comes from (and the "192.168.1.1/24" notation is called "CIDR notation"). The naming in our code is a little bit odd, since the stringification method forSubnetisto_cidr, but since a subnet is an IP address + subnet mask, it's already a CIDR block. That function just converts it to the standard notation for CIDR blocks.Testing
All unit tests updated to match the new API changes. In addition, I've improved the test that should have cause this regression, but which was tautological. The test had the same instructions as the code to be tested, so it would always pass for this issue. Now, we hardcode the expected command in the test, so future regressions here will cause a test failure.
Manual testing steps:
sudo ./bin/multipassdmultipass launch,multipass shell, etcChecklist