Skip to content

Commit 2d8e758

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 2d8e758

File tree

6 files changed

+166
-20
lines changed

6 files changed

+166
-20
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: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use netlink_packet_route::link::{
66
BridgeVlanInfoFlags, InfoBridge, InfoData, InfoKind, InfoVeth, LinkAttribute, LinkInfo,
77
LinkMessage,
88
};
9+
use netlink_packet_route::{address::AddressScope, AddressFamily};
910

1011
use crate::dns::aardvark::SafeString;
1112
use crate::network::core_utils::get_default_route_interface;
@@ -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,41 @@ 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 addresses = host_sock.dump_addresses(
274+
Some(bridge_index),
275+
Some(AddressFamily::Inet),
276+
Some(AddressScope::Universe),
277+
)?;
278+
let mut bind_addr = Vec::with_capacity(addresses.len());
279+
for addr_msg in addresses {
280+
for attr in addr_msg.attributes {
281+
if let netlink_packet_route::address::AddressAttribute::Address(ip) = attr {
282+
bind_addr.push(ip);
283+
break;
284+
}
285+
}
286+
}
287+
if bind_addr.is_empty() {
288+
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)));
289+
}
290+
response.dns_server_ips = Some(bind_addr.clone());
291+
bind_addr
292+
} else {
293+
data.ipam
294+
.gateway_addresses
295+
.iter()
296+
.map(|ipnet| ipnet.addr())
297+
.collect()
298+
};
261299

262300
match self.info.container_id.as_str().try_into() {
263301
Ok(id) => Some(AardvarkEntry {
264302
network_name: &self.info.network.name,
265303
container_id: id,
266-
network_gateways: gw,
304+
network_gateways: bind_addr,
267305
network_dns_servers: &self.info.network.network_dns_servers,
268306
container_ips_v4: ipv4,
269307
container_ips_v6: ipv6,
@@ -553,10 +591,7 @@ fn create_interfaces(
553591
rootless: bool,
554592
hostns_fd: BorrowedFd<'_>,
555593
netns_fd: BorrowedFd<'_>,
556-
) -> NetavarkResult<(
557-
String,
558-
Option<sysctl::SysctlDWriter<'static, String, String>>,
559-
)> {
594+
) -> NetavarkResult<CreateInterfacesResult> {
560595
let mut sysctl_writer = None;
561596
let (bridge_index, mtu, mac) = match host.get_link(netlink::LinkID::Name(
562597
data.bridge_interface_name.to_string(),
@@ -738,7 +773,11 @@ fn create_interfaces(
738773
netns_fd,
739774
mtu,
740775
)?;
741-
Ok((mac, sysctl_writer))
776+
Ok(CreateInterfacesResult {
777+
mac_address: mac,
778+
sysctl_writer,
779+
bridge_index,
780+
})
742781
}
743782

744783
/// return the container veth mac address

src/network/netlink.rs

Lines changed: 23 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,27 @@ impl Socket {
409411
Ok(links)
410412
}
411413

412-
pub fn dump_addresses(&mut self) -> NetavarkResult<Vec<AddressMessage>> {
413-
let msg = AddressMessage::default();
414+
// If interface's LinkID is supplied, then only the ip addresses of that specific interface is 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+
address_filter: Option<AddressFamily>,
419+
scope_filter: Option<AddressScope>,
420+
) -> NetavarkResult<Vec<AddressMessage>> {
421+
let mut msg = AddressMessage::default();
414422

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

418436
let mut addresses = Vec::with_capacity(results.len());
419437

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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,28 @@ 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 ip link set brtest0 up
62+
run_in_host_netns ip -j --details link show brtest0
63+
link_info="$output"
64+
assert_json "$link_info" '.[].flags[] | select(.=="UP")' == "UP" "Host bridge interface is up"
65+
66+
run_in_host_netns ip addr add 10.88.0.1/16 dev brtest0
67+
run_netavark --file ${TESTSDIR}/testfiles/bridge-unmanaged-dns.json setup $(get_container_netns_path)
68+
69+
run_helper cat "$NETAVARK_TMPDIR/config/aardvark-dns/podman"
70+
assert "${lines[0]}" == "10.88.0.1" "aardvark-dns should bind to the unmanaged bridge IP"
71+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"container_id": "6ce776ea58b5",
3+
"container_name": "testcontainer",
4+
"networks": {
5+
"podman": {
6+
"interface_name": "eth0",
7+
"static_ips": ["10.88.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+
"options": {
20+
"mode": "unmanaged"
21+
}
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)