Skip to main content

libcontainer/network/
cidr.rs

1use std::net::IpAddr;
2
3use netlink_packet_route::address::{AddressAttribute, AddressMessage};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct CidrAddress {
8    pub prefix_len: u8,
9    pub address: IpAddr,
10}
11
12impl From<&AddressMessage> for CidrAddress {
13    fn from(msg: &AddressMessage) -> Self {
14        let address =
15            parse_ip_address(msg).expect("AddressMessage without IFA_LOCAL or IFA_ADDRESS");
16        CidrAddress {
17            prefix_len: msg.header.prefix_len,
18            address,
19        }
20    }
21}
22
23/// Parses the IP address from an AddressMessage following libnl conventions.
24///
25/// From libnl addr.c:
26/// - IPv6 sends the local address as IFA_ADDRESS with no IFA_LOCAL
27/// - IPv4 sends both IFA_LOCAL and IFA_ADDRESS, with IFA_ADDRESS being the peer address if they differ
28/// - For IPv6 Point-to-Point addresses, IFA_LOCAL should also be handled
29///
30/// Priority:
31/// 1. If IFA_LOCAL exists, use it (this handles IPv4 and IPv6 PtP correctly)
32/// 2. Otherwise, fall back to IFA_ADDRESS (this handles regular IPv6)
33///
34/// Note: While the RFC does not explicitly prohibit multiple IFA_ADDRESS attributes,
35/// common implementations (libnl, vishvananda/netlink) assume a single instance.
36/// This implementation follows the same assumption and uses the first matching attribute.
37fn parse_ip_address(addr: &AddressMessage) -> Option<IpAddr> {
38    // First, try to find IFA_LOCAL
39    let local = addr.attributes.iter().find_map(|attr| match attr {
40        AddressAttribute::Local(ip) => Some(*ip),
41        _ => None,
42    });
43
44    // If IFA_LOCAL exists, use it
45    if let Some(ip) = local {
46        return Some(ip);
47    }
48
49    // Otherwise, fall back to IFA_ADDRESS
50    addr.attributes.iter().find_map(|attr| match attr {
51        AddressAttribute::Address(ip) => Some(*ip),
52        _ => None,
53    })
54}
55
56#[cfg(test)]
57mod tests {
58    use std::net::{IpAddr, Ipv4Addr};
59
60    use netlink_packet_route::AddressFamily;
61    use netlink_packet_route::address::{AddressFlags, AddressMessage, AddressScope};
62
63    use super::*;
64
65    #[test]
66    fn test_address_message_to_cidr() {
67        let mut msg = AddressMessage::default();
68        msg.header.index = 10;
69        msg.header.prefix_len = 24;
70        msg.header.family = AddressFamily::Inet;
71        msg.header.scope = AddressScope::Universe;
72        let ip = "192.168.1.1".parse().unwrap();
73        msg.attributes.push(AddressAttribute::Address(ip));
74        msg.attributes
75            .push(AddressAttribute::Flags(AddressFlags::Permanent));
76
77        let cidr = CidrAddress::from(&msg);
78        assert_eq!(cidr.prefix_len, 24);
79        assert_eq!(cidr.address, ip);
80    }
81
82    #[test]
83    fn test_parse_ip_address_with_local() {
84        // Test IPv4 with IFA_LOCAL (typical IPv4 case)
85        let mut addr_msg = AddressMessage::default();
86        addr_msg
87            .attributes
88            .push(AddressAttribute::Local(IpAddr::V4(Ipv4Addr::new(
89                10, 0, 0, 1,
90            ))));
91        addr_msg
92            .attributes
93            .push(AddressAttribute::Address(IpAddr::V4(Ipv4Addr::new(
94                10, 0, 0, 2,
95            ))));
96
97        let result = parse_ip_address(&addr_msg);
98        assert_eq!(result, Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))));
99    }
100
101    #[test]
102    fn test_parse_ip_address_without_local() {
103        // Test IPv6 without IFA_LOCAL (typical IPv6 case)
104        let mut addr_msg = AddressMessage::default();
105        addr_msg
106            .attributes
107            .push(AddressAttribute::Address(IpAddr::V6(
108                std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),
109            )));
110
111        let result = parse_ip_address(&addr_msg);
112        assert_eq!(
113            result,
114            Some(IpAddr::V6(std::net::Ipv6Addr::new(
115                0x2001, 0xdb8, 0, 0, 0, 0, 0, 1
116            )))
117        );
118    }
119
120    #[test]
121    fn test_parse_ip_address_ipv6_with_local() {
122        // Test IPv6 PtP with IFA_LOCAL
123        let mut addr_msg = AddressMessage::default();
124        addr_msg.attributes.push(AddressAttribute::Local(IpAddr::V6(
125            std::net::Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
126        )));
127        addr_msg
128            .attributes
129            .push(AddressAttribute::Address(IpAddr::V6(
130                std::net::Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 2),
131            )));
132
133        let result = parse_ip_address(&addr_msg);
134        assert_eq!(
135            result,
136            Some(IpAddr::V6(std::net::Ipv6Addr::new(
137                0xfe80, 0, 0, 0, 0, 0, 0, 1
138            )))
139        );
140    }
141
142    #[test]
143    fn test_parse_ip_address_no_attributes() {
144        // Test with no address attributes
145        let addr_msg = AddressMessage::default();
146
147        let result = parse_ip_address(&addr_msg);
148        assert_eq!(result, None);
149    }
150}