Skip to content

Commit 8655e62

Browse files
committed
bridge: bind ip for aardvark-dns in unmanaged mode if gateway ip is not on the host
Find all the IPv4 and IPv6 addresses except link local IPv6 addressses of the bridge with the modified dump_addresses() function. 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 is no usable 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. Fixes: #1177 Signed-off-by: Shivang K Raghuvanshi <[email protected]>
1 parent 9b3e332 commit 8655e62

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)