@@ -11,24 +11,38 @@ pub fn linux_find_routers(command: &dyn FindRoutersCommand) -> Result<Vec<IpAddr
1111 Ok ( stdout) => stdout,
1212 Err ( stderr) => return Err ( AutomapError :: ProtocolError ( stderr) ) ,
1313 } ;
14- let addresses = output
14+ let init: Result < Vec < IpAddr > , AutomapError > = Ok ( vec ! [ ] ) ;
15+ output
1516 . split ( '\n' )
16- . map ( |line| {
17- line. split ( ' ' )
18- . filter ( |piece| !piece. is_empty ( ) )
19- . collect :: < Vec < & str > > ( )
17+ . take_while ( |line| line. trim_start ( ) . starts_with ( "default " ) )
18+ . fold ( init, |acc, line| match acc {
19+ Ok ( mut ip_addr_vec) => {
20+ let ip_str: String = line
21+ . chars ( )
22+ . skip_while ( |char| !char. is_numeric ( ) )
23+ . take_while ( |char| !char. is_whitespace ( ) )
24+ . collect ( ) ;
25+
26+ match IpAddr :: from_str ( & ip_str) {
27+ Ok ( ip_addr) => {
28+ ip_addr_vec. push ( ip_addr) ;
29+ Ok ( ip_addr_vec)
30+ }
31+ Err ( e) => Err ( AutomapError :: FindRouterError ( format ! (
32+ "Failed to parse an IP from \" ip route\" : {:?} Line: {}" ,
33+ e, line
34+ ) ) ) ,
35+ }
36+ }
37+ Err ( e) => Err ( e) ,
2038 } )
21- . filter ( |line_vec| ( line_vec. len ( ) >= 4 ) && ( line_vec[ 3 ] == "UG" ) )
22- . map ( |line_vec| IpAddr :: from_str ( line_vec[ 1 ] ) . expect ( "Bad syntax from route -n" ) )
23- . collect :: < Vec < IpAddr > > ( ) ;
24- Ok ( addresses)
2539}
2640
2741pub struct LinuxFindRoutersCommand { }
2842
2943impl FindRoutersCommand for LinuxFindRoutersCommand {
3044 fn execute ( & self ) -> Result < String , String > {
31- self . execute_command ( "route -n " )
45+ self . execute_command ( "ip route " )
3246 }
3347}
3448
@@ -48,19 +62,18 @@ impl LinuxFindRoutersCommand {
4862mod tests {
4963 use super :: * ;
5064 use crate :: mocks:: FindRoutersCommandMock ;
65+ use regex:: Regex ;
5166 use std:: str:: FromStr ;
5267
5368 #[ test]
5469 fn find_routers_works_when_there_is_a_router_to_find ( ) {
55- let route_n_output = "Kernel IP routing table
56- Destination Gateway Genmask Flags Metric Ref Use Iface
57- 0.0.0.0 192.168.0.1 0.0.0.0 UG 100 0 0 enp4s0
58- 169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 enp4s0
59- 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
60- 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-2c4b4b668d71
61- 192.168.0.0 0.0.0.0 255.255.255.0 U 100 0 0 enp4s0
62- " ;
63- let find_routers_command = FindRoutersCommandMock :: new ( Ok ( & route_n_output) ) ;
70+ let ip_route_output = "\
71+ default via 192.168.0.1 dev enp4s0 proto dhcp src 192.168.0.100 metric 100\n \
72+ 169.254.0.0/16 dev enp4s0 scope link metric 1000\n \
73+ 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown\n \
74+ 172.18.0.0/16 dev br-85f38f356a58 proto kernel scope link src 172.18.0.1 linkdown\n \
75+ 192.168.0.0/24 dev enp4s0 proto kernel scope link src 192.168.0.100 metric 100";
76+ let find_routers_command = FindRoutersCommandMock :: new ( Ok ( & ip_route_output) ) ;
6477
6578 let result = linux_find_routers ( & find_routers_command) . unwrap ( ) ;
6679
@@ -69,16 +82,14 @@ Destination Gateway Genmask Flags Metric Ref Use Iface
6982
7083 #[ test]
7184 fn find_routers_works_when_there_are_multiple_routers_to_find ( ) {
72- let route_n_output = "Kernel IP routing table
73- Destination Gateway Genmask Flags Metric Ref Use Iface
74- 0.0.0.0 192.168.0.1 0.0.0.0 UG 100 0 0 enp4s0
75- 0.0.0.0 192.168.0.2 0.0.0.0 UG 100 0 0 enp4s0
76- 169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 enp4s0
77- 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
78- 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-2c4b4b668d71
79- 192.168.0.0 0.0.0.0 255.255.255.0 U 100 0 0 enp4s0
80- " ;
81- let find_routers_command = FindRoutersCommandMock :: new ( Ok ( & route_n_output) ) ;
85+ let ip_route_output = "\
86+ default via 192.168.0.1 dev enp0s8 proto dhcp metric 101\n \
87+ default via 192.168.0.2 dev enp0s3 proto dhcp metric 102\n \
88+ 10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15 metric 102\n \
89+ 169.254.0.0/16 dev enp0s3 scope link metric 1000\n \
90+ 192.168.1.0/24 dev enp0s8 proto kernel scope link src 192.168.1.64 metric 101\n \
91+ 192.168.1.1 via 10.0.2.15 dev enp0s3";
92+ let find_routers_command = FindRoutersCommandMock :: new ( Ok ( & ip_route_output) ) ;
8293
8394 let result = linux_find_routers ( & find_routers_command) . unwrap ( ) ;
8495
@@ -91,16 +102,30 @@ Destination Gateway Genmask Flags Metric Ref Use Iface
91102 )
92103 }
93104
105+ #[ test]
106+ fn find_routers_supports_ip_address_of_ipv6 ( ) {
107+ let route_n_output = "\
108+ default via 2001:1:2:3:4:5:6:7 dev enX0 proto kernel metric 256 pref medium\n \
109+ fe80::/64 dev docker0 proto kernel metric 256 pref medium";
110+
111+ let find_routers_command = FindRoutersCommandMock :: new ( Ok ( & route_n_output) ) ;
112+
113+ let result = linux_find_routers ( & find_routers_command) ;
114+
115+ assert_eq ! (
116+ result,
117+ Ok ( vec![ IpAddr :: from_str( "2001:1:2:3:4:5:6:7" ) . unwrap( ) ] )
118+ )
119+ }
120+
94121 #[ test]
95122 fn find_routers_works_when_there_is_no_router_to_find ( ) {
96- let route_n_output = "Kernel IP routing table
97- Destination Gateway Genmask Flags Metric Ref Use Iface
98- 0.0.0.0 192.168.0.1 0.0.0.0 U 100 0 0 enp4s0
99- 169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 enp4s0
100- 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
101- 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-2c4b4b668d71
102- 192.168.0.0 0.0.0.0 255.255.255.0 U 100 0 0 enp4s0
103- " ;
123+ let route_n_output = "\
124+ 10.1.0.0/16 dev eth0 proto kernel scope link src 10.1.0.84 metric 100\n \
125+ 0.1.0.1 dev eth0 proto dhcp scope link src 10.1.0.84 metric 100\n \
126+ 168.63.129.16 via 10.1.0.1 dev eth0 proto dhcp src 10.1.0.84 metric 100\n \
127+ 169.254.169.254 via 10.1.0.1 dev eth0 proto dhcp src 10.1.0.84 metric 100\n \
128+ 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown";
104129 let find_routers_command = FindRoutersCommandMock :: new ( Ok ( & route_n_output) ) ;
105130
106131 let result = linux_find_routers ( & find_routers_command) . unwrap ( ) ;
@@ -120,55 +145,127 @@ Destination Gateway Genmask Flags Metric Ref Use Iface
120145 )
121146 }
122147
148+ #[ test]
149+ fn find_routers_returns_error_if_ip_addresses_can_not_be_parsed ( ) {
150+ let route_n_output = "\
151+ default via 192.168.0.1 dev enp4s0 proto dhcp src 192.168.0.100 metric 100\n \
152+ default via 192.168.0 dev enp0s3 proto dhcp metric 102\n \
153+ 169.254.0.0/16 dev enp4s0 scope link metric 1000";
154+ let find_routers_command = FindRoutersCommandMock :: new ( Ok ( & route_n_output) ) ;
155+
156+ let result = linux_find_routers ( & find_routers_command) ;
157+
158+ eprintln ! ( "{:?}" , result) ;
159+
160+ assert_eq ! (
161+ result,
162+ Err ( AutomapError :: FindRouterError (
163+ "Failed to parse an IP from \" ip route\" : AddrParseError(Ip) Line: default via 192.168.0 dev enp0s3 proto dhcp metric 102" . to_string( )
164+ ) )
165+ )
166+ }
167+
123168 #[ test]
124169 fn find_routers_command_produces_output_that_looks_right ( ) {
125170 let subject = LinuxFindRoutersCommand :: new ( ) ;
126171
127172 let result = subject. execute ( ) . unwrap ( ) ;
128173
129- let lines = result. split ( '\n' ) . collect :: < Vec < & str > > ( ) ;
130- assert_eq ! ( "Kernel IP routing table" , lines[ 0 ] ) ;
131- let headings = lines[ 1 ]
132- . split ( ' ' )
133- . filter ( |s| s. len ( ) > 0 )
134- . collect :: < Vec < & str > > ( ) ;
135- assert_eq ! (
136- headings,
137- vec![
138- "Destination" ,
139- "Gateway" ,
140- "Genmask" ,
141- "Flags" ,
142- "Metric" ,
143- "Ref" ,
144- "Use" ,
145- "Iface" ,
146- ]
147- ) ;
148- for line in & lines[ 3 ..] {
149- if line. len ( ) == 0 {
150- continue ;
151- }
152- let columns = line
153- . split ( ' ' )
154- . filter ( |s| s. len ( ) > 0 )
155- . collect :: < Vec < & str > > ( ) ;
156- for idx in 0 ..3 {
157- if IpAddr :: from_str ( columns[ idx] ) . is_err ( ) {
158- panic ! (
159- "Column {} should have been an IP address but wasn't: {}" ,
160- idx, columns[ idx]
161- )
162- }
163- }
164- for idx in 4 ..7 {
165- if columns[ idx] . parse :: < u64 > ( ) . is_err ( ) {
166- panic ! (
167- "Column {} should have been numeric but wasn't: {}" ,
168- idx, columns[ idx]
169- )
170- }
171- }
174+ let mut lines = result. split ( '\n' ) . collect :: < Vec < & str > > ( ) ;
175+ let len = lines. len ( ) ;
176+ if lines[ len - 1 ] . is_empty ( ) {
177+ lines. remove ( len - 1 ) ;
172178 }
179+ let reg = ip_route_regex ( ) ;
180+ lines. iter ( ) . for_each ( |line| {
181+ assert ! ( reg. is_match( line) , "Lines: {:?} line: {}" , lines, line) ;
182+ } ) ;
183+ }
184+
185+ fn ip_route_regex ( ) -> Regex {
186+ let reg_for_ip = r"((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}" ;
187+ Regex :: new ( & format ! (
188+ r#"^(default via )?{}(/\d+)?\s(dev|via)(\s.+){{3,}}"# ,
189+ reg_for_ip
190+ ) )
191+ . unwrap ( )
192+ }
193+
194+ #[ test]
195+ fn reg_for_ip_route_command_output_good_and_bad_ip ( ) {
196+ let route_n_output = vec ! [
197+ (
198+ "default via 0.1.0.1 dev eth0 proto dhcp scope link src 10.1.0.84 metric 100" ,
199+ true ,
200+ "Example of good IPv4" ,
201+ ) ,
202+ (
203+ "10.1.0.0/16 dev eth0 proto kernel scope link src 10.1.0.84 metric 100" ,
204+ true ,
205+ "Example of good IPv4" ,
206+ ) ,
207+ (
208+ "168.63.129.16 via 10.1.0.1 dev eth0 proto dhcp src 10.1.0.84 metric 100" ,
209+ true ,
210+ "Example of good IPv4" ,
211+ ) ,
212+ (
213+ "169.254.169.254 via 10.1.0.1 dev eth0 proto dhcp src 10.1.0.84 metric 100" ,
214+ true ,
215+ "Example of good IPv4" ,
216+ ) ,
217+ (
218+ "172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown" ,
219+ true ,
220+ "Example of good IPv4" ,
221+ ) ,
222+ (
223+ "10.1.0.0/16 dev eth0 proto kernel scope link src 10.1.0.84 metric 100" ,
224+ true ,
225+ "Example of good IPv4" ,
226+ ) ,
227+ (
228+ "0.1.255.1 dev eth0 proto dhcp" ,
229+ true ,
230+ "Example of good IPv4" ,
231+ ) ,
232+ (
233+ "0.1.0 dev eth0 proto dhcp scope link src 10.1.0.84 metric 100" ,
234+ false ,
235+ "IPv4 address has only three elements" ,
236+ ) ,
237+ (
238+ "0.1.256.1 dev eth0 proto dhcp" ,
239+ false ,
240+ "IPv4 address has an element greater than 255" ,
241+ ) ,
242+ (
243+ "0.1.b.1 dev eth0 proto dhcp" ,
244+ false ,
245+ "IPv4 address contains a letter" ,
246+ ) ,
247+ (
248+ "0.1.0.1/ dev eth0 proto dhcp" ,
249+ false ,
250+ "IPv4 Subnet is missing a netmask" ,
251+ ) ,
252+ (
253+ "2001:0db8:0000:0000:0000:ff00:0042:8329 dev eth0 proto dhcp" ,
254+ false ,
255+ "Regex does not support IPV6" ,
256+ ) ,
257+ ] ;
258+
259+ let regex = ip_route_regex ( ) ;
260+
261+ route_n_output. iter ( ) . for_each ( |line| {
262+ assert_eq ! (
263+ regex. is_match( line. 0 ) ,
264+ line. 1 ,
265+ "{}: Line: {}" ,
266+ line. 2 ,
267+ line. 0
268+ ) ;
269+ } ) ;
173270 }
174271}
0 commit comments