1
0
mirror of https://github.com/containers/netavark.git synced 2026-02-05 06:45:56 +01:00

Add container hostname to DHCP requests and use container id as client id

Currently, since a container's hostname isn't sent to netavark, we can't
include it in DHCP requests when using a macvlan network and DHCP IPAM
driver. Therefore, if DNS records need to be added for a container, it needs
to be done by external scripts.  Another issue is that since every time a
container starts it gets a new random mac address and since, in the absense
of a client id, the mac address is used by DHCP servers to keep track of
leases, it's likely that the container will get a new ip address on restart.
If the container is providing an exposed service, even just ssh, clients may
have the old address cached and may not be able to reach the container.

This commit accepts the container's hostname on incoming JSON setup commands
and adds it to the DHCP client configuration so an Option 12
(Hostname) parameter can be sent in DHCP messages. This should allow DDNS
updates of DNS resords. This is dependent on corresponding commits in Podman
and common to actually pass the host name of course.  This commit also sets
the Option 61 (Client identifier) to the container's ID.  Since this stays
constant across container restarts, it's likely the DHCP server will use the
existing lease and return the same IP address.

* Added "container_hostname" to network::types::NetworkOptions. The setup
  command will set this from the incoming JSON and pass it through ultimately
  to dhcp_service::DhcpV4Service via macvlan_dhcp.get_dhcp_lease().

* Added "container_id" to proxy.proto so it can also be passed to
  dhcp_service::DhcpV4Service and used to set the client id.

* Added tests to test-dhcp/002-setup.bats to check that hostname is passed
  correctly.

Fixes: #676
Signed-off-by: George Joseph <g.devel@wxy78.net>
This commit is contained in:
George Joseph
2024-11-24 11:03:42 -07:00
parent 03c09b62ed
commit 2bf9cfdc68
12 changed files with 75 additions and 11 deletions

View File

@@ -79,6 +79,7 @@ impl Setup {
firewall: firewall_driver.as_ref(),
container_id: &network_options.container_id,
container_name: &network_options.container_name,
container_hostname: &network_options.container_hostname,
container_dns_servers: &network_options.dns_servers,
netns_host: hostns.file.as_fd(),
netns_container: netns.file.as_fd(),

View File

@@ -96,6 +96,7 @@ impl Teardown {
firewall: firewall_driver.as_ref(),
container_id: &network_options.container_id,
container_name: &network_options.container_name,
container_hostname: &network_options.container_hostname,
container_dns_servers: &network_options.dns_servers,
netns_host: hostns.file.as_fd(),
netns_container: netns.file.as_fd(),

View File

@@ -49,6 +49,30 @@ impl DhcpV4Service {
pub fn new(nc: NetworkConfig, timeout: u32) -> Result<Self, DhcpServiceError> {
let mut config = DhcpV4Config::new_proxy(&nc.host_iface, &nc.container_mac_addr);
config.set_timeout(timeout);
// Sending the hostname to the DHCP server is optional but it can be useful
// in environments where DDNS is used to create or update DNS records.
if !nc.host_name.is_empty() {
config.set_host_name(&nc.host_name);
};
// DHCP servers use the "client id", which is usually the MAC address,
// to keep track of leases but each time the container starts, it gets
// a new, random, MAC address so there's a good chance that the container
// won't get the same IP address if it restarts. This can be an issue if
// a container provides a service and needs to be restarted because, even
// if DDNS is in use and the container has a DNS A record, a client may
// still have the old IP address cached until the DNS TTL expires.
//
// Since the container id remains constant for life of the container
// and it should be globally unique, we can use it as the client id to
// ensure the container gets the same IP address each time it starts.
// The client id is a byte array so we need to convert the container id
// to a byte array. The client_id_type of "0" means the client id
// is not a hardware address.
config.set_client_id(0, nc.container_id.as_bytes());
let client = match DhcpV4ClientAsync::init(config, None) {
Ok(client) => Ok(client),
Err(err) => Err(DhcpServiceError::new(InvalidArgument, err.to_string())),

View File

@@ -23,6 +23,7 @@ impl FromStr for NetworkConfig {
version: 0,
ns_path: "".to_string(),
container_iface: "".to_string(),
container_id: "".to_string(),
})
}
}

View File

@@ -29,6 +29,7 @@ pub struct DriverInfo<'a> {
pub dns_port: u16,
pub config_dir: &'a Path,
pub rootless: bool,
pub container_hostname: &'a Option<String>,
}
pub trait NetworkDriver {

View File

@@ -33,17 +33,19 @@ pub fn get_dhcp_lease(
container_network_interface: &str,
ns_path: &str,
container_macvlan_mac: &str,
container_hostname: &str,
container_id: &str,
) -> NetavarkResult<DhcpLeaseInfo> {
let nvp_config = NetworkConfig {
host_iface: host_network_interface.to_string(),
// TODO add in domain name support
domain_name: "".to_string(),
// TODO add in host name support
host_name: "".to_string(),
host_name: container_hostname.to_string(),
version: 0,
ns_path: ns_path.to_string(),
container_iface: container_network_interface.to_string(),
container_mac_addr: container_macvlan_mac.to_string(),
container_id: container_id.to_string(),
};
let lease = match tokio::task::LocalSet::new().block_on(
match &tokio::runtime::Builder::new_current_thread()
@@ -127,12 +129,12 @@ pub fn release_dhcp_lease(
host_iface: host_network_interface.to_string(),
// TODO add in domain name support
domain_name: "".to_string(),
// TODO add in host name support
host_name: "".to_string(),
version: 0,
ns_path: ns_path.to_string(),
container_iface: container_network_interface.to_string(),
container_mac_addr: container_macvlan_mac.to_string(),
container_id: "".to_string(),
};
match tokio::task::LocalSet::new().block_on(
match &tokio::runtime::Builder::new_current_thread()

View File

@@ -66,10 +66,14 @@ pub struct NetworkOptions {
#[serde(rename = "container_id")]
pub container_id: String,
/// The container name, used as dns name.
/// The container name.
#[serde(rename = "container_name")]
pub container_name: String,
/// The container hostname.
#[serde(rename = "container_hostname")]
pub container_hostname: Option<String>,
/// The options used to create the interfaces with.
/// The networks listed in "network_info" have to match this,
/// both use the network name as key for the map.

View File

@@ -188,6 +188,8 @@ impl driver::NetworkDriver for Vlan<'_> {
&data.container_interface_name,
self.info.netns_path,
&container_vlan_mac,
self.info.container_hostname.as_deref().unwrap_or(""),
self.info.container_id,
)?;
// do not overwrite dns servers set by dns podman flag
if !self.info.container_dns_servers.is_some() {

View File

@@ -16,6 +16,7 @@ message NetworkConfig {
string host_name = 5;
Version version = 6;
string ns_path = 7;
string container_id = 8;
}
// Lease can either contain a IPv4 or IPv6 DHCP lease, and the common IP information

View File

@@ -15,7 +15,8 @@ load helpers
"domain_name": "example.com",
"host_name": "foobar",
"version": 0,
"ns_path": "$NS_PATH"
"ns_path": "$NS_PATH",
"container_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
\0
EOF
@@ -25,8 +26,28 @@ EOF
assert `echo "$output" | jq -r .siaddr` == $(gateway_from_subnet "$SUBNET_CIDR")
container_ip=$(echo "$output" | jq -r .yiaddr)
has_ip "$container_ip" veth0
# Check that there was a hostname in the DHCP requests
assert `grep -c "client provides name: foobar" "$TMP_TESTDIR/dnsmasq.log"` == 3
}
@test "no hostname" {
read -r -d '\0' input_config <<EOF
{
"host_iface": "veth1",
"container_iface": "veth0",
"container_mac_addr": "$CONTAINER_MAC",
"domain_name": "example.com",
"version": 0,
"ns_path": "$NS_PATH",
"container_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
\0
EOF
# Check that there was no hostname in the DHCP requests
assert `grep -c "client provides name" "$TMP_TESTDIR/dnsmasq.log"` == 0
}
@test "empty interface should fail 155" {
read -r -d '\0' input_config <<EOF
@@ -37,7 +58,8 @@ EOF
"domain_name": "example.com",
"host_name": "foobar",
"version": 0,
"ns_path": "$NS_PATH"
"ns_path": "$NS_PATH",
"container_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
\0
EOF
@@ -55,7 +77,8 @@ EOF
"domain_name": "example.com",
"host_name": "foobar",
"version": 0,
"ns_path": "$NS_PATH"
"ns_path": "$NS_PATH",
"container_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
\0
EOF
@@ -73,7 +96,8 @@ EOF
"domain_name": "example.com",
"host_name": "foobar",
"version": 0,
"ns_path": "$NS_PATH"
"ns_path": "$NS_PATH",
"container_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
\0
EOF
@@ -91,7 +115,8 @@ EOF
"domain_name": "example.com",
"host_name": "foobar",
"version": 0,
"ns_path": "$NS_PATH"
"ns_path": "$NS_PATH",
"container_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
\0
EOF

View File

@@ -14,7 +14,8 @@ load helpers
"domain_name": "example.com",
"host_name": "foobar",
"version": 0,
"ns_path": "$NS_PATH"
"ns_path": "$NS_PATH",
"container_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
\0
EOF

View File

@@ -15,7 +15,8 @@ load helpers
"domain_name": "example.com",
"host_name": "foobar",
"version": 0,
"ns_path": "$NS_PATH"
"ns_path": "$NS_PATH",
"container_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
\0
EOF