Skip to content

Commit cc646bc

Browse files
Merge pull request #1304 from shivkr6/bridge-unmanaged-dns-fix
bridge: unmanaged aardvark-dns fix
2 parents 1148b5f + 8655e62 commit cc646bc

File tree

6 files changed

+176
-26
lines changed

6 files changed

+176
-26
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)?;
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: 67 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,49 @@ 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(Some(bridge_index))?;
274+
275+
let addresses: Vec<IpAddr> = addr_msgs
276+
.into_iter()
277+
.filter_map(|addr_msg| {
278+
// address is either a IPv4 address, or it's an IPv6 address that is not a link-local address.
279+
if (addr_msg.header.family == AddressFamily::Inet6
280+
&& addr_msg.header.scope != AddressScope::Link)
281+
|| addr_msg.header.family == AddressFamily::Inet
282+
{
283+
addr_msg.attributes.into_iter().find_map(|attr| {
284+
if let AddressAttribute::Address(ip) = attr {
285+
Some(ip)
286+
} else {
287+
None
288+
}
289+
})
290+
} else {
291+
None
292+
}
293+
})
294+
.collect();
295+
if addresses.is_empty() {
296+
return Err(NetavarkError::msg(format!("bridge '{}' in unmanaged mode has no usable IP addresses. Aardvark-dns requires at least one address (should not be an IPv6 link-local address) to bind to. Please add an IP address or disable DNS for this network (--disable-dns).", data.bridge_interface_name)));
297+
}
298+
response.dns_server_ips = Some(addresses.clone());
299+
addresses
300+
} else {
301+
data.ipam
302+
.gateway_addresses
303+
.iter()
304+
.map(|ipnet| ipnet.addr())
305+
.collect()
306+
};
261307

262308
match self.info.container_id.as_str().try_into() {
263309
Ok(id) => Some(AardvarkEntry {
264310
network_name: &self.info.network.name,
265311
container_id: id,
266-
network_gateways: gw,
312+
network_gateways: bind_addr,
267313
network_dns_servers: &self.info.network.network_dns_servers,
268314
container_ips_v4: ipv4,
269315
container_ips_v6: ipv6,
@@ -553,10 +599,7 @@ fn create_interfaces(
553599
rootless: bool,
554600
hostns_fd: BorrowedFd<'_>,
555601
netns_fd: BorrowedFd<'_>,
556-
) -> NetavarkResult<(
557-
String,
558-
Option<sysctl::SysctlDWriter<'static, String, String>>,
559-
)> {
602+
) -> NetavarkResult<CreateInterfacesResult> {
560603
let mut sysctl_writer = None;
561604
let (bridge_index, mtu, mac) = match host.get_link(netlink::LinkID::Name(
562605
data.bridge_interface_name.to_string(),
@@ -738,7 +781,11 @@ fn create_interfaces(
738781
netns_fd,
739782
mtu,
740783
)?;
741-
Ok((mac, sysctl_writer))
784+
Ok(CreateInterfacesResult {
785+
mac_address: mac,
786+
sysctl_writer,
787+
bridge_index,
788+
})
742789
}
743790

744791
/// return the container veth mac address

src/network/netlink.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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,19 @@ 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+
pub fn dump_addresses(
416+
&mut self,
417+
interface_id_filter: Option<u32>,
418+
) -> NetavarkResult<Vec<AddressMessage>> {
419+
let mut msg = AddressMessage::default();
414420

415-
let results = self
416-
.make_netlink_request(RouteNetlinkMessage::GetAddress(msg), NLM_F_DUMP | NLM_F_ACK)?;
421+
if let Some(id) = interface_id_filter {
422+
msg.header.index = id;
423+
}
424+
425+
let results =
426+
self.make_netlink_request(RouteNetlinkMessage::GetAddress(msg), NLM_F_DUMP)?;
417427

418428
let mut addresses = Vec::with_capacity(results.len());
419429

src/test/netlink.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ 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.dump_addresses(None).expect("dump_addresses failed");
179179
for nla in addresses[0].attributes.iter() {
180180
if let address::AddressAttribute::Address(ip) = nla {
181181
assert_eq!(ip, &IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)))
@@ -187,4 +187,38 @@ mod tests {
187187
}
188188
}
189189
}
190+
#[test]
191+
fn test_dump_addr_filter() {
192+
test_setup!();
193+
let mut sock = Socket::new().expect("Socket::new()");
194+
195+
let out = run_command!("ip", "link", "add", "test1", "type", "dummy");
196+
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
197+
assert!(out.status.success(), "failed to add link via ip");
198+
199+
let net = "10.0.0.2/24";
200+
201+
let out = run_command!("ip", "addr", "add", net, "dev", "test1");
202+
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
203+
assert!(out.status.success(), "failed to add addr via ip");
204+
205+
let out = run_command!("ip", "link", "set", "up", "lo");
206+
eprintln!("{}", String::from_utf8(out.stderr).unwrap());
207+
assert!(out.status.success(), "failed to set up lo via ip");
208+
209+
let bridge_id: u32 = sock
210+
.get_link(LinkID::Name("test1".to_string()))
211+
.expect("get_link failed")
212+
.header
213+
.index;
214+
215+
let addresses = sock
216+
.dump_addresses(Some(bridge_id))
217+
.expect("dump_address_filter failed");
218+
for nla in addresses[0].attributes.iter() {
219+
if let address::AddressAttribute::Address(ip) = nla {
220+
assert_eq!(ip, &IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)))
221+
}
222+
}
223+
}
190224
}

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 usable IP addresses. Aardvark-dns requires at least one address (should not be an IPv6 link-local address) to bind to. Please add an 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)