summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorshizhendong <[email protected]>2024-08-27 15:46:51 +0800
committershizhendong <[email protected]>2024-08-27 15:46:51 +0800
commit9814018149617bf79e81373f5dd6be7376355f84 (patch)
tree2b3f52e6b40a9fc16b530cb889e4858de6bc67da
parent6f6bb2ad90cfcef28323a720f24414314b4edfe4 (diff)
feat: 请求 API 添加 token 认证;调整 tcpdump 执行逻辑;新增 execShellCmd 接口
-rw-r--r--pom.xml1
-rw-r--r--src/main/java/net/geedge/api/controller/APIController.java21
-rw-r--r--src/main/java/net/geedge/api/util/AdbUtil.java100
-rw-r--r--src/main/java/net/geedge/common/RCode.java1
-rw-r--r--src/main/java/net/geedge/common/T.java30
-rw-r--r--src/main/java/net/geedge/common/config/TokenInterceptor.java54
-rw-r--r--src/main/resources/application.yml4
-rw-r--r--src/main/resources/config/token.auth1
8 files changed, 202 insertions, 10 deletions
diff --git a/pom.xml b/pom.xml
index c4dd00b..7ac2eb9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,6 +71,7 @@
<excludes>
<exclude>**/application-*.yml</exclude>
<exclude>**/logback-spring.xml</exclude>
+ <exclude>**/token.auth</exclude>
<!--lib 下的依赖不加入 jar,部署时放到 jar 文件同级即可-->
<exclude>lib/*.*</exclude>
</excludes>
diff --git a/src/main/java/net/geedge/api/controller/APIController.java b/src/main/java/net/geedge/api/controller/APIController.java
index 58e8c73..f1b4be1 100644
--- a/src/main/java/net/geedge/api/controller/APIController.java
+++ b/src/main/java/net/geedge/api/controller/APIController.java
@@ -127,6 +127,11 @@ public class APIController {
return R.ok();
}
+ @GetMapping("/pcap")
+ public R listTcpdump() {
+ return R.ok().putData("records", adbUtil.listTcpdump());
+ }
+
@PostMapping("/pcap")
public R startTcpdump(@RequestParam(required = false, defaultValue = "") String packageName) {
AdbUtil.CommandResult result = adbUtil.startTcpdump(packageName);
@@ -149,7 +154,10 @@ public class APIController {
// response pcap file
File tempFile = T.FileUtil.file(Constant.TEMP_PATH, id + ".pcap");
try {
- String filePath = "/data/local/tmp/" + id + ".pcap";
+ String filePath = result.output();
+ if (T.StrUtil.isEmpty(filePath)) {
+ throw new APIException(RCode.NOT_EXISTS);
+ }
AdbUtil.CommandResult pulled = adbUtil.pull(filePath, tempFile.getAbsolutePath());
if (0 != pulled.exitCode()) {
throw new APIException(pulled.output());
@@ -163,4 +171,15 @@ public class APIController {
response.getWriter().write(T.JSONUtil.toJsonStr(R.ok().putData("id", id)));
}
}
+
+ @PostMapping("/shell")
+ public R execShellCmd(@RequestBody Map<String, Object> requestBody) {
+ String cmd = T.MapUtil.getStr(requestBody, "cmd", "");
+ if (T.StrUtil.isEmpty(cmd)) {
+ return R.error(RCode.BAD_REQUEST);
+ }
+
+ Integer timeout = T.MapUtil.getInt(requestBody, "timeout", 10);
+ return R.ok().putData("result", adbUtil.execShellCommand(cmd, timeout));
+ }
} \ No newline at end of file
diff --git a/src/main/java/net/geedge/api/util/AdbUtil.java b/src/main/java/net/geedge/api/util/AdbUtil.java
index beba56e..71d4adb 100644
--- a/src/main/java/net/geedge/api/util/AdbUtil.java
+++ b/src/main/java/net/geedge/api/util/AdbUtil.java
@@ -482,6 +482,7 @@ public class AdbUtil {
* iptables -F
* iptables -X
*/
+ @Deprecated
private void cleanIptables() {
// Delete all rules in chain or all chains
CommandExec.exec(AdbCommandBuilder.builder()
@@ -498,16 +499,46 @@ public class AdbUtil {
}
/**
+ * 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
- * iptables option
* tcpdump pcap
*/
public CommandResult startTcpdump(String packageName) {
- // clean iptables conf
- this.cleanIptables();
-
String taskId = T.IdUtil.fastSimpleUUID();
- String pcapFilePath = "/data/local/tmp/" + taskId + ".pcap";
if (T.StrUtil.isNotEmpty(packageName)) {
log.info("[startTcpdump] [capture app package] [pkg: {}]", packageName);
String dumpsysResult = CommandExec.exec(AdbCommandBuilder.builder()
@@ -541,12 +572,16 @@ public class AdbUtil {
.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))
@@ -566,12 +601,39 @@ public class AdbUtil {
* 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: {}] [result: {}]", id, result);
- return new CommandResult(T.StrUtil.isEmpty(result) ? 0 : 1, result);
+ log.info("[stopTcpdump] [id: {}] [pcapFilePath: {}] [result: {}]", id, pcapFilePath, result);
+ if (T.StrUtil.isEmpty(result)) {
+ return new CommandResult(0, pcapFilePath);
+ }
+ return new CommandResult(1, result);
}
/**
@@ -585,6 +647,30 @@ public class AdbUtil {
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(
diff --git a/src/main/java/net/geedge/common/RCode.java b/src/main/java/net/geedge/common/RCode.java
index a7e6b97..8880eef 100644
--- a/src/main/java/net/geedge/common/RCode.java
+++ b/src/main/java/net/geedge/common/RCode.java
@@ -9,6 +9,7 @@ public enum RCode {
NOT_EXISTS(404, "No such file or directory"),
NOT_PERMISSION(401 , "Permission denied"),
+ TIMEOUT(408, "Request Timeout"),
ERROR(999, "error"), // 通用错误/未知错误
diff --git a/src/main/java/net/geedge/common/T.java b/src/main/java/net/geedge/common/T.java
index 2545cfb..8bb3b1a 100644
--- a/src/main/java/net/geedge/common/T.java
+++ b/src/main/java/net/geedge/common/T.java
@@ -1,10 +1,14 @@
package net.geedge.common;
import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.log.Log;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
+import java.io.File;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.StringTokenizer;
@@ -212,4 +216,30 @@ public class T {
T.IoUtil.write(response.getOutputStream(), false, data);
}
}
+
+ public static class WebPathUtil {
+ static Log log = Log.get();
+
+ /**
+ * 如果已打成jar包,则返回jar包所在目录
+ * 如果未打成jar,则返回target所在目录
+ *
+ * @return
+ */
+ public static String getClassPath() {
+ try {
+ // 项目的编译文件的根目录
+ String path = URLDecoder.decode(System.getProperty("user.dir"), "utf-8");
+ log.debug("root path:{}", path);
+ return path;
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+ }
+
+ public static String getRootPath() {
+ File file = T.FileUtil.file(WebPathUtil.getClassPath());
+ return file.getAbsolutePath();
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/net/geedge/common/config/TokenInterceptor.java b/src/main/java/net/geedge/common/config/TokenInterceptor.java
new file mode 100644
index 0000000..c0c0708
--- /dev/null
+++ b/src/main/java/net/geedge/common/config/TokenInterceptor.java
@@ -0,0 +1,54 @@
+package net.geedge.common.config;
+
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.log.Log;
+import jakarta.annotation.PostConstruct;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import net.geedge.common.T;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.io.File;
+
+@Configuration(proxyBeanMethods = false)
+public class TokenInterceptor implements WebMvcConfigurer {
+ private final static Log log = Log.get();
+
+ private static String tokenValue;
+
+ @Value("${device.tokenFile:config/token.auth}")
+ protected String tokenFile;
+
+
+ @PostConstruct
+ public void init() throws IORuntimeException {
+ tokenValue = readToken();
+ }
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(new HandlerInterceptor() {
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ String token = request.getHeader("Authorization");
+ if (token == null || !token.equals(tokenValue)) {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ response.getWriter().write("Unauthorized");
+ return false;
+ }
+ return true;
+ }
+ }).addPathPatterns("/**");
+ }
+
+ private String readToken() throws IORuntimeException {
+ File tf = T.FileUtil.file(T.WebPathUtil.getRootPath(), tokenFile);
+ log.info("token file path: {}", tf.getAbsolutePath());
+ String token = T.FileUtil.readString(tf, T.CharsetUtil.UTF_8);
+ return T.StrUtil.trim(token);
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index e86d68f..628890e 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -8,5 +8,5 @@ spring:
max-request-size: 500MB
enabled: true
-logging:
- config:./config/logback-spring.xml
+device:
+ tokenFile: ./config/token.auth
diff --git a/src/main/resources/config/token.auth b/src/main/resources/config/token.auth
new file mode 100644
index 0000000..ead27ac
--- /dev/null
+++ b/src/main/resources/config/token.auth
@@ -0,0 +1 @@
+2fa9a369 \ No newline at end of file