Skip to content
This repository was archived by the owner on Oct 26, 2022. It is now read-only.

Commit 2b7ae36

Browse files
authored
Merge pull request #205 from little-dude/multipath-attribute
2 parents 8cbf334 + ec64afa commit 2b7ae36

File tree

8 files changed

+357
-11
lines changed

8 files changed

+357
-11
lines changed

netlink-packet-route/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ criterion = "0.3.0"
3030
pcap-file = "1.1.1"
3131
lazy_static = "1.4.0"
3232
netlink-sys = "0.8"
33+
pretty_assertions = "0.7.2"
3334

3435
[[bench]]
3536
name = "link_message"

netlink-packet-route/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,23 @@ pub use self::rtnl::*;
2222
#[cfg(test)]
2323
#[macro_use]
2424
extern crate lazy_static;
25+
26+
use std::net::IpAddr;
27+
28+
pub(crate) fn ip_len(addr: &IpAddr) -> usize {
29+
match addr {
30+
IpAddr::V4(_) => 4,
31+
IpAddr::V6(_) => 16,
32+
}
33+
}
34+
35+
pub(crate) fn emit_ip(buf: &mut [u8], addr: &IpAddr) {
36+
match addr {
37+
IpAddr::V4(ref ip) => buf.copy_from_slice(&ip.octets()),
38+
IpAddr::V6(ref ip) => buf.copy_from_slice(&ip.octets()),
39+
}
40+
}
41+
42+
#[cfg(test)]
43+
#[macro_use]
44+
extern crate pretty_assertions;

netlink-packet-route/src/rtnl/constants.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -783,12 +783,12 @@ pub const IFA_F_STABLE_PRIVACY: u32 = 2048;
783783
// pub const RTNL_FAMILY_MAX: int = 129;
784784
// pub const RTA_ALIGNTO: int = 4;
785785
//
786-
// pub const RTNH_F_DEAD: int = 1;
787-
// pub const RTNH_F_PERVASIVE: int = 2;
788-
// pub const RTNH_F_ONLINK: int = 4;
789-
// pub const RTNH_F_OFFLOAD: int = 8;
790-
// pub const RTNH_F_LINKDOWN: int = 16;
791-
// pub const RTNH_F_UNRESOLVED: int = 32;
786+
pub const RTNH_F_DEAD: u8 = 1;
787+
pub const RTNH_F_PERVASIVE: u8 = 2;
788+
pub const RTNH_F_ONLINK: u8 = 4;
789+
pub const RTNH_F_OFFLOAD: u8 = 8;
790+
pub const RTNH_F_LINKDOWN: u8 = 16;
791+
pub const RTNH_F_UNRESOLVED: u8 = 32;
792792
// pub const RTNH_COMPARE_MASK: int = 25;
793793
// pub const RTNH_ALIGNTO: int = 4;
794794
// pub const RTNETLINK_HAVE_PEERINFO: int = 1;

netlink-packet-route/src/rtnl/route/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ mod message;
66
pub mod nlas;
77

88
pub use self::{buffer::*, header::*, message::*, nlas::*};
9+
10+
#[cfg(test)]
11+
mod test;

netlink-packet-route/src/rtnl/route/nlas/mod.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ pub use self::metrics::*;
99
mod mfc_stats;
1010
pub use self::mfc_stats::*;
1111

12+
mod next_hops;
13+
pub use self::next_hops::*;
14+
1215
use anyhow::Context;
1316
use byteorder::{ByteOrder, NativeEndian};
1417

@@ -36,6 +39,11 @@ pub enum Nla {
3639
#[cfg(feature = "rich_nlas")]
3740
MfcStats(MfcStats),
3841
#[cfg(not(feature = "rich_nlas"))]
42+
MultiPath(Vec<u8>),
43+
#[cfg(feature = "rich_nlas")]
44+
// See: https://codecave.cc/multipath-routing-in-linux-part-1.html
45+
MultiPath(Vec<NextHop>),
46+
#[cfg(not(feature = "rich_nlas"))]
3947
CacheInfo(Vec<u8>),
4048
#[cfg(feature = "rich_nlas")]
4149
CacheInfo(CacheInfo),
@@ -44,7 +52,6 @@ pub enum Nla {
4452
Source(Vec<u8>),
4553
Gateway(Vec<u8>),
4654
PrefSource(Vec<u8>),
47-
MultiPath(Vec<u8>),
4855
Session(Vec<u8>),
4956
MpAlgo(Vec<u8>),
5057
Via(Vec<u8>),
@@ -76,7 +83,6 @@ impl nlas::Nla for Nla {
7683
| Source(ref bytes)
7784
| Gateway(ref bytes)
7885
| PrefSource(ref bytes)
79-
| MultiPath(ref bytes)
8086
| Session(ref bytes)
8187
| MpAlgo(ref bytes)
8288
| Via(ref bytes)
@@ -93,13 +99,17 @@ impl nlas::Nla for Nla {
9399
CacheInfo(ref bytes)
94100
| MfcStats(ref bytes)
95101
| Metrics(ref bytes)
102+
| MultiPath(ref bytes)
96103
=> bytes.len(),
104+
97105
#[cfg(feature = "rich_nlas")]
98106
CacheInfo(ref cache_info) => cache_info.buffer_len(),
99107
#[cfg(feature = "rich_nlas")]
100108
MfcStats(ref stats) => stats.buffer_len(),
101109
#[cfg(feature = "rich_nlas")]
102110
Metrics(ref metrics) => metrics.buffer_len(),
111+
#[cfg(feature = "rich_nlas")]
112+
MultiPath(ref next_hops) => next_hops.iter().map(|nh| nh.buffer_len()).sum(),
103113

104114
EncapType(_) => 2,
105115
Iif(_)
@@ -124,7 +134,6 @@ impl nlas::Nla for Nla {
124134
| Source(ref bytes)
125135
| Gateway(ref bytes)
126136
| PrefSource(ref bytes)
127-
| MultiPath(ref bytes)
128137
| Session(ref bytes)
129138
| MpAlgo(ref bytes)
130139
| Via(ref bytes)
@@ -138,6 +147,7 @@ impl nlas::Nla for Nla {
138147
=> buffer.copy_from_slice(bytes.as_slice()),
139148

140149
#[cfg(not(feature = "rich_nlas"))]
150+
MultiPath(ref bytes)
141151
| CacheInfo(ref bytes)
142152
| MfcStats(ref bytes)
143153
| Metrics(ref bytes)
@@ -149,6 +159,16 @@ impl nlas::Nla for Nla {
149159
MfcStats(ref stats) => stats.emit(buffer),
150160
#[cfg(feature = "rich_nlas")]
151161
Metrics(ref metrics) => metrics.emit(buffer),
162+
#[cfg(feature = "rich_nlas")]
163+
MultiPath(ref next_hops) => {
164+
let mut offset = 0;
165+
for nh in next_hops {
166+
let len = nh.buffer_len();
167+
nh.emit(&mut buffer[offset..offset+len]);
168+
offset += len
169+
}
170+
}
171+
152172
EncapType(value) => NativeEndian::write_u16(buffer, value),
153173
Iif(value)
154174
| Oif(value)
@@ -208,7 +228,6 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>> for Nla {
208228
RTA_SRC => Source(payload.to_vec()),
209229
RTA_GATEWAY => Gateway(payload.to_vec()),
210230
RTA_PREFSRC => PrefSource(payload.to_vec()),
211-
RTA_MULTIPATH => MultiPath(payload.to_vec()),
212231
RTA_SESSION => Session(payload.to_vec()),
213232
RTA_MP_ALGO => MpAlgo(payload.to_vec()),
214233
RTA_VIA => Via(payload.to_vec()),
@@ -260,6 +279,25 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable<NlaBuffer<&'a T>> for Nla {
260279
)
261280
.context("invalid RTA_METRICS value")?,
262281
),
282+
#[cfg(not(feature = "rich_nlas"))]
283+
RTA_MULTIPATH => MultiPath(payload.to_vec()),
284+
#[cfg(feature = "rich_nlas")]
285+
RTA_MULTIPATH => {
286+
let mut next_hops = vec![];
287+
let mut buf = payload;
288+
loop {
289+
let nh_buf =
290+
NextHopBuffer::new_checked(&buf).context("invalid RTA_MULTIPATH value")?;
291+
let len = nh_buf.length() as usize;
292+
let nh = NextHop::parse(&nh_buf).context("invalid RTA_MULTIPATH value")?;
293+
next_hops.push(nh);
294+
if buf.len() == len {
295+
break;
296+
}
297+
buf = &buf[len..];
298+
}
299+
MultiPath(next_hops)
300+
}
263301
_ => Other(DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?),
264302
})
265303
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use anyhow::Context;
2+
use std::net::IpAddr;
3+
4+
use crate::{
5+
constants::{self, RTA_GATEWAY},
6+
emit_ip,
7+
ip_len,
8+
nlas::{Nla, NlaBuffer},
9+
parsers::parse_ip,
10+
traits::{Emitable, Parseable},
11+
DecodeError,
12+
};
13+
14+
bitflags! {
15+
pub struct NextHopFlags: u8 {
16+
const RTNH_F_EMPTY = 0;
17+
const RTNH_F_DEAD = constants::RTNH_F_DEAD as u8;
18+
const RTNH_F_PERVASIVE = constants::RTNH_F_PERVASIVE as u8;
19+
const RTNH_F_ONLINK = constants::RTNH_F_ONLINK as u8;
20+
const RTNH_F_OFFLOAD = constants::RTNH_F_OFFLOAD as u8;
21+
const RTNH_F_LINKDOWN = constants::RTNH_F_LINKDOWN as u8;
22+
const RTNH_F_UNRESOLVED = constants::RTNH_F_UNRESOLVED as u8;
23+
}
24+
}
25+
26+
buffer!(NextHopBuffer {
27+
length: (u16, 0..2),
28+
flags: (u8, 2),
29+
hops: (u8, 3),
30+
interface_id: (u32, 4..8),
31+
gateway_nla: (slice, GATEWAY_OFFSET..),
32+
});
33+
34+
impl<T: AsRef<[u8]>> NextHopBuffer<T> {
35+
pub fn new_checked(buffer: T) -> Result<Self, DecodeError> {
36+
let packet = Self::new(buffer);
37+
packet.check_buffer_length()?;
38+
Ok(packet)
39+
}
40+
41+
fn check_buffer_length(&self) -> Result<(), DecodeError> {
42+
let len = self.buffer.as_ref().len();
43+
if len < 8 {
44+
return Err(format!("invalid NextHopBuffer: length {} < {}", len, 8).into());
45+
}
46+
if len < self.length() as usize {
47+
return Err(format!(
48+
"invalid NextHopBuffer: length {} < {}",
49+
len,
50+
8 + self.length()
51+
)
52+
.into());
53+
}
54+
Ok(())
55+
}
56+
}
57+
58+
const GATEWAY_OFFSET: usize = 8;
59+
60+
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
61+
pub struct NextHop {
62+
/// Next-hop flags (see [`NextHopFlags`])
63+
pub flags: NextHopFlags,
64+
/// Next-hop priority
65+
pub hops: u8,
66+
/// Interface index for the next-hop
67+
pub interface_id: u32,
68+
/// Gateway address (it is actually encoded as an `RTA_GATEWAY` nla)
69+
pub gateway: Option<IpAddr>,
70+
}
71+
72+
impl<'a, T: AsRef<[u8]>> Parseable<NextHopBuffer<&'a T>> for NextHop {
73+
fn parse(buf: &NextHopBuffer<&T>) -> Result<NextHop, DecodeError> {
74+
let gateway = if buf.length() as usize > GATEWAY_OFFSET {
75+
let gateway_nla_buf = NlaBuffer::new_checked(buf.gateway_nla())
76+
.context("cannot parse RTA_GATEWAY attribute in next-hop")?;
77+
if gateway_nla_buf.kind() != RTA_GATEWAY {
78+
return Err(format!("invalid RTA_GATEWAY attribute in next-hop: expected NLA type to be RTA_GATEWAY ({}), but got {} instead", RTA_GATEWAY, gateway_nla_buf.kind()).into());
79+
}
80+
let gateway = parse_ip(gateway_nla_buf.value()).context(
81+
"invalid RTA_GATEWAY attribute in next-hop: failed to parse NLA value as an IP address",
82+
)?;
83+
Some(gateway)
84+
} else {
85+
None
86+
};
87+
Ok(NextHop {
88+
flags: NextHopFlags::from_bits_truncate(buf.flags()),
89+
hops: buf.hops(),
90+
interface_id: buf.interface_id(),
91+
gateway,
92+
})
93+
}
94+
}
95+
96+
struct GatewayNla<'a>(&'a IpAddr);
97+
98+
impl<'a> Nla for GatewayNla<'a> {
99+
fn value_len(&self) -> usize {
100+
ip_len(self.0)
101+
}
102+
fn kind(&self) -> u16 {
103+
RTA_GATEWAY
104+
}
105+
fn emit_value(&self, buffer: &mut [u8]) {
106+
emit_ip(buffer, self.0)
107+
}
108+
}
109+
110+
impl Emitable for NextHop {
111+
fn buffer_len(&self) -> usize {
112+
// len, flags, hops and interface id fields
113+
8 + self
114+
.gateway
115+
.as_ref()
116+
.map(|ip| {
117+
// RTA_GATEWAY attribute header (length and type) + value length
118+
4 + ip_len(ip)
119+
})
120+
.unwrap_or(0)
121+
}
122+
123+
fn emit(&self, buffer: &mut [u8]) {
124+
let mut nh_buffer = NextHopBuffer::new(buffer);
125+
nh_buffer.set_length(self.buffer_len() as u16);
126+
nh_buffer.set_flags(self.flags.bits());
127+
nh_buffer.set_hops(self.hops);
128+
nh_buffer.set_interface_id(self.interface_id);
129+
if let Some(ref gateway) = self.gateway {
130+
let gateway_nla = GatewayNla(gateway);
131+
gateway_nla.emit(nh_buffer.gateway_nla_mut());
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)