698 lines
28 KiB
Java
698 lines
28 KiB
Java
package net.geedge.api.util;
|
||
|
||
import cn.hutool.core.codec.Base32Codec;
|
||
import cn.hutool.core.thread.NamedThreadFactory;
|
||
import cn.hutool.log.Log;
|
||
import net.geedge.api.entity.EnvApiYml;
|
||
import net.geedge.common.APIException;
|
||
import net.geedge.common.Constant;
|
||
import net.geedge.common.RCode;
|
||
import net.geedge.common.T;
|
||
|
||
import java.io.File;
|
||
import java.nio.file.Paths;
|
||
import java.util.*;
|
||
import java.util.concurrent.*;
|
||
import java.util.regex.Matcher;
|
||
import java.util.regex.Pattern;
|
||
|
||
public class AdbUtil {
|
||
|
||
private static final Log log = Log.get();
|
||
|
||
private static AdbUtil instance;
|
||
|
||
private static String DEFAULT_DROIDVNC_NG_APK_PATH = "./lib/droidvnc-np-2.6.0.apk";
|
||
private static String DEFAULT_DROIDVNC_NG_DEFAULTS_JSON_PATH = "./lib/droidvnc-np-defaults.json";
|
||
|
||
private String serial;
|
||
private String host;
|
||
private Integer port;
|
||
|
||
private ExecutorService threadPool;
|
||
|
||
public String getSerial() {
|
||
return T.StrUtil.isNotEmpty(this.serial) ? serial : String.format("%s:%s", this.host, this.port);
|
||
}
|
||
|
||
public record CommandResult(Integer exitCode, String output) {
|
||
}
|
||
|
||
private AdbUtil(EnvApiYml.Adb adb) {
|
||
this.serial = T.StrUtil.emptyToDefault(adb.getSerial(), "");
|
||
this.host = adb.getHost();
|
||
this.port = adb.getPort();
|
||
// adb connect
|
||
this.connect();
|
||
// init
|
||
this.init();
|
||
}
|
||
|
||
public static AdbUtil getInstance(EnvApiYml.Adb connInfo) {
|
||
if (instance == null) {
|
||
synchronized (AdbUtil.class) {
|
||
if (instance == null) {
|
||
instance = new AdbUtil(connInfo);
|
||
}
|
||
}
|
||
}
|
||
return instance;
|
||
}
|
||
|
||
/**
|
||
* connect
|
||
*/
|
||
private void connect() {
|
||
if (T.StrUtil.isNotEmpty(this.serial)) {
|
||
// local
|
||
AdbDevice adbDevice = this.getAdbDevice();
|
||
log.info("[connect] [result: {}]", T.JSONUtil.toJsonStr(adbDevice));
|
||
if (null == adbDevice || !adbDevice.isAvailable()) {
|
||
log.error("[device is not available, program exit]");
|
||
Runtime.getRuntime().halt(1);
|
||
}
|
||
} else {
|
||
// remote
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.buildConnectCommand(this.host, this.port)
|
||
.build());
|
||
log.info("[connect] [result: {}]", result);
|
||
if (!T.StrUtil.contains(result, "connected")) {
|
||
log.error("[connect error, program exit]");
|
||
Runtime.getRuntime().halt(1);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* init
|
||
* su root
|
||
* install droidVNC NG
|
||
*/
|
||
private void init() {
|
||
// adb root
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildRootCommand()
|
||
.build()
|
||
);
|
||
log.info("[init] [adb root] [result: {}]", result);
|
||
|
||
// install droidVNC NG
|
||
CommandResult installed = this.install(DEFAULT_DROIDVNC_NG_APK_PATH, true, true);
|
||
log.info("[init] [install droidVNC NG] [result: {}]", installed);
|
||
|
||
// 上传默认配置
|
||
this.execShellCommand("shell mkdir -p /storage/emulated/0/Android/data/net.christianbeier.droidvnc_ng/files");
|
||
this.push(DEFAULT_DROIDVNC_NG_DEFAULTS_JSON_PATH, "/storage/emulated/0/Android/data/net.christianbeier.droidvnc_ng/files/defaults.json");
|
||
|
||
// 无障碍权限
|
||
this.execShellCommand("shell settings put secure enabled_accessibility_services net.christianbeier.droidvnc_ng/.InputService:$(settings get secure enabled_accessibility_services)");
|
||
// 存储空间权限
|
||
this.execShellCommand("shell pm grant net.christianbeier.droidvnc_ng android.permission.WRITE_EXTERNAL_STORAGE");
|
||
// 屏幕录制权限
|
||
this.execShellCommand("shell appops set net.christianbeier.droidvnc_ng PROJECT_MEDIA allow");
|
||
|
||
// 后台启动
|
||
this.execShellCommand("shell am start-foreground-service -n net.christianbeier.droidvnc_ng/.MainService -a net.christianbeier.droidvnc_ng.ACTION_STOP --es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY d042e2b5d5f348588a4e1a243eb7a9a0");
|
||
this.execShellCommand("shell am start-foreground-service -n net.christianbeier.droidvnc_ng/.MainService -a net.christianbeier.droidvnc_ng.ACTION_START --es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY d042e2b5d5f348588a4e1a243eb7a9a0");
|
||
}
|
||
|
||
/**
|
||
* status
|
||
*
|
||
* @return
|
||
*/
|
||
public Map<Object, Object> status() {
|
||
Map<Object, Object> m = T.MapUtil.builder()
|
||
.put("platform", "android")
|
||
.build();
|
||
AdbDevice device = this.getAdbDevice();
|
||
m.put("status", device.isAvailable() ? "online" : "offline");
|
||
|
||
Map<String, String> prop = this.getProp();
|
||
m.put("name", T.MapUtil.getStr(prop, "ro.product.name", ""));
|
||
m.put("brand", T.MapUtil.getStr(prop, "ro.product.brand", ""));
|
||
m.put("model", T.MapUtil.getStr(prop, "ro.product.model", ""));
|
||
m.put("version", T.MapUtil.getStr(prop, "ro.build.version.release", ""));
|
||
m.put("resolution", T.MapUtil.getStr(prop, "wm.size", ""));
|
||
|
||
// 默认为真机
|
||
String type = "device";
|
||
for (Map.Entry<String, String> entry : prop.entrySet()) {
|
||
// 根据 ro.build.* 这一组配置值判定是否为模拟器,如果包含 emulator、sdk 则为模拟器
|
||
if (entry.getKey().contains("ro.build")) {
|
||
String value = entry.getValue();
|
||
if (T.StrUtil.containsAnyIgnoreCase(value, "emulator", "sdk")) {
|
||
type = "emulator";
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
m.put("type", type);
|
||
|
||
// check root
|
||
String checkRootResult = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildCheckRootCommand()
|
||
.build()
|
||
);
|
||
m.put("root", !T.StrUtil.containsIgnoreCase(checkRootResult, "Permission denied"));
|
||
return m;
|
||
}
|
||
|
||
/**
|
||
* getAdbDevice
|
||
* adb devices -l
|
||
*
|
||
* @return
|
||
*/
|
||
private AdbDevice getAdbDevice() {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.buildDevicesCommand()
|
||
.build()
|
||
);
|
||
List<AdbDevice> list = T.ListUtil.list(true);
|
||
String[] lines = result.split("\\n");
|
||
for (String line : lines) {
|
||
if (line.startsWith("*") || line.startsWith("List") || T.StrUtil.isEmpty(line))
|
||
continue;
|
||
list.add(new AdbDevice(line));
|
||
}
|
||
AdbDevice adbDevice = list.stream()
|
||
.filter(pojo -> T.StrUtil.equals(pojo.getSerial(), this.getSerial()))
|
||
.findFirst()
|
||
.orElse(null);
|
||
return adbDevice;
|
||
}
|
||
|
||
/**
|
||
* getProp
|
||
* adb shell getprop
|
||
*
|
||
* @return
|
||
*/
|
||
private Map<String, String> getProp() {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildGetpropCommand()
|
||
.build()
|
||
);
|
||
Map<String, String> prop = new LinkedHashMap<>();
|
||
|
||
Pattern pattern = Pattern.compile("\\[(.*?)\\]: \\[(.*?)\\]");
|
||
Matcher matcher = pattern.matcher(result);
|
||
|
||
while (matcher.find()) {
|
||
String key = matcher.group(1).trim();
|
||
String value = matcher.group(2).trim();
|
||
prop.put(key, value);
|
||
}
|
||
|
||
// 分辨率 Physical size: 1440x3040
|
||
String wmSize = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildWmSizeCommand()
|
||
.build()
|
||
);
|
||
prop.put("wm.size", T.StrUtil.trim(wmSize.replaceAll("Physical size: ", "")));
|
||
return prop;
|
||
}
|
||
|
||
|
||
/**
|
||
* md5sum
|
||
*/
|
||
private CommandResult md5sum(String path) {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildMd5sumCommand(path)
|
||
.build()
|
||
);
|
||
log.info("[md5sum] [path: {}] [result: {}]", path, result);
|
||
if (T.StrUtil.isNotEmpty(result)) {
|
||
String md5 = result.split("\\s+")[0];
|
||
return new CommandResult(0, md5);
|
||
}
|
||
return new CommandResult(1, "");
|
||
}
|
||
|
||
/**
|
||
* push
|
||
* 0 success; !0 failed
|
||
*/
|
||
public CommandResult push(String local, String remote) {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildPushCommand(local, remote)
|
||
.build()
|
||
);
|
||
log.info("[push] [local: {}] [remote: {}] [result: {}]", local, remote, result);
|
||
return new CommandResult(T.StrUtil.contains(result, "failed") ? 1 : 0, result);
|
||
}
|
||
|
||
/**
|
||
* pull
|
||
* 0 success; !0 failed
|
||
*/
|
||
public CommandResult pull(String remote, String local) {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildPullCommand(remote, local)
|
||
.build()
|
||
);
|
||
log.info("[pull] [remote: {}] [local: {}] [result: {}]", remote, local, result);
|
||
return new CommandResult(T.StrUtil.containsAny(result, "file pulled", "files pulled") ? 0 : 1, result);
|
||
}
|
||
|
||
/**
|
||
* list dir
|
||
* ls -l
|
||
* stat filename
|
||
*/
|
||
public List<Map> listDir(String path) {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildLsDirCommand(path)
|
||
.build()
|
||
);
|
||
if (T.StrUtil.contains(result, "No such file or directory")) {
|
||
log.warn("[listDir] [path: {}] [result: {}]", path, result);
|
||
throw new APIException(RCode.NOT_EXISTS);
|
||
}
|
||
|
||
if (T.StrUtil.contains(result, "Permission denied")) {
|
||
log.warn("[listDir] [path: {}] [result: {}]", path, result);
|
||
throw new APIException(RCode.NOT_PERMISSION);
|
||
}
|
||
|
||
List<CompletableFuture<Map>> futureList = T.ListUtil.list(false);
|
||
|
||
List<Map> listDir = T.ListUtil.list(true);
|
||
String[] lines = result.split("\\n");
|
||
boolean isDir = false;
|
||
for (String line : lines) {
|
||
if (line.startsWith("total")) {
|
||
isDir = true;
|
||
continue;
|
||
}
|
||
String[] split = line.split("\\s+");
|
||
String name;
|
||
// link file|dir
|
||
if (10 == split.length) {
|
||
name = split[7];
|
||
} else {
|
||
name = split[split.length - 1];
|
||
}
|
||
|
||
String statFilePath = isDir ? Paths.get(path).resolve(name).toString() : path;
|
||
String statCommand = "shell stat -c \"'%N %a %X %Y'\" " + statFilePath;
|
||
futureList.add(
|
||
CompletableFuture.supplyAsync(() -> {
|
||
String statResult = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(statCommand.replaceAll("\\\\", "/"))
|
||
.build()
|
||
);
|
||
// reverse result
|
||
List<String> list = Arrays.asList(statResult.split("\\s+"));
|
||
Collections.reverse(list);
|
||
|
||
String fullName = list.get(3).replaceAll("'|`", "");
|
||
Map<String, Object> relMap = T.MapUtil.newHashMap();
|
||
relMap.put("name", name);
|
||
relMap.put("value", T.MapUtil.builder()
|
||
.put("id", Base32Codec.Base32Encoder.ENCODER.encode(fullName.getBytes()))
|
||
.put("fullName", fullName)
|
||
.put("permissions", Long.parseLong(list.get(2)))
|
||
.put("cts", Long.parseLong(list.get(1)))
|
||
.put("uts", Long.parseLong(list.get(0)))
|
||
.build());
|
||
return relMap;
|
||
}, getThreadPool())
|
||
);
|
||
|
||
Map<Object, Object> m = T.MapUtil.builder()
|
||
.put("name", name)
|
||
.build();
|
||
listDir.add(m);
|
||
}
|
||
|
||
try {
|
||
CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).get();
|
||
futureList.forEach(f -> {
|
||
Map map = f.getNow(null);
|
||
if (T.MapUtil.isNotEmpty(map)) {
|
||
String name = T.MapUtil.getStr(map, "name");
|
||
Map fileAttr = listDir.stream().filter(m -> T.MapUtil.getStr(m, "name").equals(name)).findFirst().get();
|
||
fileAttr.putAll(T.MapUtil.get(map, "value", Map.class));
|
||
}
|
||
});
|
||
} catch (Exception e) {
|
||
log.warn(e);
|
||
}
|
||
return listDir;
|
||
}
|
||
|
||
/**
|
||
* listApp
|
||
* adb shell pm list packages
|
||
*
|
||
* @return
|
||
*/
|
||
public List<Map> listApp(String arg) {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildPmListPackagesCommand(arg)
|
||
.build()
|
||
);
|
||
|
||
List<Map> listApp = T.ListUtil.list(true);
|
||
|
||
List<CompletableFuture<Map>> futureList = T.ListUtil.list(false);
|
||
|
||
String prefix = "package:";
|
||
String[] lines = result.split("\\n");
|
||
for (String line : lines) {
|
||
String packageName = T.StrUtil.trim(line.substring(prefix.length()));
|
||
|
||
String dumpsysResult = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand("shell dumpsys package " + packageName)
|
||
.build()
|
||
);
|
||
String[] split = dumpsysResult.split("\\n");
|
||
String version = "", apkPath = "";
|
||
for (String s : split) {
|
||
if (s.contains("versionName=")) {
|
||
version = T.StrUtil.trim(s).replaceAll("versionName=", "");
|
||
}
|
||
if (s.contains("path: ")) {
|
||
apkPath = T.StrUtil.trim(s).replaceAll("path: ", "");
|
||
}
|
||
}
|
||
if (T.StrUtil.isNotEmpty(apkPath)) {
|
||
String finalApkPath = apkPath;
|
||
futureList.add(
|
||
CompletableFuture.supplyAsync(() -> {
|
||
try {
|
||
CommandResult md5sumRes = this.md5sum(finalApkPath);
|
||
String md5Value = md5sumRes.output();
|
||
File localApk = T.FileUtil.file(Constant.TEMP_PATH, md5Value + ".apk");
|
||
if (!T.FileUtil.exist(localApk)) {
|
||
CommandResult pulled = this.pull(finalApkPath, localApk.getAbsolutePath());
|
||
if (0 != pulled.exitCode()) {
|
||
log.warn("[listApp] [pull apk error] [pkg: {}]", packageName);
|
||
return null;
|
||
}
|
||
}
|
||
ApkUtil apkUtil = new ApkUtil();
|
||
ApkInfo apkInfo = apkUtil.parseApk(localApk.getAbsolutePath());
|
||
String appName = apkInfo.getLabel();
|
||
String iconFilename = apkInfo.getIcon();
|
||
String base64IconDate = apkUtil.extractFileFromApk(localApk.getAbsolutePath(), iconFilename);
|
||
|
||
Map<String, Object> relMap = T.MapUtil.newHashMap();
|
||
relMap.put("pkg", packageName);
|
||
relMap.put("value", T.MapUtil.builder()
|
||
.put("name", appName)
|
||
.put("icon", base64IconDate)
|
||
.build());
|
||
return relMap;
|
||
} catch (Exception e) {
|
||
log.error(e, "[listApp] [parse apk] [pkg: {}]", packageName);
|
||
}
|
||
return null;
|
||
}, getThreadPool())
|
||
);
|
||
}
|
||
|
||
Map<Object, Object> m = T.MapUtil.builder()
|
||
.put("packageName", packageName)
|
||
.put("version", version)
|
||
.build();
|
||
listApp.add(m);
|
||
}
|
||
|
||
try {
|
||
CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).get();
|
||
futureList.forEach(f -> {
|
||
Map map = f.getNow(null);
|
||
if (T.MapUtil.isNotEmpty(map)) {
|
||
String pkg = T.MapUtil.getStr(map, "pkg");
|
||
Map appAttr = listApp.stream().filter(m -> T.MapUtil.getStr(m, "packageName").equals(pkg)).findFirst().get();
|
||
appAttr.putAll(T.MapUtil.get(map, "value", Map.class));
|
||
}
|
||
});
|
||
} catch (Exception e) {
|
||
log.warn(e);
|
||
}
|
||
return listApp;
|
||
}
|
||
|
||
/**
|
||
* install app
|
||
* adb install apk
|
||
*/
|
||
public CommandResult install(String localFilePath, boolean isDebugApk, boolean isReInstall) {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildInstallCommand(localFilePath, isDebugApk, isReInstall)
|
||
.build()
|
||
);
|
||
log.info("[install] [localFilePath: {}] [isDebugApk: {}] [isReInstall: {}] [result: {}]", localFilePath, isDebugApk, isReInstall, result);
|
||
return new CommandResult(T.StrUtil.containsAny(result, "Success") ? 0 : 1, result);
|
||
}
|
||
|
||
/**
|
||
* uninstall app
|
||
* adb uninstall package_name
|
||
*/
|
||
public CommandResult uninstall(String packageName) {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildUnInstallCommand(packageName)
|
||
.build()
|
||
);
|
||
log.info("[uninstall] [packageName: {}] [result: {}]", packageName, result);
|
||
return new CommandResult(T.StrUtil.containsAny(result, "Success") ? 0 : 1, result);
|
||
}
|
||
|
||
/**
|
||
* iptables -F
|
||
* iptables -X
|
||
*/
|
||
@Deprecated
|
||
private void cleanIptables() {
|
||
// Delete all rules in chain or all chains
|
||
CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand("shell iptables -F")
|
||
.build()
|
||
);
|
||
// Delete user-defined chain
|
||
CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand("shell iptables -X")
|
||
.build()
|
||
);
|
||
}
|
||
|
||
/**
|
||
* list tcpdump
|
||
*/
|
||
public List<Map> listTcpdump() {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep capture_ | awk '{print $NF}' \""))
|
||
.build());
|
||
|
||
List<Map> list = T.ListUtil.list(true);
|
||
|
||
String[] lines = result.split("\\n");
|
||
for (String line : lines) {
|
||
try {
|
||
String fileName = T.FileUtil.mainName(line);
|
||
String taskId = "", packageName = "";
|
||
if (fileName.contains("capture_all_")) {
|
||
taskId = fileName.replaceAll("capture_all_", "");
|
||
} else {
|
||
String[] split = fileName.split("_");
|
||
packageName = split[2];
|
||
taskId = split[split.length - 1];
|
||
}
|
||
Map<Object, Object> m = T.MapUtil.builder()
|
||
.put("id", taskId)
|
||
.put("packageName", packageName)
|
||
.build();
|
||
list.add(m);
|
||
} catch (Exception e) {
|
||
log.warn(e, "[listTcpdump] [get task info error] [line: {}]", line);
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
|
||
/**
|
||
* start Tcpdump
|
||
* tcpdump pcap
|
||
*/
|
||
public CommandResult startTcpdump(String packageName) {
|
||
String taskId = T.IdUtil.fastSimpleUUID();
|
||
if (T.StrUtil.isNotEmpty(packageName)) {
|
||
log.info("[startTcpdump] [capture app package] [pkg: {}]", packageName);
|
||
String dumpsysResult = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand("shell dumpsys package " + packageName)
|
||
.build()
|
||
);
|
||
String[] lines = dumpsysResult.split("\\n");
|
||
String userId = Arrays.stream(lines)
|
||
.filter(s -> T.StrUtil.contains(s, "userId="))
|
||
.findFirst()
|
||
.map(s -> T.StrUtil.trim(s).replaceAll("userId=", ""))
|
||
.orElseThrow(() -> new APIException("Not found userId by package name. package name: " + packageName));
|
||
|
||
CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell iptables -A OUTPUT -m owner --uid-owner %s -j CONNMARK --set-mark %s", userId, userId))
|
||
.build());
|
||
CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell iptables -A INPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId))
|
||
.build());
|
||
CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell iptables -A OUTPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId))
|
||
.build());
|
||
|
||
String ruleList = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand("shell iptables -L")
|
||
.build());
|
||
log.info("[startTcpdump] [iptables -L] [result: {}]", ruleList);
|
||
|
||
// pcap 格式:capture_{userId}_{pcakageName}_{taskId}.pcap
|
||
String pcapFilePath = "/data/local/tmp/capture_" + userId + "_" + packageName + "_" + taskId + ".pcap";
|
||
CommandExec.execForProcess(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell tcpdump -i nflog:%s -w %s &", userId, pcapFilePath))
|
||
.build());
|
||
} else {
|
||
log.info("[startTcpdump] [capture all package]");
|
||
// pcap 格式:capture_all_{taskId}.pcap
|
||
String pcapFilePath = "/data/local/tmp/capture_all_" + taskId + ".pcap";
|
||
CommandExec.execForProcess(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell tcpdump -w %s &", pcapFilePath))
|
||
.build());
|
||
}
|
||
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $2}' \"", taskId))
|
||
.build());
|
||
log.info("[startTcpdump] [taskId: {}] [tcpdump pid: {}]", taskId, result);
|
||
return new CommandResult(T.StrUtil.isNotEmpty(result) ? 0 : 1, taskId);
|
||
}
|
||
|
||
/**
|
||
* stop tcpdump
|
||
* kill -INT {pid}
|
||
*/
|
||
public CommandResult stopTcpdump(String id) {
|
||
String pcapFilePath = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $NF}' \"", id))
|
||
.build());
|
||
if (T.StrUtil.isNotEmpty(pcapFilePath)) {
|
||
if (!pcapFilePath.contains("capture_all_")) {
|
||
// 删除 iptables rule
|
||
String[] split = T.FileUtil.mainName(pcapFilePath).split("_");
|
||
String userId = split[1];
|
||
log.info("[stopTcpdump] [remove iptables rule] [userId: {}]", userId);
|
||
CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell iptables -D OUTPUT -m owner --uid-owner %s -j CONNMARK --set-mark %s", userId, userId))
|
||
.build());
|
||
CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell iptables -D INPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId))
|
||
.build());
|
||
CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell iptables -D OUTPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId))
|
||
.build());
|
||
}
|
||
}
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $2}' | xargs kill -INT \"", id))
|
||
.build());
|
||
log.info("[stopTcpdump] [id: {}] [pcapFilePath: {}] [result: {}]", id, pcapFilePath, result);
|
||
if (T.StrUtil.isEmpty(result)) {
|
||
return new CommandResult(0, pcapFilePath);
|
||
}
|
||
return new CommandResult(1, result);
|
||
}
|
||
|
||
/**
|
||
* exec shell command
|
||
*/
|
||
public void execShellCommand(String shellCmd) {
|
||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand(shellCmd)
|
||
.build());
|
||
log.info("[execShellCommand] [shellCmd: {}] [result: {}]", shellCmd, result);
|
||
}
|
||
|
||
/**
|
||
* exec shell command
|
||
*/
|
||
public String execShellCommand(String cmd, Integer timeout){
|
||
Process process = CommandExec.execForProcess(AdbCommandBuilder.builder()
|
||
.serial(this.getSerial())
|
||
.buildShellCommand("shell " + cmd)
|
||
.build());
|
||
|
||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||
Future<String> future = executor.submit(() -> T.IoUtil.read(process.getInputStream(), T.CharsetUtil.CHARSET_UTF_8));
|
||
try {
|
||
String result = future.get(timeout, TimeUnit.SECONDS);
|
||
return result;
|
||
} catch (TimeoutException e) {
|
||
process.destroyForcibly();
|
||
throw new APIException(RCode.TIMEOUT);
|
||
} catch (ExecutionException | InterruptedException e) {
|
||
throw new APIException(RCode.ERROR);
|
||
} finally {
|
||
executor.shutdown();
|
||
}
|
||
}
|
||
|
||
private synchronized ExecutorService getThreadPool() {
|
||
if (threadPool == null) {
|
||
threadPool = new ThreadPoolExecutor(
|
||
5,
|
||
10,
|
||
30,
|
||
TimeUnit.SECONDS,
|
||
new ArrayBlockingQueue<Runnable>(10000),
|
||
new NamedThreadFactory("API-", true));
|
||
}
|
||
return threadPool;
|
||
}
|
||
|
||
class CommandExec {
|
||
public static String exec(List<String> command) {
|
||
String str = T.RuntimeUtil.execForStr(T.CharsetUtil.CHARSET_UTF_8, command.stream().toArray(String[]::new));
|
||
return str.stripTrailing();
|
||
}
|
||
|
||
public static Process execForProcess(List<String> command) {
|
||
Process process = T.RuntimeUtil.exec(command.stream().toArray(String[]::new));
|
||
return process;
|
||
}
|
||
}
|
||
} |