Skip to content

Commit 8b11a17

Browse files
committed
tests: add test_ipv4_redirect_to_ipv6
1 parent 57706cc commit 8b11a17

File tree

2 files changed

+89
-29
lines changed

2 files changed

+89
-29
lines changed

src/integration_tests/kernel_linux.rs

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use tokio::time::sleep;
2424

2525
#[apply(test_local!)]
2626
async fn test_order() -> anyhow::Result<()> {
27-
let (name, (_g1, _g2, chans, _g3)) = run_kernel_test([
27+
let (name, (_g1, _g2, chans, _g3)) = run_kernel_test(0xffff0000, [
2828
"flow4 { dst 10.0.0.0/9; length > 1024; } { bgp_ext_community.add((unknown 0x8006, 0, 0)); }",
2929
"flow4 { dst 10.0.0.0/10; length > 1024; } { bgp_ext_community.add((unknown 0x8006, 0, 0x4c97a25c)); }",
3030
"flow6 { src fdfd::/128; next header 17; } { bgp_ext_community.add((unknown 0x800c, 0, 0)); }",
@@ -69,9 +69,9 @@ async fn test_redirect_to_ip() -> anyhow::Result<()> {
6969
let (conn, handle, _) = rtnetlink::new_connection()?;
7070
tokio::spawn(conn);
7171

72-
let table_index = 0xffff0000;
72+
let table_index = 0xffff0001;
7373
let dummy_index = create_dummy_link(&handle, "10.128.128.254/24".parse()?).await?;
74-
let (name, (_g1, bird, chans, _g2)) = run_kernel_test([
74+
let (name, (_g1, bird, chans, _g2)) = run_kernel_test(table_index, [
7575
"flow4 { dst 172.20.0.0/16; } { bgp_ext_community.add((unknown 0x800c, 10.128.128.1, 0)); }",
7676
"flow4 { dst 172.21.0.0/16; } { bgp_ext_community.add((unknown 0x800c, 10.128.128.1, 0)); }",
7777
])
@@ -81,9 +81,9 @@ async fn test_redirect_to_ip() -> anyhow::Result<()> {
8181
print_ip_rule(false).await?;
8282
print_ip_route(false, table_index).await?;
8383

84+
let nft_stmts = get_nft_stmts(&name, &name).await?;
8485
let ip_rules = get_ip_rule(&handle, IpVersion::V4).await?;
8586
let ip_routes = get_ip_route(&handle, IpVersion::V4, table_index).await?;
86-
let nft_stmts = get_nft_stmts(&name, &name).await?;
8787
close_cli(chans).await;
8888
drop(bird);
8989
remove_link(&handle, dummy_index).await?;
@@ -131,9 +131,9 @@ async fn test_redirect_to_ipv6() -> anyhow::Result<()> {
131131
let (conn, handle, _) = rtnetlink::new_connection()?;
132132
tokio::spawn(conn);
133133

134-
let table_index = 0xffff0000;
134+
let table_index = 0xffff0002;
135135
let dummy_index = create_dummy_link(&handle, "fc64::1/64".parse()?).await?;
136-
let (name, (_g1, exabgp, chans, _g2)) = run_kernel_test_exabgp([
136+
let (name, (_g1, exabgp, chans, _g2)) = run_kernel_test_exabgp(table_index, [
137137
"match { destination fc00::/16; } then { redirect-to-nexthop-ietf fc64::ffff; }",
138138
"match { destination fc65:6565::/32; } then { redirect-to-nexthop-ietf fc64::2333; }",
139139
])
@@ -143,9 +143,9 @@ async fn test_redirect_to_ipv6() -> anyhow::Result<()> {
143143
print_ip_rule(true).await?;
144144
print_ip_route(true, table_index).await?;
145145

146+
let nft_stmts = get_nft_stmts(&name, &name).await?;
146147
let ip_rules = get_ip_rule(&handle, IpVersion::V6).await?;
147148
let ip_routes = get_ip_route(&handle, IpVersion::V6, table_index).await?;
148-
let nft_stmts = get_nft_stmts(&name, &name).await?;
149149
close_cli(chans).await;
150150
drop(exabgp);
151151
remove_link(&handle, dummy_index).await?;
@@ -189,20 +189,73 @@ async fn test_redirect_to_ipv6() -> anyhow::Result<()> {
189189
}
190190

191191
#[apply(test_local!)]
192-
async fn test_random_114514() -> anyhow::Result<()> {
192+
async fn test_ipv4_redirect_to_ipv6() -> anyhow::Result<()> {
193193
let (conn, handle, _) = rtnetlink::new_connection()?;
194194
tokio::spawn(conn);
195-
let ip_routes = get_ip_route(&handle, IpVersion::V4, 254).await?;
196195

197-
println!("{ip_routes:?}");
196+
let table_index = 0xffff0003;
197+
let dummy_index = create_dummy_link(&handle, "fc65::1/64".parse()?).await?;
198+
let (name, (_g1, exabgp, chans, _g2)) = run_kernel_test_exabgp(table_index, [
199+
"match { destination 172.17.254.192/26; } then { redirect-to-nexthop-ietf fc65::ffff; }",
200+
"match { destination 192.0.2.0/27; } then { redirect-to-nexthop-ietf fc65::2333; }",
201+
])
202+
.await?;
203+
204+
print_nft_chain(&name, &name).await?;
205+
print_ip_rule(false).await?;
206+
print_ip_route(false, table_index).await?;
207+
208+
let nft_stmts = get_nft_stmts(&name, &name).await?;
209+
let ip_rules = get_ip_rule(&handle, IpVersion::V4).await?;
210+
let ip_routes = get_ip_route(&handle, IpVersion::V4, table_index).await?;
211+
close_cli(chans).await;
212+
drop(exabgp);
213+
remove_link(&handle, dummy_index).await?;
214+
215+
assert_eq!(nft_stmts, [
216+
vec![
217+
prefix_stmt("daddr", "192.0.2.0/27".parse()?).unwrap(),
218+
stmt::Statement::Mangle(stmt::Mangle { key: make_meta(expr::MetaKey::Mark), value: Number(table_index) }),
219+
ACCEPT,
220+
],
221+
vec![
222+
prefix_stmt("daddr", "172.17.254.192/26".parse()?).unwrap(),
223+
stmt::Statement::Mangle(stmt::Mangle { key: make_meta(expr::MetaKey::Mark), value: Number(table_index) }),
224+
ACCEPT,
225+
],
226+
]);
227+
228+
let ip_rule_exp = make_ip_rule_mark(IpVersion::V4, 100, table_index, table_index);
229+
println!("> ip rule = {ip_rules:?}");
230+
println!("> exp = {ip_rule_exp:?}");
231+
assert!(ip_rules.contains(&ip_rule_exp));
232+
233+
let mut ip_routes_exp = [
234+
RouteMessageBuilder::<Ipv4Addr>::new()
235+
.table_id(table_index)
236+
.destination_prefix("172.17.254.192".parse()?, 26)
237+
.output_interface(dummy_index)
238+
.gateway("fc65::ffff".parse()?)
239+
.build(),
240+
RouteMessageBuilder::<Ipv4Addr>::new()
241+
.table_id(table_index)
242+
.destination_prefix("192.0.2.0".parse()?, 27)
243+
.output_interface(dummy_index)
244+
.gateway("fc65::2333".parse()?)
245+
.build(),
246+
];
247+
ip_routes_exp.iter_mut().for_each(route_msg_normalize);
248+
assert_eq!(ip_routes, ip_routes_exp);
198249

199250
Ok(())
200251
}
201252

202-
// TODO: test IPv4 with IPv6 nexthop
203253
// TODO: test IPv6 offset
204254

205-
async fn run_kernel_test(flows: impl IntoIterator<Item = &str>) -> anyhow::Result<(String, CliGuard)> {
255+
async fn run_kernel_test(
256+
init_table_id: u32,
257+
flows: impl IntoIterator<Item = &str>,
258+
) -> anyhow::Result<(String, CliGuard)> {
206259
ensure_bird_2();
207260
ensure_root();
208261
ensure_loopback_up().await?;
@@ -215,7 +268,7 @@ async fn run_kernel_test(flows: impl IntoIterator<Item = &str>) -> anyhow::Resul
215268
}
216269
});
217270

218-
let (table_name, flow_port, cli) = prepare_kernel_test().await?;
271+
let (table_name, flow_port, cli) = prepare_kernel_test(init_table_id).await?;
219272
let bird = BIRD_CONFIG_1
220273
.replace("@@BIRD_PORT@@", &pick_port().await?.to_string())
221274
.replace("@@FLOW_PORT@@", &flow_port.to_string())
@@ -226,21 +279,24 @@ async fn run_kernel_test(flows: impl IntoIterator<Item = &str>) -> anyhow::Resul
226279
Ok((table_name, guard))
227280
}
228281

229-
async fn run_kernel_test_exabgp(flows: impl IntoIterator<Item = &str>) -> anyhow::Result<(String, CliGuard)> {
282+
async fn run_kernel_test_exabgp(
283+
init_table_id: u32,
284+
flows: impl IntoIterator<Item = &str>,
285+
) -> anyhow::Result<(String, CliGuard)> {
230286
// ensure_exabgp();
231287
ensure_root();
232288
ensure_loopback_up().await?;
233289

234290
let flows = flows.into_iter().map(|x| format!("route {{ {x} }}")).join("\n");
235291

236-
let (table_name, port, cli) = prepare_kernel_test().await?;
292+
let (table_name, port, cli) = prepare_kernel_test(init_table_id).await?;
237293
let daemon = EXABGP_CONFIG_1.replace("@@FLOWS@@", &flows);
238294

239295
let guard = run_kernel_test_common(run_cli_with_exabgp(cli, &daemon, port).await?).await?;
240296
Ok((table_name, guard))
241297
}
242298

243-
async fn prepare_kernel_test() -> anyhow::Result<(String, u16, Cli)> {
299+
async fn prepare_kernel_test(init_table_id: u32) -> anyhow::Result<(String, u16, Cli)> {
244300
let table_name: String = "flow_test_"
245301
.chars()
246302
.chain(rand::thread_rng().sample_iter(&Alphanumeric).take(8).map(char::from))
@@ -257,6 +313,8 @@ async fn prepare_kernel_test() -> anyhow::Result<(String, u16, Cli)> {
257313
&table_name,
258314
"--chain",
259315
&table_name,
316+
"--init-table-id",
317+
&init_table_id.to_string(),
260318
])?;
261319
Ok((table_name, port, cli))
262320
}

src/kernel/rtnl.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
use super::{Kernel, Result};
22
use crate::bgp::flow::Flowspec;
3-
use crate::net::IpPrefix;
3+
use crate::net::{Afi, IpPrefix};
44
use crate::util::grace;
55
use clap::Args;
66
use futures::channel::mpsc::UnboundedReceiver;
77
use futures::{try_join, StreamExt, TryStreamExt};
8-
use libc::{RTA_GATEWAY, RTA_OIF};
9-
use log::warn;
8+
use libc::{RTA_GATEWAY, RTA_OIF, RTA_VIA};
109
use rtnetlink::packet_core::{NetlinkMessage, NetlinkPayload};
1110
use rtnetlink::packet_route::address::{AddressAttribute, AddressMessage};
12-
use rtnetlink::packet_route::route::{RouteAddress, RouteAttribute, RouteMessage};
11+
use rtnetlink::packet_route::route::{RouteAddress, RouteAttribute, RouteMessage, RouteVia};
1312
use rtnetlink::packet_route::rule::{RuleAction, RuleAttribute, RuleMessage};
1413
use rtnetlink::packet_route::{AddressFamily, RouteNetlinkMessage};
1514
use rtnetlink::packet_utils::nla::Nla;
@@ -53,7 +52,7 @@ impl<K: Kernel> RtNetlink<K> {
5352

5453
pub async fn add(&mut self, id: K::Handle, spec: &Flowspec, next_hop: IpAddr) -> Result<u32> {
5554
let prefix = spec.dst_prefix();
56-
let attrs = self.get_route(next_hop).await?;
55+
let attrs = self.get_route(prefix.afi(), next_hop).await?;
5756

5857
// Create table first...
5958
let (table_id, table_created) = if let Some((table_id, prefixes)) =
@@ -238,8 +237,7 @@ impl<K: Kernel> RtNetlink<K> {
238237
) -> Result<()> {
239238
// TODO: remove route if next hop becomes unreachable
240239
for (prefix, next_hop, table_id, attrs) in iter {
241-
warn!("process {prefix}");
242-
let new_attrs = Self::get_route2(handle, *next_hop).await?;
240+
let new_attrs = Self::get_route2(handle, prefix.afi(), *next_hop).await?;
243241
if *attrs != new_attrs {
244242
*attrs = new_attrs.clone();
245243
let mut msg = RouteMessageBuilder::<IpAddr>::new()
@@ -254,10 +252,10 @@ impl<K: Kernel> RtNetlink<K> {
254252
Ok(())
255253
}
256254

257-
async fn get_route(&self, ip: IpAddr) -> Result<Vec<RouteAttribute>> {
258-
Self::get_route2(&self.handle, ip).await
255+
async fn get_route(&self, afi: Afi, ip: IpAddr) -> Result<Vec<RouteAttribute>> {
256+
Self::get_route2(&self.handle, afi, ip).await
259257
}
260-
async fn get_route2(handle: &Handle, ip: IpAddr) -> Result<Vec<RouteAttribute>> {
258+
async fn get_route2(handle: &Handle, afi: Afi, ip: IpAddr) -> Result<Vec<RouteAttribute>> {
261259
let msg = RouteMessageBuilder::<IpAddr>::new()
262260
.destination_prefix(ip, if ip.is_ipv4() { 32 } else { 128 })
263261
.expect("destination prefix should be valid")
@@ -269,7 +267,7 @@ impl<K: Kernel> RtNetlink<K> {
269267
let mut has_gateway = false;
270268
let attrs = rt.attributes.into_iter().filter(|x| {
271269
let kind = x.kind();
272-
if kind == RTA_GATEWAY {
270+
if kind == RTA_GATEWAY || kind == RTA_VIA {
273271
has_gateway = true;
274272
true
275273
} else {
@@ -278,7 +276,11 @@ impl<K: Kernel> RtNetlink<K> {
278276
});
279277
let mut attrs: Vec<_> = attrs.collect();
280278
if !has_gateway {
281-
attrs.push(RouteAttribute::Gateway(ip.into()));
279+
if let (Afi::Ipv4, IpAddr::V6(v6)) = (afi, ip) {
280+
attrs.push(RouteAttribute::Via(RouteVia::Inet6(v6)));
281+
} else {
282+
attrs.push(RouteAttribute::Gateway(ip.into()));
283+
}
282284
}
283285
Ok(attrs)
284286
}
@@ -306,7 +308,7 @@ pub struct RtNetlinkArgs {
306308
///
307309
/// Table IDs are also used as fwmarks.
308310
#[arg(long, value_name = "ID", default_value_t = 0xffff0000)]
309-
pub init_table_id: u32,
311+
pub init_table_id: u32, // TODO: specify table range
310312

311313
/// Route rule priority as shown in `ip rule`.
312314
#[arg(long, value_name = "PRIO", default_value_t = 100)]

0 commit comments

Comments
 (0)