Skip to content

Commit 6c834d7

Browse files
committed
bridge: bind ip for aardvark-dns in unmanaged mode if gateway ip is not on the host
Find the Universe scope IPv4 addresses of the bridge with the modified dump_addresses() function. This function works for all interfaces, not just bridges. If the dns is enabled and the bridge mode is unmanaged, then the bind IP of aardvark-dns is changed to the IP addresses of the bridge instead of the gateway. If there are no IP address on the bridge then we just fail with a clear error that the user must disable dns (--disable-dns) when creating the network. Signed-off-by: Shivang K Raghuvanshi <[email protected]>
1 parent 9b3e332 commit 6c834d7

File tree

6 files changed

+196
-27
lines changed

6 files changed

+196
-27
lines changed

examples/host-device-plugin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl Plugin for Exec {
5050
}
5151
}
5252

53-
let addresses = host.netlink.dump_addresses()?;
53+
let addresses = host.netlink.dump_addresses(None, None, None)?;
5454
let mut subnets = Vec::new();
5555
for address in addresses {
5656
if address.header.index == link.header.index {

src/network/bridge.rs

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
use std::{collections::HashMap, fs, net::IpAddr, os::fd::BorrowedFd};
22

3-
use ipnet::IpNet;
4-
use log::{debug, error};
5-
use netlink_packet_route::link::{
6-
BridgeVlanInfoFlags, InfoBridge, InfoData, InfoKind, InfoVeth, LinkAttribute, LinkInfo,
7-
LinkMessage,
8-
};
9-
103
use crate::dns::aardvark::SafeString;
114
use crate::network::core_utils::get_default_route_interface;
125
use crate::network::dhcp::{dhcp_teardown, get_dhcp_lease};
@@ -20,6 +13,14 @@ use crate::{
2013
},
2114
network::{constants, sysctl::disable_ipv6_autoconf, types},
2215
};
16+
use ipnet::IpNet;
17+
use log::{debug, error};
18+
use netlink_packet_route::address::{AddressAttribute, AddressScope};
19+
use netlink_packet_route::link::{
20+
BridgeVlanInfoFlags, InfoBridge, InfoData, InfoKind, InfoVeth, LinkAttribute, LinkInfo,
21+
LinkMessage,
22+
};
23+
use netlink_packet_route::AddressFamily;
2324

2425
use super::{
2526
constants::{
@@ -79,6 +80,16 @@ pub struct Bridge<'a> {
7980
data: Option<InternalData>,
8081
}
8182

83+
struct CreateInterfacesResult {
84+
/// The MAC address of the container's veth interface.
85+
mac_address: String,
86+
/// An optional writer for creating a sysctl.d config file.
87+
/// This is only created when a new bridge is created.
88+
sysctl_writer: Option<sysctl::SysctlDWriter<'static, String, String>>,
89+
/// The interface index of the bridge.
90+
bridge_index: u32,
91+
}
92+
8293
impl<'a> Bridge<'a> {
8394
pub fn new(info: DriverInfo<'a>) -> Self {
8495
Bridge { info, data: None }
@@ -166,7 +177,11 @@ impl driver::NetworkDriver for Bridge<'_> {
166177

167178
let (host_sock, netns_sock) = netlink_sockets;
168179

169-
let (container_veth_mac, sysctl_writer) = create_interfaces(
180+
let CreateInterfacesResult {
181+
mac_address: container_veth_mac,
182+
sysctl_writer,
183+
bridge_index,
184+
} = create_interfaces(
170185
host_sock,
171186
netns_sock,
172187
data,
@@ -252,18 +267,53 @@ impl driver::NetworkDriver for Bridge<'_> {
252267
}
253268
}
254269

255-
let gw = data
256-
.ipam
257-
.gateway_addresses
258-
.iter()
259-
.map(|ipnet| ipnet.addr())
260-
.collect();
270+
// Fixes #1177: In unmanaged mode, the gateway IP may not be on the host.
271+
// We need to find an IP on the bridge itself for aardvark-dns to bind to.
272+
let bind_addr: Vec<IpAddr> = if data.mode == BridgeMode::Unmanaged {
273+
let addr_msgs = host_sock.dump_addresses(
274+
Some(bridge_index),
275+
None,
276+
Some(AddressScope::Universe),
277+
)?;
278+
279+
let addresses: Vec<IpAddr> = addr_msgs
280+
.into_iter()
281+
.filter_map(|addr_msg| {
282+
// address is either a IPv4 address, or it's an IPv6 address that is not a link-local address.
283+
if (addr_msg.header.family == AddressFamily::Inet6
284+
&& !(addr_msg.header.scope == AddressScope::Link))
285+
|| (addr_msg.header.family == AddressFamily::Inet)
286+
{
287+
addr_msg.attributes.into_iter().find_map(|attr| {
288+
if let AddressAttribute::Address(ip) = attr {
289+
Some(ip)
290+
} else {
291+
None
292+
}
293+
})
294+
} else {
295+
None
296+
}
297+
})
298+
.collect();
299+
if addresses.is_empty() {
300+
return Err(NetavarkError::msg(format!("bridge '{}' in unmanaged mode has no universe scope IP addresses, but aardvark-dns requires at least one universe scope address to bind to. Please add an universe scope IP address or disable DNS for this network (--disable-dns).", data.bridge_interface_name)));
301+
}
302+
response.dns_server_ips = Some(addresses.clone());
303+
addresses
304+
} else {
305+
data.ipam
306+
.gateway_addresses
307+
.iter()
308+
.map(|ipnet| ipnet.addr())
309+
.collect()
310+
};
261311

262312
match self.info.container_id.as_str().try_into() {
263313
Ok(id) => Some(AardvarkEntry {
264314
network_name: &self.info.network.name,
265315
container_id: id,
266-
network_gateways: gw,
316+
network_gateways: bind_addr,
267317
network_dns_servers: &self.info.network.network_dns_servers,
268318
container_ips_v4: ipv4,
269319
container_ips_v6: ipv6,
@@ -553,10 +603,7 @@ fn create_interfaces(
553603
rootless: bool,
554604
hostns_fd: BorrowedFd<'_>,
555605
netns_fd: BorrowedFd<'_>,
556-
) -> NetavarkResult<(
557-
String,
558-
Option<sysctl::SysctlDWriter<'static, String, String>>,
559-
)> {
606+
) -> NetavarkResult<CreateInterfacesResult> {
560607
let mut sysctl_writer = None;
561608
let (bridge_index, mtu, mac) = match host.get_link(netlink::LinkID::Name(
562609
data.bridge_interface_name.to_string(),
@@ -738,7 +785,11 @@ fn create_interfaces(
738785
netns_fd,
739786
mtu,
740787
)?;
741-
Ok((mac, sysctl_writer))
788+
Ok(CreateInterfacesResult {
789+
mac_address: mac,
790+
sysctl_writer,
791+
bridge_index,
792+
})
742793
}
743794

744795
/// return the container veth mac address

src/network/netlink.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use netlink_packet_core::{
1414
NLM_F_REQUEST,
1515
};
1616
use netlink_packet_route::{
17-
address::AddressMessage,
17+
address::{AddressMessage, AddressScope},
1818
link::{
1919
AfSpecBridge, BridgeVlanInfo, BridgeVlanInfoFlags, InfoBridge, InfoData, InfoKind,
2020
LinkAttribute, LinkFlags, LinkInfo, LinkMessage,
@@ -114,6 +114,8 @@ impl Socket {
114114
pub fn new() -> NetavarkResult<Socket> {
115115
let mut socket = wrap!(netlink_sys::Socket::new(NETLINK_ROUTE), "open")?;
116116
let addr = &SocketAddr::new(0, 0);
117+
// Needs to be enabled for dump filtering to work
118+
socket.set_netlink_get_strict_chk(true)?;
117119
wrap!(socket.bind(addr), "bind")?;
118120
wrap!(socket.connect(addr), "connect")?;
119121

@@ -409,11 +411,28 @@ impl Socket {
409411
Ok(links)
410412
}
411413

412-
pub fn dump_addresses(&mut self) -> NetavarkResult<Vec<AddressMessage>> {
413-
let msg = AddressMessage::default();
414+
// If filtering options are supplied, then only the ip addresses satisfying the filter are returned. Otherwise all ip addresses of all interfaces are returned
415+
// WARNING: When scope_filter is set to AddressScope::Universe, this function returns all scope IP addresses without filtering. In that case, caller must perform any required filtering in userspace.
416+
pub fn dump_addresses(
417+
&mut self,
418+
interface_id_filter: Option<u32>,
419+
address_filter: Option<AddressFamily>,
420+
scope_filter: Option<AddressScope>,
421+
) -> NetavarkResult<Vec<AddressMessage>> {
422+
let mut msg = AddressMessage::default();
414423

415-
let results = self
416-
.make_netlink_request(RouteNetlinkMessage::GetAddress(msg), NLM_F_DUMP | NLM_F_ACK)?;
424+
if let Some(id) = interface_id_filter {
425+
msg.header.index = id;
426+
}
427+
if let Some(addr_family) = address_filter {
428+
msg.header.family = addr_family;
429+
}
430+
if let Some(scope) = scope_filter {
431+
msg.header.scope = scope;
432+
}
433+
434+
let results =
435+
self.make_netlink_request(RouteNetlinkMessage::GetAddress(msg), NLM_F_DUMP)?;
417436

418437
let mut addresses = Vec::with_capacity(results.len());
419438

src/test/netlink.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,9 @@ mod tests {
175175
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
176176
assert!(out.status.success(), "failed to set up lo via ip");
177177

178-
let addresses = sock.dump_addresses().expect("dump_addresses failed");
178+
let addresses = sock
179+
.dump_addresses(None, None, None)
180+
.expect("dump_addresses failed");
179181
for nla in addresses[0].attributes.iter() {
180182
if let address::AddressAttribute::Address(ip) = nla {
181183
assert_eq!(ip, &IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)))
@@ -187,4 +189,42 @@ mod tests {
187189
}
188190
}
189191
}
192+
#[test]
193+
fn test_dump_addr_filter() {
194+
test_setup!();
195+
let mut sock = Socket::new().expect("Socket::new()");
196+
197+
let out = run_command!("ip", "link", "add", "test1", "type", "dummy");
198+
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
199+
assert!(out.status.success(), "failed to add link via ip");
200+
201+
let net = "10.0.0.2/24";
202+
203+
let out = run_command!("ip", "addr", "add", net, "dev", "test1");
204+
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
205+
assert!(out.status.success(), "failed to add addr via ip");
206+
207+
let out = run_command!("ip", "link", "set", "up", "lo");
208+
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
209+
assert!(out.status.success(), "failed to set up lo via ip");
210+
211+
let bridge_id: u32 = sock
212+
.get_link(LinkID::Name("test1".to_string()))
213+
.expect("get_link failed")
214+
.header
215+
.index;
216+
217+
let addresses = sock
218+
.dump_addresses(
219+
Some(bridge_id),
220+
Some(netlink_packet_route::AddressFamily::Inet),
221+
Some(address::AddressScope::Universe),
222+
)
223+
.expect("dump_address_filter failed");
224+
for nla in addresses[0].attributes.iter() {
225+
if let address::AddressAttribute::Address(ip) = nla {
226+
assert_eq!(ip, &IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)))
227+
}
228+
}
229+
}
190230
}

test/620-bridge-mode.bats

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,30 @@ load helpers
4444
expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/bridge-managed-dhcp.json setup $(get_container_netns_path)
4545
assert_json ".error" "cannot use dhcp ipam driver without using the option mode=unmanaged" "dhcp error"
4646
}
47+
48+
@test bridge - unmanaged mode with aardvark-dns no bridge ip {
49+
run_in_host_netns ip link add brtest0 type bridge
50+
run_in_host_netns ip link set brtest0 up
51+
run_in_host_netns ip -j --details link show brtest0
52+
link_info="$output"
53+
assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Host bridge interface is up"
54+
55+
expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/bridge-unmanaged-dns.json setup $(get_container_netns_path)
56+
assert_json ".error" "bridge 'brtest0' in unmanaged mode has no universe scope IP addresses, but aardvark-dns requires at least one universe scope address to bind to. Please add an universe scope IP address or disable DNS for this network (--disable-dns)."
57+
}
58+
59+
@test bridge - unmanaged mode with aardvark-dns bridge ip {
60+
run_in_host_netns ip link add brtest0 type bridge
61+
run_in_host_netns sysctl -w net.ipv6.conf.brtest0.accept_dad=0
62+
run_in_host_netns ip link set brtest0 up
63+
run_in_host_netns ip -j --details link show brtest0
64+
link_info="$output"
65+
assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Host bridge interface is up"
66+
67+
run_in_host_netns ip addr add 10.88.0.100/16 dev brtest0
68+
run_in_host_netns ip addr add 2001:db8:abcd:000a:50a6:6bff:fe84:255a dev brtest0
69+
run_netavark --file ${TESTSDIR}/testfiles/bridge-unmanaged-dns.json setup $(get_container_netns_path)
70+
71+
run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman"
72+
assert "${lines[0]}" == "10.88.0.100,2001:db8:abcd:a:50a6:6bff:fe84:255a" "aardvark-dns should bind to the unmanaged bridge IP"
73+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"container_id": "6ce776ea58b5",
3+
"container_name": "testcontainer",
4+
"networks": {
5+
"podman": {
6+
"interface_name": "eth0",
7+
"static_ips": ["10.88.0.2", "2001:db8:abcd:0::2"]
8+
}
9+
},
10+
"network_info": {
11+
"podman": {
12+
"dns_enabled": true,
13+
"driver": "bridge",
14+
"id": "53ce4390f2adb1681eb1a90ec8b48c49c015e0a8d336c197637e7f65e365fa9e",
15+
"internal": false,
16+
"ipv6_enabled": false,
17+
"name": "podman",
18+
"network_interface": "brtest0",
19+
"subnets": [
20+
{
21+
"subnet": "10.88.0.0/16"
22+
},
23+
{
24+
"subnet": "2001:db8:abcd::/48"
25+
}
26+
],
27+
"options": {
28+
"mode": "unmanaged"
29+
}
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)