@@ -7,24 +7,27 @@ use crate::args::Cli;
77use crate :: bgp:: flow:: Op ;
88use crate :: integration_tests:: helpers:: kernel:: linux:: { get_nft_stmts, print_ip_route, print_ip_rule, print_nft_chain} ;
99use crate :: integration_tests:: helpers:: kernel:: rtnl:: make_ip_rule_mark;
10- use crate :: kernel:: nft:: { make_limit, make_meta, make_payload_field, prefix_stmt, range_stmt, ACCEPT , DROP } ;
10+ use crate :: kernel:: nft:: {
11+ make_limit, make_meta, make_payload_field, mangle_stmt, prefix_stmt, range_stmt, ACCEPT , DROP ,
12+ } ;
13+ use crate :: net:: IpPrefix ;
1114use clap:: Parser ;
1215use itertools:: Itertools ;
1316use macro_rules_attribute:: apply;
1417use nftables:: expr:: Expression :: Number ;
1518use nftables:: expr:: { self , MetaKey } ;
16- use nftables:: stmt;
1719use rand:: distr:: Alphanumeric ;
1820use rand:: Rng ;
21+ use rtnetlink:: packet_route:: route:: { RouteAttribute , RouteType } ;
1922use rtnetlink:: { IpVersion , RouteMessageBuilder } ;
20- use std:: net:: { Ipv4Addr , Ipv6Addr } ;
23+ use std:: net:: { IpAddr , Ipv4Addr , Ipv6Addr } ;
2124use std:: time:: Duration ;
2225use tokio:: select;
2326use tokio:: time:: sleep;
2427
2528#[ apply( test_local!) ]
2629async fn test_order ( ) -> anyhow:: Result < ( ) > {
27- let ( name, ( _g1, _g2, chans, _g3) ) = run_kernel_test ( 0xffff0000 , [
30+ let ( name, ( _g1, _g2, chans, _g3) ) = run_kernel_test_bird ( 0xffff0000 , [
2831 "flow4 { dst 10.0.0.0/9; length > 1024; } { bgp_ext_community.add((unknown 0x8006, 0, 0)); }" ,
2932 "flow4 { dst 10.0.0.0/10; length > 1024; } { bgp_ext_community.add((unknown 0x8006, 0, 0x4c97a25c)); }" ,
3033 "flow6 { src fdfd::/128; next header 17; } { bgp_ext_community.add((unknown 0x800c, 0, 0)); }" ,
@@ -69,54 +72,54 @@ async fn test_redirect_to_ip() -> anyhow::Result<()> {
6972 let ( conn, handle, _) = rtnetlink:: new_connection ( ) ?;
7073 tokio:: spawn ( conn) ;
7174
72- let table_index = 0xffff0001 ;
73- let dummy_index = create_dummy_link ( & handle, "10.128.128.254/24" . parse ( ) ?) . await ?;
74- let ( name, ( _g1, bird, chans, _g2) ) = run_kernel_test ( table_index , [
75- "flow4 { dst 172.20.0.0/16; } { bgp_ext_community.add((unknown 0x800c , 10.128.128.1, 0)); }" ,
76- "flow4 { dst 172.21.0.0/16; } { bgp_ext_community.add((unknown 0x800c , 10.128.128.1, 0)); }" ,
75+ let table_id = 0xffff0001 ;
76+ let dummy_id = create_dummy_link ( & handle, "10.128.128.254/24" . parse ( ) ?) . await ?;
77+ let ( name, ( _g1, bird, chans, _g2) ) = run_kernel_test_bird ( table_id , [
78+ "flow4 { dst 172.20.0.0/16; } { bgp_ext_community.add((unknown 0x000c , 10.128.128.1, 0)); }" ,
79+ "flow4 { dst 172.21.0.0/16; } { bgp_ext_community.add((unknown 0x000c , 10.128.128.1, 0)); }" ,
7780 ] )
7881 . await ?;
7982
8083 print_nft_chain ( & name, & name) . await ?;
8184 print_ip_rule ( false ) . await ?;
82- print_ip_route ( false , table_index ) . await ?;
85+ print_ip_route ( false , table_id ) . await ?;
8386
8487 let nft_stmts = get_nft_stmts ( & name, & name) . await ?;
8588 let ip_rules = get_ip_rule ( & handle, IpVersion :: V4 ) . await ?;
86- let ip_routes = get_ip_route ( & handle, IpVersion :: V4 , table_index ) . await ?;
89+ let ip_routes = get_ip_route ( & handle, IpVersion :: V4 , table_id ) . await ?;
8790 close_cli ( chans) . await ;
8891 drop ( bird) ;
89- remove_link ( & handle, dummy_index ) . await ?;
92+ remove_link ( & handle, dummy_id ) . await ?;
9093
9194 assert_eq ! ( nft_stmts, [
9295 vec![
9396 prefix_stmt( "daddr" , "172.20.0.0/16" . parse( ) ?) . unwrap( ) ,
94- stmt :: Statement :: Mangle ( stmt :: Mangle { key : make_meta( expr:: MetaKey :: Mark ) , value : Number ( table_index ) } ) ,
97+ mangle_stmt ( make_meta( expr:: MetaKey :: Mark ) , Number ( table_id ) ) ,
9598 ACCEPT ,
9699 ] ,
97100 vec![
98101 prefix_stmt( "daddr" , "172.21.0.0/16" . parse( ) ?) . unwrap( ) ,
99- stmt :: Statement :: Mangle ( stmt :: Mangle { key : make_meta( expr:: MetaKey :: Mark ) , value : Number ( table_index ) } ) ,
102+ mangle_stmt ( make_meta( expr:: MetaKey :: Mark ) , Number ( table_id ) ) ,
100103 ACCEPT ,
101104 ]
102105 ] ) ;
103106
104- let ip_rule_exp = make_ip_rule_mark ( IpVersion :: V4 , 100 , table_index , table_index ) ;
107+ let ip_rule_exp = make_ip_rule_mark ( IpVersion :: V4 , 100 , table_id , table_id ) ;
105108 println ! ( "> ip rule = {ip_rules:?}" ) ;
106109 println ! ( "> exp = {ip_rule_exp:?}" ) ;
107110 assert ! ( ip_rules. contains( & ip_rule_exp) ) ;
108111
109112 let mut ip_routes_exp = [
110113 RouteMessageBuilder :: < Ipv4Addr > :: new ( )
111- . table_id ( table_index )
114+ . table_id ( table_id )
112115 . destination_prefix ( "172.20.0.0" . parse ( ) ?, 16 )
113- . output_interface ( dummy_index )
116+ . output_interface ( dummy_id )
114117 . gateway ( "10.128.128.1" . parse ( ) ?)
115118 . build ( ) ,
116119 RouteMessageBuilder :: < Ipv4Addr > :: new ( )
117- . table_id ( table_index )
120+ . table_id ( table_id )
118121 . destination_prefix ( "172.21.0.0" . parse ( ) ?, 16 )
119- . output_interface ( dummy_index )
122+ . output_interface ( dummy_id )
120123 . gateway ( "10.128.128.1" . parse ( ) ?)
121124 . build ( ) ,
122125 ] ;
@@ -131,54 +134,54 @@ async fn test_redirect_to_ipv6() -> anyhow::Result<()> {
131134 let ( conn, handle, _) = rtnetlink:: new_connection ( ) ?;
132135 tokio:: spawn ( conn) ;
133136
134- let table_index = 0xffff0002 ;
135- let dummy_index = create_dummy_link ( & handle, "fc64::1/64" . parse ( ) ?) . await ?;
136- let ( name, ( _g1, exabgp, chans, _g2) ) = run_kernel_test_exabgp ( table_index , [
137+ let table_id = 0xffff0002 ;
138+ let dummy_id = create_dummy_link ( & handle, "fc64::1/64" . parse ( ) ?) . await ?;
139+ let ( name, ( _g1, exabgp, chans, _g2) ) = run_kernel_test_exabgp ( table_id , [
137140 "match { destination fc00::/16; } then { redirect-to-nexthop-ietf fc64::ffff; }" ,
138141 "match { destination fc65:6565::/32; } then { redirect-to-nexthop-ietf fc64::2333; }" ,
139142 ] )
140143 . await ?;
141144
142145 print_nft_chain ( & name, & name) . await ?;
143146 print_ip_rule ( true ) . await ?;
144- print_ip_route ( true , table_index ) . await ?;
147+ print_ip_route ( true , table_id ) . await ?;
145148
146149 let nft_stmts = get_nft_stmts ( & name, & name) . await ?;
147150 let ip_rules = get_ip_rule ( & handle, IpVersion :: V6 ) . await ?;
148- let ip_routes = get_ip_route ( & handle, IpVersion :: V6 , table_index ) . await ?;
151+ let ip_routes = get_ip_route ( & handle, IpVersion :: V6 , table_id ) . await ?;
149152 close_cli ( chans) . await ;
150153 drop ( exabgp) ;
151- remove_link ( & handle, dummy_index ) . await ?;
154+ remove_link ( & handle, dummy_id ) . await ?;
152155
153156 assert_eq ! ( nft_stmts, [
154157 vec![
155158 prefix_stmt( "daddr" , "fc65:6565::/32" . parse( ) ?) . unwrap( ) ,
156- stmt :: Statement :: Mangle ( stmt :: Mangle { key : make_meta( expr:: MetaKey :: Mark ) , value : Number ( table_index ) } ) ,
159+ mangle_stmt ( make_meta( expr:: MetaKey :: Mark ) , Number ( table_id ) ) ,
157160 ACCEPT ,
158161 ] ,
159162 vec![
160163 prefix_stmt( "daddr" , "fc00::/16" . parse( ) ?) . unwrap( ) ,
161- stmt :: Statement :: Mangle ( stmt :: Mangle { key : make_meta( expr:: MetaKey :: Mark ) , value : Number ( table_index ) } ) ,
164+ mangle_stmt ( make_meta( expr:: MetaKey :: Mark ) , Number ( table_id ) ) ,
162165 ACCEPT ,
163166 ] ,
164167 ] ) ;
165168
166- let ip_rule_exp = make_ip_rule_mark ( IpVersion :: V6 , 100 , table_index , table_index ) ;
169+ let ip_rule_exp = make_ip_rule_mark ( IpVersion :: V6 , 100 , table_id , table_id ) ;
167170 println ! ( "> ip rule = {ip_rules:?}" ) ;
168171 println ! ( "> exp = {ip_rule_exp:?}" ) ;
169172 assert ! ( ip_rules. contains( & ip_rule_exp) ) ;
170173
171174 let mut ip_routes_exp = [
172175 RouteMessageBuilder :: < Ipv6Addr > :: new ( )
173- . table_id ( table_index )
176+ . table_id ( table_id )
174177 . destination_prefix ( "fc00::" . parse ( ) ?, 16 )
175- . output_interface ( dummy_index )
178+ . output_interface ( dummy_id )
176179 . gateway ( "fc64::ffff" . parse ( ) ?)
177180 . build ( ) ,
178181 RouteMessageBuilder :: < Ipv6Addr > :: new ( )
179- . table_id ( table_index )
182+ . table_id ( table_id )
180183 . destination_prefix ( "fc65:6565::" . parse ( ) ?, 32 )
181- . output_interface ( dummy_index )
184+ . output_interface ( dummy_id )
182185 . gateway ( "fc64::2333" . parse ( ) ?)
183186 . build ( ) ,
184187 ] ;
@@ -193,54 +196,54 @@ async fn test_ipv4_redirect_to_ipv6() -> anyhow::Result<()> {
193196 let ( conn, handle, _) = rtnetlink:: new_connection ( ) ?;
194197 tokio:: spawn ( conn) ;
195198
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+ let table_id = 0xffff0003 ;
200+ let dummy_id = create_dummy_link ( & handle, "fc65::1/64" . parse ( ) ?) . await ?;
201+ let ( name, ( _g1, exabgp, chans, _g2) ) = run_kernel_test_exabgp ( table_id , [
199202 "match { destination 172.17.254.192/26; } then { redirect-to-nexthop-ietf fc65::ffff; }" ,
200203 "match { destination 192.0.2.0/27; } then { redirect-to-nexthop-ietf fc65::2333; }" ,
201204 ] )
202205 . await ?;
203206
204207 print_nft_chain ( & name, & name) . await ?;
205208 print_ip_rule ( false ) . await ?;
206- print_ip_route ( false , table_index ) . await ?;
209+ print_ip_route ( false , table_id ) . await ?;
207210
208211 let nft_stmts = get_nft_stmts ( & name, & name) . await ?;
209212 let ip_rules = get_ip_rule ( & handle, IpVersion :: V4 ) . await ?;
210- let ip_routes = get_ip_route ( & handle, IpVersion :: V4 , table_index ) . await ?;
213+ let ip_routes = get_ip_route ( & handle, IpVersion :: V4 , table_id ) . await ?;
211214 close_cli ( chans) . await ;
212215 drop ( exabgp) ;
213- remove_link ( & handle, dummy_index ) . await ?;
216+ remove_link ( & handle, dummy_id ) . await ?;
214217
215218 assert_eq ! ( nft_stmts, [
216219 vec![
217220 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 ) } ) ,
221+ mangle_stmt ( make_meta( expr:: MetaKey :: Mark ) , Number ( table_id ) ) ,
219222 ACCEPT ,
220223 ] ,
221224 vec![
222225 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 ) } ) ,
226+ mangle_stmt ( make_meta( expr:: MetaKey :: Mark ) , Number ( table_id ) ) ,
224227 ACCEPT ,
225228 ] ,
226229 ] ) ;
227230
228- let ip_rule_exp = make_ip_rule_mark ( IpVersion :: V4 , 100 , table_index , table_index ) ;
231+ let ip_rule_exp = make_ip_rule_mark ( IpVersion :: V4 , 100 , table_id , table_id ) ;
229232 println ! ( "> ip rule = {ip_rules:?}" ) ;
230233 println ! ( "> exp = {ip_rule_exp:?}" ) ;
231234 assert ! ( ip_rules. contains( & ip_rule_exp) ) ;
232235
233236 let mut ip_routes_exp = [
234237 RouteMessageBuilder :: < Ipv4Addr > :: new ( )
235- . table_id ( table_index )
238+ . table_id ( table_id )
236239 . destination_prefix ( "172.17.254.192" . parse ( ) ?, 26 )
237- . output_interface ( dummy_index )
240+ . output_interface ( dummy_id )
238241 . gateway ( "fc65::ffff" . parse ( ) ?)
239242 . build ( ) ,
240243 RouteMessageBuilder :: < Ipv4Addr > :: new ( )
241- . table_id ( table_index )
244+ . table_id ( table_id )
242245 . destination_prefix ( "192.0.2.0" . parse ( ) ?, 27 )
243- . output_interface ( dummy_index )
246+ . output_interface ( dummy_id )
244247 . gateway ( "fc65::2333" . parse ( ) ?)
245248 . build ( ) ,
246249 ] ;
@@ -250,9 +253,114 @@ async fn test_ipv4_redirect_to_ipv6() -> anyhow::Result<()> {
250253 Ok ( ( ) )
251254}
252255
256+ #[ apply( test_local!) ]
257+ async fn test_unreachable_routes ( ) -> anyhow:: Result < ( ) > {
258+ let ( conn, handle, _) = rtnetlink:: new_connection ( ) ?;
259+ tokio:: spawn ( conn) ;
260+
261+ let table_id = 0xffff0004 ;
262+ let unreach_prefixes = [ "192.0.2.0/24" , "fc99::/64" ] ;
263+ let unreach_msgs: Vec < _ > = unreach_prefixes
264+ . into_iter ( )
265+ . map ( |p| {
266+ let p = p. parse :: < IpPrefix > ( ) . unwrap ( ) ;
267+ RouteMessageBuilder :: < IpAddr > :: new ( )
268+ . destination_prefix ( p. prefix ( ) , p. len ( ) )
269+ . unwrap ( )
270+ . kind ( RouteType :: Unreachable )
271+ . build ( )
272+ } )
273+ . collect ( ) ;
274+ for msg in unreach_msgs. iter ( ) . cloned ( ) {
275+ handle. route ( ) . add ( msg) . execute ( ) . await ?;
276+ }
277+
278+ let ( name, ( _g1, exabgp, chans, _g2) ) = run_kernel_test_exabgp ( table_id, [
279+ "match { destination 172.17.254.192/26; } then { redirect-to-nexthop-ietf 192.0.2.128; }" ,
280+ "match { destination 192.0.2.0/27; } then { redirect-to-nexthop-ietf fc99::2333; }" ,
281+ "match { destination fc42::/32; } then { redirect-to-nexthop-ietf fc99::6666; }" ,
282+ ] )
283+ . await ?;
284+
285+ print_nft_chain ( & name, & name) . await ?;
286+ print_ip_rule ( false ) . await ?;
287+ print_ip_rule ( true ) . await ?;
288+ print_ip_route ( false , 254 ) . await ?;
289+ print_ip_route ( true , 254 ) . await ?;
290+ print_ip_route ( false , table_id) . await ?;
291+ print_ip_route ( true , table_id) . await ?;
292+
293+ let nft_stmts = get_nft_stmts ( & name, & name) . await ?;
294+ let ip_rules = [
295+ get_ip_rule ( & handle, IpVersion :: V4 ) . await ?,
296+ get_ip_rule ( & handle, IpVersion :: V6 ) . await ?,
297+ ] ;
298+ let ip_routes = [
299+ get_ip_route ( & handle, IpVersion :: V4 , table_id) . await ?,
300+ get_ip_route ( & handle, IpVersion :: V6 , table_id) . await ?,
301+ ] ;
302+ let ip_rules: Vec < _ > = ip_rules. into_iter ( ) . flatten ( ) . collect ( ) ;
303+ let ip_routes: Vec < _ > = ip_routes. into_iter ( ) . flatten ( ) . collect ( ) ;
304+ close_cli ( chans) . await ;
305+ drop ( exabgp) ;
306+ for msg in unreach_msgs {
307+ handle. route ( ) . del ( msg) . execute ( ) . await ?;
308+ }
309+
310+ assert_eq ! ( nft_stmts, [
311+ vec![
312+ prefix_stmt( "daddr" , "192.0.2.0/27" . parse( ) ?) . unwrap( ) ,
313+ mangle_stmt( make_meta( expr:: MetaKey :: Mark ) , Number ( table_id) ) ,
314+ ACCEPT ,
315+ ] ,
316+ vec![
317+ prefix_stmt( "daddr" , "172.17.254.192/26" . parse( ) ?) . unwrap( ) ,
318+ mangle_stmt( make_meta( expr:: MetaKey :: Mark ) , Number ( table_id) ) ,
319+ ACCEPT ,
320+ ] ,
321+ vec![
322+ prefix_stmt( "daddr" , "fc42::/32" . parse( ) ?) . unwrap( ) ,
323+ mangle_stmt( make_meta( expr:: MetaKey :: Mark ) , Number ( table_id) ) ,
324+ ACCEPT ,
325+ ] ,
326+ ] ) ;
327+
328+ let ip_rules_exp: Vec < _ > = [ IpVersion :: V4 , IpVersion :: V6 ]
329+ . into_iter ( )
330+ . map ( |x| make_ip_rule_mark ( x, 100 , table_id, table_id) )
331+ . collect ( ) ;
332+ println ! ( "> ip rule = {ip_rules:?}" ) ;
333+ println ! ( "> exp = {ip_rules_exp:?}" ) ;
334+ assert ! ( ip_rules_exp. iter( ) . all( |x| ip_rules. contains( x) ) ) ;
335+
336+ let ip_routes_exp = [ "172.17.254.192/26" , "192.0.2.0/27" , "fc42::/32" ] ;
337+ let mut ip_routes_exp: Vec < _ > = ip_routes_exp
338+ . into_iter ( )
339+ . map ( |x| {
340+ let x = x. parse :: < IpPrefix > ( ) . unwrap ( ) ;
341+ let mut msg = RouteMessageBuilder :: < IpAddr > :: new ( )
342+ . destination_prefix ( x. prefix ( ) , x. len ( ) )
343+ . unwrap ( )
344+ . kind ( RouteType :: Unreachable )
345+ . table_id ( table_id)
346+ . build ( ) ;
347+ if x. is_ipv6 ( ) {
348+ msg. attributes . push ( RouteAttribute :: Oif ( 1 ) ) ;
349+ }
350+ msg
351+ } )
352+ . collect ( ) ;
353+ ip_routes_exp. iter_mut ( ) . for_each ( route_msg_normalize) ;
354+ assert_eq ! ( ip_routes, ip_routes_exp) ;
355+
356+ Ok ( ( ) )
357+ }
358+
253359// TODO: test IPv6 offset
360+ // TODO: test prefix overlap
361+ // TODO: test rtnetlink listen to network changes
254362
255- async fn run_kernel_test (
363+ async fn run_kernel_test_bird (
256364 init_table_id : u32 ,
257365 flows : impl IntoIterator < Item = & str > ,
258366) -> anyhow:: Result < ( String , CliGuard ) > {
0 commit comments