blob: 80fb9cd5bd5c177f6422ca1fe1b2db89636ca7e4 [file] [log] [blame]
Mohammed Naser9acc76a2025-02-16 13:18:14 -05001extern crate ipnet;
2
3mod routes;
4
5use futures_util::stream::TryStreamExt;
6use ipnet::IpNet;
7use log::{error, info};
8use netlink_packet_route::{
9 address::{AddressAttribute, AddressMessage},
10 route::{RouteAttribute, RouteMessage, RouteScope},
11 AddressFamily,
12};
13use rtnetlink::{Handle, IpVersion};
14use std::net::IpAddr;
15use thiserror::Error;
16
17#[derive(Error, Debug)]
18pub enum InterfaceError {
19 #[error("Interface {0} not found")]
20 NotFound(String),
21
22 #[error(transparent)]
23 NetlinkError(#[from] rtnetlink::Error),
24
25 #[error(transparent)]
26 IpNetError(#[from] ipnet::PrefixLenError),
27
28 #[error(transparent)]
29 RouteError(#[from] routes::RouteError),
30}
31
32#[derive(Error, Debug)]
33pub enum InterfaceMigrationError {
34 #[error(transparent)]
35 InterfaceError(#[from] InterfaceError),
36
37 #[error("IP configuration on both interfaces")]
38 IpConflict,
39}
40
41pub struct Interface {
42 name: String,
43 index: u32,
44 address_messages: Vec<AddressMessage>,
45 route_messages: Vec<RouteMessage>,
46}
47
48impl Interface {
49 pub async fn new(handle: &Handle, name: String) -> Result<Self, InterfaceError> {
50 let index = handle
51 .link()
52 .get()
53 .match_name(name.clone())
54 .execute()
55 .try_next()
56 .await
57 .map_err(|e| match e {
58 rtnetlink::Error::NetlinkError(inner) if -inner.raw_code() == libc::ENODEV => {
59 InterfaceError::NotFound(name.clone())
60 }
61 _ => InterfaceError::NetlinkError(e),
62 })?
63 .map(|link| link.header.index)
64 .ok_or_else(|| InterfaceError::NotFound(name.clone()))?;
65
66 let address_messages: Vec<AddressMessage> = handle
67 .address()
68 .get()
69 .set_link_index_filter(index)
70 .execute()
71 .map_err(InterfaceError::NetlinkError)
72 .try_filter(|msg| futures::future::ready(msg.header.family == AddressFamily::Inet))
73 .try_collect()
74 .await?;
75
76 let route_messages: Vec<RouteMessage> = handle
77 .route()
78 .get(IpVersion::V4)
79 .execute()
80 .map_err(InterfaceError::NetlinkError)
81 .try_filter(move |route_msg| {
82 let matches = route_msg
83 .attributes
84 .iter()
85 .any(|attr| matches!(attr, RouteAttribute::Oif(idx) if *idx == index))
86 && route_msg.header.kind != netlink_packet_route::route::RouteType::Local;
87
88 futures_util::future::ready(matches)
89 })
90 .try_collect()
91 .await?;
92
93 Ok(Self {
94 name,
95 index,
96 address_messages,
97 route_messages,
98 })
99 }
100
101 fn addresses(&self) -> Vec<IpNet> {
102 self.address_messages
103 .iter()
104 .filter_map(|msg| {
105 msg.attributes.iter().find_map(|nla| {
106 if let AddressAttribute::Address(ip) = nla {
107 IpNet::new(*ip, msg.header.prefix_len).ok()
108 } else {
109 None
110 }
111 })
112 })
113 .collect()
114 }
115
116 fn routes(&self) -> Result<Vec<routes::Route>, routes::RouteError> {
117 self.route_messages
118 .iter()
119 .filter_map(|msg| {
120 if msg.header.scope == RouteScope::Link {
121 return None;
122 }
123
124 Some(routes::Route::from_message(msg.clone()))
125 })
126 .collect::<Result<Vec<routes::Route>, routes::RouteError>>()
127 }
128
129 async fn up(&self, handle: &Handle) -> Result<(), InterfaceError> {
130 handle
131 .link()
132 .set(self.index)
133 .up()
134 .execute()
135 .await
136 .map_err(InterfaceError::NetlinkError)
137 }
138
139 async fn restore(&self, handle: &Handle) -> Result<(), InterfaceError> {
140 self.migrate_addresses_from_interface(handle, self).await?;
141 self.migrate_routes_from_interface(handle, self).await?;
142
143 Ok(())
144 }
145
146 async fn flush(&self, handle: &Handle) -> Result<(), InterfaceError> {
147 for msg in self.address_messages.iter() {
148 handle.address().del(msg.clone()).execute().await?;
149 }
150
151 // NOTE(mnaser): Once the interface has no more addresses, it will
152 // automatically lose all of it's routes.
153
154 Ok(())
155 }
156
157 async fn migrate_addresses_from_interface(
158 &self,
159 handle: &Handle,
160 src_interface: &Interface,
161 ) -> Result<(), InterfaceError> {
162 for msg in src_interface.address_messages.iter() {
163 let ip = msg.attributes.iter().find_map(|nla| match nla {
164 AddressAttribute::Address(ip) => Some(ip),
165 _ => None,
166 });
167
168 if let Some(ip) = ip {
169 handle
170 .address()
171 .add(self.index, *ip, msg.header.prefix_len)
172 .replace()
173 .execute()
174 .await?;
175 }
176 }
177
178 Ok(())
179 }
180
181 async fn migrate_routes_from_interface(
182 &self,
183 handle: &Handle,
184 src_interface: &Interface,
185 ) -> Result<(), InterfaceError> {
186 for route in src_interface.routes()?.iter() {
187 let mut request = handle.route().add();
188 request = request.protocol(route.protocol);
189
190 match route.destination.addr() {
191 IpAddr::V4(ipv4) => {
192 let mut request = request
193 .v4()
194 .replace()
195 .destination_prefix(ipv4, route.destination.prefix_len());
196
197 if let IpAddr::V4(gateway) = route.gateway {
198 request = request.gateway(gateway);
199 }
200
201 request.execute().await?;
202 }
203 IpAddr::V6(ipv6) => {
204 let mut request = request
205 .v6()
206 .replace()
207 .destination_prefix(ipv6, route.destination.prefix_len());
208
209 if let IpAddr::V6(gateway) = route.gateway {
210 request = request.gateway(gateway);
211 }
212
213 request.execute().await?;
214 }
215 }
216 }
217
218 Ok(())
219 }
220
221 pub async fn migrate_from_interface(
222 &self,
223 handle: &Handle,
224 src_interface: &Interface,
225 ) -> Result<(), InterfaceMigrationError> {
226 self.up(handle).await?;
227
228 match (
229 src_interface.address_messages.is_empty(),
230 self.address_messages.is_empty(),
231 ) {
232 (false, false) => {
233 // Both source and destination interfaces have IPs assigned
234 error!(
235 src_interface = src_interface.name.as_str(),
236 dst_interface = self.name.as_str(),
237 src_ip_addresses = format!("{:?}", src_interface.addresses()).as_str(),
238 dst_ip_addresses = format!("{:?}", self.addresses()).as_str();
239 "Both source and destination interfaces have IPs assigned. This is not safe in production, please fix manually."
240 );
241
242 Err(InterfaceMigrationError::IpConflict)
243 }
244 (false, true) => {
245 // Source interface has IPs, destination interface has no IPs
246 info!(
247 src_interface = src_interface.name.as_str(),
248 dst_interface = self.name.as_str(),
249 ip_addresses = format!("{:?}", src_interface.addresses()).as_str(),
250 routes = format!("{:?}", src_interface.routes()).as_str();
251 "Migrating IP addresses from interface to bridge."
252 );
253
254 if let Err(e) = src_interface.flush(handle).await {
255 error!(
256 src_interface = src_interface.name.as_str(),
257 error = e.to_string().as_str();
258 "Error while flushing IPs from source interface."
259 );
260
261 if let Err(restore_err) = src_interface.restore(handle).await {
262 error!(
263 src_interface = src_interface.name.as_str(),
264 error = restore_err.to_string().as_str();
265 "Error while restoring IPs to source interface."
266 );
267 }
268
269 return Err(InterfaceMigrationError::InterfaceError(e));
270 }
271
272 info!(
273 src_interface = src_interface.name.as_str(),
274 dst_interface = self.name.as_str();
275 "Successfully flushed IP addresses from source interface."
276 );
277
278 if let Err(e) = self
279 .migrate_addresses_from_interface(handle, src_interface)
280 .await
281 {
282 error!(
283 dst_interface = self.name.as_str(),
284 error = e.to_string().as_str();
285 "Error while migrating IP addresses to destination interface."
286 );
287
288 if let Err(restore_err) = src_interface.restore(handle).await {
289 error!(
290 src_interface = src_interface.name.as_str(),
291 error = restore_err.to_string().as_str();
292 "Error while restoring IPs to source interface."
293 );
294 }
295
296 return Err(InterfaceMigrationError::InterfaceError(e));
297 }
298
299 info!(
300 src_interface = src_interface.name.as_str(),
301 dst_interface = self.name.as_str();
302 "Successfully migrated IP addresseses to new interface."
303 );
304
305 if let Err(e) = self
306 .migrate_routes_from_interface(handle, src_interface)
307 .await
308 {
309 error!(
310 dst_interface = self.name.as_str(),
311 routes = format!("{:?}", src_interface.routes()).as_str(),
312 error = e.to_string().as_str();
313 "Error while migrating routes to destination interface."
314 );
315
316 if let Err(restore_err) = src_interface.restore(handle).await {
317 error!(
318 src_interface = src_interface.name.as_str(),
319 routes = format!("{:?}", src_interface.routes()).as_str(),
320 error = restore_err.to_string().as_str();
321 "Error while restoring source interface."
322 );
323 }
324
325 return Err(InterfaceMigrationError::InterfaceError(e));
326 }
327
328 Ok(())
329 }
330 (true, false) => {
331 // Destination interface has IPs, source interface has no IPs
332 info!(
333 src_interface = src_interface.name.as_str(),
334 dst_interface = self.name.as_str(),
335 ip_addresses = format!("{:?}", self.addresses()).as_str();
336 "Bridge already has IPs assigned. Skipping migration."
337 );
338
339 Ok(())
340 }
341 (true, true) => {
342 // Neither interface has IPs
343 info!(
344 src_interface = src_interface.name.as_str(),
345 dst_interface = self.name.as_str();
346 "Neither interface nor bridge have IPs assigned. Skipping migration."
347 );
348
349 Ok(())
350 }
351 }
352 }
353}