22
33use std:: collections:: HashMap ;
44
5+ use futures_util:: TryStreamExt ;
56use iproute_rs:: { CliError , parse_mac_str} ;
67use rtnetlink:: {
78 LinkDummy , LinkMessageBuilder ,
@@ -37,33 +38,36 @@ impl LinkAddCommand {
3738
3839 let base_conf = LinkBaseConf :: parse ( opts) ?;
3940
41+ let ( connection, handle, _) = rtnetlink:: new_connection ( ) ?;
42+ tokio:: spawn ( connection) ;
43+
4044 let nl_msg = match base_conf. iface_type {
4145 InfoKind :: Dummy => {
4246 base_conf. apply ( LinkDummy :: new ( & base_conf. name ) ) ?
4347 }
48+ InfoKind :: Vlan => {
49+ base_conf. apply ( base_conf. apply_vlan ( & handle) . await ?) ?
50+ }
4451 t => {
4552 return Err ( CliError :: from ( format ! (
4653 "Unsupported device type: {t}"
4754 ) ) ) ;
4855 }
4956 } ;
5057
51- let ( connection, handle, _) = rtnetlink:: new_connection ( ) ?;
52-
53- tokio:: spawn ( connection) ;
5458 handle. link ( ) . add ( nl_msg) . execute ( ) . await ?;
5559
5660 Ok ( vec ! [ ] )
5761 }
5862}
5963
6064#[ derive( Debug ) ]
61- struct LinkBaseConf {
62- // link: Option<String>,
63- name : String ,
64- address : Option < String > ,
65- iface_type : InfoKind ,
66- _iface_specific : Vec < String > ,
65+ pub ( crate ) struct LinkBaseConf {
66+ pub ( crate ) link : Option < String > ,
67+ pub ( crate ) name : String ,
68+ pub ( crate ) address : Option < String > ,
69+ pub ( crate ) iface_type : InfoKind ,
70+ pub ( crate ) iface_specific : Vec < String > ,
6771}
6872
6973impl LinkBaseConf {
@@ -77,6 +81,19 @@ impl LinkBaseConf {
7781 Ok ( builder. build ( ) )
7882 }
7983
84+ pub ( crate ) async fn get_ifindex_by_name (
85+ & self ,
86+ handle : & rtnetlink:: Handle ,
87+ name : & str ,
88+ ) -> Result < u32 , CliError > {
89+ let mut links =
90+ handle. link ( ) . get ( ) . match_name ( name. to_string ( ) ) . execute ( ) ;
91+ let link = links. try_next ( ) . await ?. ok_or_else ( || {
92+ CliError :: from ( format ! ( "Device \" {name}\" does not exist" ) )
93+ } ) ?;
94+ Ok ( link. header . index )
95+ }
96+
8097 fn parse ( args : Vec < String > ) -> Result < Self , CliError > {
8198 if let Some ( type_index) =
8299 args. as_slice ( ) . iter ( ) . position ( |a| a. as_str ( ) == "type" )
@@ -112,17 +129,19 @@ impl LinkBaseConf {
112129
113130 let address =
114131 base_args_dict. remove ( "address" ) . map ( |s| s. to_string ( ) ) ;
132+ let link = base_args_dict. remove ( "link" ) . map ( |s| s. to_string ( ) ) ;
115133
116- let _iface_specific = if args. len ( ) > type_index + 1 {
134+ let iface_specific = if args. len ( ) > type_index + 1 {
117135 args[ type_index + 2 ..] . to_vec ( )
118136 } else {
119137 Vec :: new ( )
120138 } ;
121139 Ok ( Self {
122140 name,
123141 address,
142+ link,
124143 iface_type,
125- _iface_specific ,
144+ iface_specific ,
126145 } )
127146 } else {
128147 Err ( CliError :: from (
@@ -131,3 +150,90 @@ impl LinkBaseConf {
131150 }
132151 }
133152}
153+
154+ #[ cfg( test) ]
155+ mod tests {
156+ use super :: * ;
157+
158+ fn args ( input : & [ & str ] ) -> Vec < String > {
159+ input. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( )
160+ }
161+
162+ #[ test]
163+ fn parse_basic_dummy ( ) {
164+ let conf =
165+ LinkBaseConf :: parse ( args ( & [ "eth0" , "type" , "dummy" ] ) ) . unwrap ( ) ;
166+ assert_eq ! ( conf. name, "eth0" ) ;
167+ assert_eq ! ( conf. iface_type, InfoKind :: Dummy ) ;
168+ assert ! ( conf. address. is_none( ) ) ;
169+ assert ! ( conf. link. is_none( ) ) ;
170+ assert ! ( conf. iface_specific. is_empty( ) ) ;
171+ }
172+
173+ #[ test]
174+ fn parse_with_address ( ) {
175+ let conf = LinkBaseConf :: parse ( args ( & [
176+ "name" ,
177+ "eth0" ,
178+ "address" ,
179+ "00:11:22:33:44:55" ,
180+ "type" ,
181+ "dummy" ,
182+ ] ) )
183+ . unwrap ( ) ;
184+ assert_eq ! ( conf. name, "eth0" ) ;
185+ assert_eq ! ( conf. address. as_deref( ) , Some ( "00:11:22:33:44:55" ) ) ;
186+ assert_eq ! ( conf. iface_type, InfoKind :: Dummy ) ;
187+ }
188+
189+ #[ test]
190+ fn parse_with_link ( ) {
191+ let conf = LinkBaseConf :: parse ( args ( & [
192+ "link" , "eth0" , "name" , "eth0.1" , "type" , "vlan" , "id" , "100" ,
193+ ] ) )
194+ . unwrap ( ) ;
195+ assert_eq ! ( conf. name, "eth0.1" ) ;
196+ assert_eq ! ( conf. link. as_deref( ) , Some ( "eth0" ) ) ;
197+ assert_eq ! ( conf. iface_type, InfoKind :: Vlan ) ;
198+ assert_eq ! ( conf. iface_specific, vec![ "id" , "100" ] ) ;
199+ }
200+
201+ #[ test]
202+ fn parse_link_no_name_fails ( ) {
203+ let err = LinkBaseConf :: parse ( args ( & [ "link" , "eth0" , "type" , "dummy" ] ) )
204+ . unwrap_err ( ) ;
205+ assert ! ( err. msg. contains( "name" ) ) ;
206+ }
207+
208+ #[ test]
209+ fn parse_missing_type ( ) {
210+ let err = LinkBaseConf :: parse ( args ( & [ "eth0" ] ) ) . unwrap_err ( ) ;
211+ assert ! ( err. msg. contains( "type" ) ) ;
212+ }
213+
214+ #[ test]
215+ fn parse_type_at_end ( ) {
216+ let err = LinkBaseConf :: parse ( args ( & [ "eth0" , "type" ] ) ) . unwrap_err ( ) ;
217+ assert ! ( err. msg. contains( "type" ) ) ;
218+ }
219+
220+ #[ test]
221+ fn parse_empty_args ( ) {
222+ let err = LinkBaseConf :: parse ( args ( & [ ] ) ) . unwrap_err ( ) ;
223+ assert ! ( err. msg. contains( "type" ) ) ;
224+ }
225+
226+ #[ test]
227+ fn parse_no_name ( ) {
228+ let err = LinkBaseConf :: parse ( args ( & [ "type" , "dummy" ] ) ) . unwrap_err ( ) ;
229+ assert ! ( err. msg. contains( "name" ) ) ;
230+ }
231+
232+ #[ test]
233+ fn parse_odd_args_without_link ( ) {
234+ let conf =
235+ LinkBaseConf :: parse ( args ( & [ "foo" , "bar" , "baz" , "type" , "dummy" ] ) )
236+ . unwrap ( ) ;
237+ assert_eq ! ( conf. name, "foo" ) ;
238+ }
239+ }
0 commit comments