/* * Tencent is pleased to support the open source community by making * Tencent GT (Version 2.4 and subsequent versions) available. * * Notwithstanding anything to the contrary herein, any previous version * of Tencent GT shall not be subject to the license hereunder. * All right, title, and interest, including all intellectual property rights, * in and to the previous version of Tencent GT (including any and all copies thereof) * shall be owned and retained by Tencent and subject to the license under the * Tencent GT End User License Agreement (http://gt.qq.com/wp-content/EULA_EN.html). * * Copyright (C) 2015 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the MIT License (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at * * http://opensource.org/licenses/MIT * * Unless required by applicable law or agreed to in writing, software distributed * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.tencent.wstt.gt.plugin.tcpdump; import java.io.File; import java.util.ArrayList; import java.util.List; import com.tencent.wstt.gt.GTApp; import com.tencent.wstt.gt.api.utils.CMDExecute; import com.tencent.wstt.gt.api.utils.Env; import com.tencent.wstt.gt.plugin.PluginTaskExecutor; import com.tencent.wstt.gt.utils.FileUtil; import com.tencent.wstt.gt.utils.RootUtil; import android.content.Context; import android.os.Bundle; /** * 因为抓包的UI过于复杂,所以引擎与UI暂走两条路互不干涉 */ public class GTCaptureEngine implements PluginTaskExecutor { private static GTCaptureEngine INSTANCE; private List<GTCaptureListener> listeners; private static final String DEFAULT_PARAM = "-p -s 0 -vv -w"; private boolean captureState = false; /* * 为了兼容Android6.x,需要持有抓包进程对象才能将其停止。 * 因为在APP中没有权限得到su守护进程下的tcpdump进程(因其UID是root) */ private Process curTcpdumpProcess; private long curFileSize = -1; //KB private String curFilePath; public boolean getCaptureState(){ return captureState; } private void setCaptureState(boolean captureState){ this.captureState = captureState; } public static GTCaptureEngine getInstance() { if (null == INSTANCE) { INSTANCE = new GTCaptureEngine(); } return INSTANCE; } private GTCaptureEngine() { listeners = new ArrayList<GTCaptureListener>(); } public synchronized void addListener(GTCaptureListener listener) { listeners.add(listener); } public synchronized void removeListener(GTCaptureListener listener) { listeners.remove(listener); } @Override public void execute(Bundle bundle) { String cmd = bundle.getString("cmd"); if (cmd != null && cmd.equals("startCapture")) { String filePath = bundle.getString("filePath"); String param = bundle.getString("param"); doCapture(filePath, param); } else if (cmd != null && cmd.equals("stopCapture")) { doStopCapture(); } } public void doCapture(final String filePath, final String param) { // 对于UI来说,此时应该转菊花 for (GTCaptureListener listener : listeners) { listener.preStartCapture(); } boolean checkResult = checkTcpDump(filePath, param); // 先check抓包环境 if (checkResult) { for (GTCaptureListener listener : listeners) { listener.onStartCaptureBegin(); } new Thread(new Runnable(){ @Override public void run() { String realParam = param; if (param == null || param.trim().equals("")) { realParam = DEFAULT_PARAM; } startTcpDump(filePath, realParam, GTApp.getContext(), true); /* * 因为上面startTcpDump方法需要输出输入流(最后一个参数是true), * 所以抓包不停止或发生异常前面startTcpDump方法是不会结束,所以这里处理抓包结束监听 */ for (GTCaptureListener listener : listeners) { listener.onStopCaptureEnd(); } }}, "GTCaptureThread").start(); } } public void doStopCapture() { for (GTCaptureListener listener : listeners) { listener.onStopCaptureBegin(); } endTcpDump(); } private boolean checkTcpDump(final String filePath, final String param) { // 判断手机是否root String errorstr = ""; if (!RootUtil.isRooted()) { errorstr = "root needed!"; notifyError(errorstr); return false; } // 判断是否有手机存储 if (!Env.isSDCardExist()) { errorstr = "phone storage needed!"; notifyError(errorstr); return false; } File file = new File(filePath); if (filePath.contains("\\") // 路径可以有'/' || filePath.contains(":") || filePath.contains("*") || filePath.contains("?") || filePath.contains("\"") || filePath.contains("<") || filePath.contains(">") || filePath.contains("|")) { errorstr = "filePath can't contain::*?\"<>|"; notifyError(errorstr); return false; } if (param != null && (param.contains("|") || param.contains(">") || param.contains(">>"))) { errorstr = "param can't contain: | > >>"; notifyError(errorstr); return false; } // 尝试创建目录 if (!FileUtil.createDir(file.getParent())) { errorstr = "folder create failed!"; notifyError(errorstr); return false; } return true; } private void notifyError(String errorstr) { for (GTCaptureListener listener : listeners) { listener.onCaptureFail(errorstr); } } /** * 开始抓包 * @param path * 抓包文件存储路径 * @param command * 命令参数 * @param context * 应用程序的上下文环境 * @param needInputStream * 是否需要输出抓包命令执行后返回的输入流 */ public void startTcpDump(String path, String command, Context context, boolean needInputStream) { if (getCaptureState()) { // 已启动抓包,直接退出 return; } setCaptureState(true); if (null != curTcpdumpProcess) { try { curTcpdumpProcess.destroy(); curTcpdumpProcess = null; } catch (Exception e) { } } try { String cmd = context.getFilesDir().getPath() + "/" + "tcpdump " + command + " " + path; curTcpdumpProcess = CMDExecute.startSuCmdInteractive(); CMDExecute.continueSuCmdInteractive(curTcpdumpProcess, cmd); if(needInputStream){ File outFile = new File(path); curFilePath = path; int count = 0; boolean hasNotifyStartEnd = false; while(true) { if (outFile.exists()) { if (!hasNotifyStartEnd) { hasNotifyStartEnd = true; // 对于UI来说,此时也应该停止转菊花 for (GTCaptureListener listener : listeners) { listener.onStartCaptureEnd(curFilePath); } } long preSize = curFileSize; curFileSize = outFile.length() >> 10; if (preSize != curFileSize) { for (GTCaptureListener listener : listeners) { listener.onDataChange(curFileSize); } } } else if (count >= 5) // 5s后文件仍未生成,判定为启动抓包失败 { for (GTCaptureListener listener : listeners) { listener.onCaptureFail("create file failed!"); } endTcpDump(); break; } // 抓包结束要退出循环 if (!getCaptureState()) { break; } Thread.sleep(1000); count++; } } } catch (Exception e) { e.printStackTrace(); } } /** * 获取抓包命令执行后返回的输入流 * @return 抓包命令执行后返回的输入流 */ public long getCurFileSize(){ return curFileSize; } /** * 获取抓包命令执行后返回的输入流 * @return 抓包命令执行后返回的输入流 */ public String getCurFilePath(){ return curFilePath; } /** * 结束抓包 * @param context * 应用程序的上下文环境 */ public void endTcpDump(){ setCaptureState(false); try { curTcpdumpProcess.destroy(); } catch (Exception e) { e.printStackTrace(); } curTcpdumpProcess = null; curFileSize = -1; curFilePath = null; for (GTCaptureListener listener : listeners) { listener.onStopCaptureEnd(); } } }