-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patharc.rs
258 lines (228 loc) · 9.31 KB
/
arc.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Provides the command "arc" for crosh which can run various ARC utilities and tools.
use std::io::Write;
use std::path::Path;
use std::process;
use crate::dispatcher::{self, wait_for_result, Arguments, Command, Dispatcher};
const HELP: &str = r#"Usage: arc
[ ping [ NETWORK ] [ <ip address> | <hostname> ] |
http [ NETWORK ] <url> |
dns [ NETWORK ] <domain> |
proxy <url> |
list [ networks ] |
stats [ sockets | traffic | idle ]
]
where NETWORK := [ wifi | eth | ethernet | cell | cellular | vpn ]
If NETWORK is not specified, the default network is used.
ping: check the reachability of a host or IP address.
http: do a GET request to an URL and print the response header.
dns: perform a DNS lookup of a domain name.
proxy: resolve the current proxy configuration for a given URL.
list networks: show properties of all networks connected in Android.
stats sockets: show TCP connect and DNS statistics by Android Apps.
stats traffic: show traffic packet statistics by Android Apps.
stats idle : show system idle stats of Android.
"#;
type CommandRunner = dyn Fn(&[&str]) -> Result<(), dispatcher::Error>;
// We use adb shell for executing networking tools through dumpsys wifi.
// It is not possible to use android-sh because it has a different selinux context.
const ADB: &str = "/usr/bin/adb";
fn run_adb_command(args: &[&str]) -> Result<(), dispatcher::Error> {
process::Command::new(ADB).args(args).spawn().map_or(
Err(dispatcher::Error::CommandReturnedError),
wait_for_result,
)
}
pub fn register(dispatcher: &mut Dispatcher) {
// Only register the arc command if adb is present.
if !Path::new(ADB).exists() {
return;
}
dispatcher.register_command(
Command::new("arc".to_string(), "".to_string(), "".to_string())
.set_command_callback(Some(arc_command_callback))
.set_help_callback(arc_help),
);
}
fn arc_help(_cmd: &Command, w: &mut dyn Write, _level: usize) {
w.write_all(HELP.as_bytes()).unwrap();
w.flush().unwrap();
}
// Wraps |execute_arc_command| to register it to crosh dispatcher. This facilitates testing.
fn arc_command_callback(_cmd: &Command, _args: &Arguments) -> Result<(), dispatcher::Error> {
// Convert the slice of String to a vec of str for pattern matching.
let args: Vec<&str> = _args.get_args().iter().map(String::as_str).collect();
execute_arc_command(&args, &run_adb_command)
}
fn execute_arc_command(
args: &[&str],
adb_command_runner: &CommandRunner,
) -> Result<(), dispatcher::Error> {
match args {
[] => invalid_argument("no command"),
// dumpsys wifi tools reach [NETWORK] [<ip addr> | <hosname>]
["ping"] => invalid_argument("missing IP address or hostname to ping"),
["ping", network, dst, ..] => {
run_arc_networking_tool(adb_command_runner, "reach", dst, Some(network))
}
["ping", dst, ..] => run_arc_networking_tool(adb_command_runner, "reach", dst, None),
// dumpsys wifi tools http [NETWORK] <url>
["http"] => invalid_argument("missing url to connect to"),
["http", network, url, ..] => {
run_arc_networking_tool(adb_command_runner, "http", url, Some(network))
}
["http", url, ..] => run_arc_networking_tool(adb_command_runner, "http", url, None),
// dumpsys wifi tools dns [NETWORK] <domain>
["dns"] => invalid_argument("missing domain name to resolve"),
["dns", network, domain, ..] => {
run_arc_networking_tool(adb_command_runner, "dns", domain, Some(network))
}
["dns", domain, ..] => run_arc_networking_tool(adb_command_runner, "dns", domain, None),
// dumpsys wifi tools proxy <url>. Proxy resolution is always with the default network in
// ARC.
["proxy"] => invalid_argument("missing url to resolve"),
["proxy", url, ..] => run_arc_networking_tool(adb_command_runner, "proxy", url, None),
// Prints Android properties of all networks currently connected in ARC. This output
// contains potential PIIs (IP addresses) and should not be stored or collected without
// additional scrubbing.
["list", "networks"] => adb_command_runner(&["shell", "dumpsys", "wifi", "networks"]),
// Prints the number of TCP connect() calls and DNS queries initiated by Android Apps.
// This output does not contain any PII.
["stats", "sockets"] => adb_command_runner(&["shell", "dumpsys", "wifi", "sockets"]),
// Prints tx and rx packets and bytes counter statistics for traffic initiated by Android
// Apps. This output does not contain any PII.
["stats", "traffic"] => adb_command_runner(&["shell", "dumpsys", "wifi", "traffic"]),
// Prints the Android idle settings and states. This output does not contain any PII.
["stats", "idle"] => adb_command_runner(&["shell", "dumpsys", "deviceidle"]),
[other, ..] => invalid_argument(other),
}
}
fn run_arc_networking_tool(
adb_command_runner: &CommandRunner,
tool: &str,
arg: &str,
network: Option<&str>,
) -> Result<(), dispatcher::Error> {
let mut adb_args = vec!["shell", "dumpsys", "wifi", "tools", tool];
match network {
None => (),
Some("wifi") | Some("eth") | Some("ethernet") | Some("cell") | Some("cellular")
| Some("vpn") => adb_args.push(network.unwrap()),
Some(n) => return invalid_argument(n),
};
adb_args.push(arg);
adb_command_runner(&adb_args)
}
fn invalid_argument(msg: &str) -> Result<(), dispatcher::Error> {
Err(dispatcher::Error::CommandInvalidArguments(msg.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
fn fake_adb_command(_args: &[&str]) -> Result<(), dispatcher::Error> {
Ok(())
}
fn expect_adb_command(expected_command: &str) -> Box<CommandRunner> {
let c = expected_command.to_string();
Box::new(move |args| -> Result<(), dispatcher::Error> {
let command = args.join(" ");
if c == command {
Ok(())
} else {
invalid_argument(&command)
}
})
}
#[test]
fn test_invalid_commands() {
let invalid_commands = [
"",
"wopfhjf",
"not a command",
"ping ping ping",
"ping",
"http",
"dns",
"proxy",
"ping invalid 1.1.1.1",
];
for &command in &invalid_commands {
let args: Vec<&str> = command.split(' ').collect();
let r = execute_arc_command(&args, &fake_adb_command);
assert!(r.is_err(), "\"{}\" should not be a valid command", command);
}
}
#[test]
fn test_valid_commands() {
let valid_commands = [
"ping 8.8.8.8",
"ping eth 1.1.1.1",
"ping wifi ipv6.google.com",
"http https://google.com",
"http cell https://google.com",
"dns play.google.com",
"dns vpn portal.corp.com",
"proxy http://google.com",
];
for &command in &valid_commands {
let args: Vec<&str> = command.split(' ').collect();
let r = execute_arc_command(&args, &fake_adb_command);
assert!(
r.is_ok(),
"\"{}\" should be a valid command, but got: {}",
command,
r.unwrap_err()
);
}
}
#[test]
fn test_arc_networking_commands() {
let commands = [
("ping 8.8.8.8", "shell dumpsys wifi tools reach 8.8.8.8"),
(
"ping eth 1.1.1.1 extra1",
"shell dumpsys wifi tools reach eth 1.1.1.1",
),
(
"ping wifi ipv6.google.com",
"shell dumpsys wifi tools reach wifi ipv6.google.com",
),
(
"http https://google.com",
"shell dumpsys wifi tools http https://google.com",
),
(
"http cell https://google.com",
"shell dumpsys wifi tools http cell https://google.com",
),
(
"dns play.google.com",
"shell dumpsys wifi tools dns play.google.com",
),
(
"dns vpn portal.corp.com",
"shell dumpsys wifi tools dns vpn portal.corp.com",
),
(
"proxy http://google.com",
"shell dumpsys wifi tools proxy http://google.com",
),
("list networks", "shell dumpsys wifi networks"),
("stats sockets", "shell dumpsys wifi sockets"),
("stats traffic", "shell dumpsys wifi traffic"),
];
for (arc_command, adb_command) in &commands {
let args: Vec<&str> = arc_command.split(' ').collect();
let fake_command_runner = expect_adb_command(adb_command);
let r = execute_arc_command(&args, &fake_command_runner);
assert!(
r.is_ok(),
"expected \"{}\", but got: {}",
adb_command,
r.unwrap_err()
);
}
}
}