mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-06-16 21:10:57 +08:00
Implement embedded HTTP server to provide an easy PCAP download option
It's now possible to choose the PCAP dump mode: none (no external PCAP dump), HTTP server, UDP exporter. The HTTP server mode is a convenient way to download a PCAP without any client side configuration. This also makes it possible to capture the PCAP data on a Windows pc, where the UDP collection mode is not normally applicable. Closes #13
This commit is contained in:
parent
930bfaa218
commit
14435311a5
@ -37,4 +37,5 @@ dependencies {
|
||||
implementation project(path: ':zdtun')
|
||||
implementation 'cat.ereza:customactivityoncrash:2.2.0'
|
||||
implementation project(path: ':ndpi')
|
||||
implementation 'org.nanohttpd:nanohttpd:2.3.1'
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ package com.emanuelef.remote_capture;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.VpnService;
|
||||
import android.os.Build;
|
||||
@ -30,6 +31,7 @@ import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
@ -43,11 +45,14 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
private String vpn_dns;
|
||||
private String public_dns;
|
||||
private String collector_address;
|
||||
private Prefs.DumpMode dump_mode;
|
||||
private boolean capture_unknown_app_traffic;
|
||||
private int collector_port;
|
||||
private int http_server_port;
|
||||
private int uid_filter;
|
||||
private long last_bytes;
|
||||
private static CaptureService INSTANCE;
|
||||
private HTTPServer mHttpServer;
|
||||
|
||||
public static final String ACTION_TRAFFIC_STATS_UPDATE = "traffic_stats_update";
|
||||
public static final String ACTION_CONNECTIONS_DUMP = "connections_dump";
|
||||
@ -82,19 +87,38 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
|
||||
Log.d(CaptureService.TAG, "onStartCommand");
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Bundle settings = intent.getBundleExtra("settings");
|
||||
|
||||
// retrieve settings
|
||||
assert settings != null;
|
||||
public_dns = settings.getString("dns_server");
|
||||
|
||||
// Retrieve Configuration
|
||||
public_dns = Utils.getDnsServer(getApplicationContext());
|
||||
vpn_dns = "10.215.173.2";
|
||||
vpn_ipv4 = "10.215.173.1";
|
||||
collector_address = settings.getString(Prefs.PREF_COLLECTOR_IP_KEY);
|
||||
collector_port = settings.getInt(Prefs.PREF_COLLECTOR_PORT_KEY);;
|
||||
uid_filter = settings.getInt(Prefs.PREF_UID_FILTER);
|
||||
capture_unknown_app_traffic = settings.getBoolean(Prefs.PREF_CAPTURE_UNKNOWN_APP_TRAFFIC);
|
||||
|
||||
collector_address = Prefs.getCollectorIp(prefs);
|
||||
collector_port = Prefs.getCollectorPort(prefs);
|
||||
http_server_port = Prefs.getHttpServerPort(prefs);
|
||||
capture_unknown_app_traffic = Prefs.getCaptureUnknownAppTraffic(prefs);
|
||||
dump_mode = Prefs.getDumpMode(prefs);
|
||||
last_bytes = 0;
|
||||
|
||||
if(dump_mode == Prefs.DumpMode.HTTP_SERVER) {
|
||||
if (mHttpServer == null)
|
||||
mHttpServer = new HTTPServer(getApplicationContext(), http_server_port);
|
||||
|
||||
try {
|
||||
mHttpServer.startConnections();
|
||||
} catch (IOException e) {
|
||||
Log.e(CaptureService.TAG, "Could not start the HTTP server");
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else
|
||||
mHttpServer = null;
|
||||
|
||||
Log.i("Main", "Using DNS server " + public_dns);
|
||||
|
||||
// VPN
|
||||
/* In order to see the DNS packets into the VPN we must set an internal address as the DNS
|
||||
* server. */
|
||||
@ -139,6 +163,9 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
if(mThread != null) {
|
||||
mThread.interrupt();
|
||||
}
|
||||
if(mHttpServer != null)
|
||||
mHttpServer.stop();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@ -151,6 +178,10 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
}
|
||||
mParcelFileDescriptor = null;
|
||||
}
|
||||
|
||||
if(mHttpServer != null)
|
||||
mHttpServer.endConnections();
|
||||
// NOTE: do not destroy the mHttpServer, let it terminate the active connections
|
||||
}
|
||||
|
||||
/* Check if the VPN service was launched */
|
||||
@ -175,6 +206,14 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
return((INSTANCE != null) ? INSTANCE.collector_port : 0);
|
||||
}
|
||||
|
||||
public static int getHTTPServerPort() {
|
||||
return((INSTANCE != null) ? INSTANCE.http_server_port : 0);
|
||||
}
|
||||
|
||||
public static Prefs.DumpMode getDumpMode() {
|
||||
return((INSTANCE != null) ? INSTANCE.dump_mode : Prefs.DumpMode.NONE);
|
||||
}
|
||||
|
||||
/* Stop a running VPN service */
|
||||
public static void stopService() {
|
||||
if (INSTANCE != null) {
|
||||
@ -218,6 +257,15 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
return(capture_unknown_app_traffic ? 1 : 0);
|
||||
}
|
||||
|
||||
// returns 1 if dumpPcapData should be called
|
||||
public int dumpPcapToJava() {
|
||||
return((mHttpServer != null) ? 1 : 0);
|
||||
}
|
||||
|
||||
public int dumpPcapToUdp() {
|
||||
return((dump_mode == Prefs.DumpMode.UDP_EXPORTER) ? 1 : 0);
|
||||
}
|
||||
|
||||
// from NetGuard
|
||||
@TargetApi(Build.VERSION_CODES.Q)
|
||||
public int getUidQ(int version, int protocol, String saddr, int sport, String daddr, int dport) {
|
||||
@ -271,6 +319,12 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
return(getPackageManager().getNameForUid(uid));
|
||||
}
|
||||
|
||||
/* Exports a PCAP data chunk */
|
||||
public void dumpPcapData(byte[] data) {
|
||||
if(mHttpServer != null)
|
||||
mHttpServer.pushData(data);
|
||||
}
|
||||
|
||||
public static native void runPacketLoop(int fd, CaptureService vpn, int sdk);
|
||||
public static native void stopPacketLoop();
|
||||
}
|
||||
|
||||
@ -0,0 +1,135 @@
|
||||
/*
|
||||
This file is part of RemoteCapture.
|
||||
|
||||
RemoteCapture is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
RemoteCapture is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with RemoteCapture. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2020 by Emanuele Faranda
|
||||
*/
|
||||
|
||||
package com.emanuelef.remote_capture;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/* Provides an input stream to read data from bytes chunks produced
|
||||
asynchronously via produceData(). bytes[] chunks are used instead of a
|
||||
single bytes[] in order to avoid excessive data copies.
|
||||
*/
|
||||
class ChunkedInputStream extends InputStream {
|
||||
private static final byte[] pcapHeader = Utils.hexStringToByteArray("d4c3b2a1020004000000000000000000ffff000065000000");
|
||||
final Lock mLock = new ReentrantLock();
|
||||
final Condition newData = mLock.newCondition();
|
||||
ArrayList<byte[]> mChunks = new ArrayList<byte[]>();
|
||||
int mCurChunkIndex = 0;
|
||||
boolean hasFinished = false;
|
||||
|
||||
ChunkedInputStream() {
|
||||
// Send the PCAP header as the first chunk
|
||||
mChunks.add(pcapHeader);
|
||||
}
|
||||
|
||||
/* Mark the termination of stream */
|
||||
public void stop() {
|
||||
mLock.lock();
|
||||
|
||||
try {
|
||||
hasFinished = true;
|
||||
newData.signal();
|
||||
} finally {
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/* Produce data to be read from the stream */
|
||||
public void produceData(byte data[]) {
|
||||
mLock.lock();
|
||||
try {
|
||||
if(hasFinished)
|
||||
return;
|
||||
|
||||
mChunks.add(data);
|
||||
newData.signal();
|
||||
} finally {
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buf, int off, int maxlen) {
|
||||
int out_size = 0;
|
||||
|
||||
if(maxlen <= 0)
|
||||
return(0);
|
||||
|
||||
mLock.lock();
|
||||
try {
|
||||
/* Possibly wait for new data */
|
||||
while((!hasFinished) && (mChunks.size() == 0))
|
||||
newData.await();
|
||||
|
||||
if(mChunks.size() > 0) {
|
||||
/* At least one byte will be returned here. Do not call await() below,
|
||||
just return the available bytes to provide a more responsive transfer. */
|
||||
|
||||
while((mChunks.size() > 0) && (maxlen > 0)) {
|
||||
byte[] chunk = mChunks.get(0);
|
||||
|
||||
if(off > 0) {
|
||||
// skip bytes due to the offset
|
||||
int toSkip = Math.min(off, chunk.length - mCurChunkIndex);
|
||||
off -= toSkip;
|
||||
mCurChunkIndex += toSkip;
|
||||
}
|
||||
|
||||
if (mCurChunkIndex < chunk.length) {
|
||||
int copy_length = Math.min(maxlen, chunk.length - mCurChunkIndex);
|
||||
System.arraycopy(chunk, mCurChunkIndex, buf, out_size, copy_length);
|
||||
out_size += copy_length;
|
||||
mCurChunkIndex += copy_length;
|
||||
maxlen -= copy_length;
|
||||
}
|
||||
|
||||
if (mCurChunkIndex >= chunk.length) {
|
||||
// next chunk
|
||||
mChunks.remove(0);
|
||||
mCurChunkIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return(out_size);
|
||||
}
|
||||
|
||||
/* Should be reached when hasFinished is set */
|
||||
return(-1);
|
||||
} catch (InterruptedException e) {
|
||||
return(-1);
|
||||
} finally {
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
byte[] buf = new byte[1];
|
||||
int rv = read(buf, 0, 1);
|
||||
|
||||
if(rv == -1)
|
||||
return(-1);
|
||||
else
|
||||
return(buf[0]);
|
||||
}
|
||||
}
|
||||
@ -104,19 +104,27 @@ public class ConnectionsAdapter extends BaseAdapter {
|
||||
if((now - adapter_conn.first_seen) >= MIN_CONNECTION_DISPLAY_SECONDS)
|
||||
mItems.remove(adapter_pos);
|
||||
else
|
||||
/* Too early, let the connection displayed for a while */
|
||||
adapter_pos++;
|
||||
adapter_conn = getItem(adapter_pos);
|
||||
}
|
||||
|
||||
if (adapter_conn == null)
|
||||
/* New untracked connection */
|
||||
mItems.add(eval_conn);
|
||||
else {
|
||||
/* Existing connection */
|
||||
if (eval_conn.incr_id == adapter_conn.incr_id) {
|
||||
/* Update data */
|
||||
mItems.set(adapter_pos, eval_conn);
|
||||
} else {
|
||||
Log.e(TAG, "Logic error: missing item #" + eval_conn.incr_id +
|
||||
/* This should never happen as it would mean that a connection ID has been
|
||||
* reused. */
|
||||
Log.w(TAG, "Logic error? Missing item #" + eval_conn.incr_id +
|
||||
" (adapter item: #" + adapter_conn.incr_id + ")");
|
||||
|
||||
/* Try to recover */
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
127
app/src/main/java/com/emanuelef/remote_capture/HTTPServer.java
Normal file
127
app/src/main/java/com/emanuelef/remote_capture/HTTPServer.java
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
This file is part of RemoteCapture.
|
||||
|
||||
RemoteCapture is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
RemoteCapture is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with RemoteCapture. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2020 by Emanuele Faranda
|
||||
*/
|
||||
|
||||
package com.emanuelef.remote_capture;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import fi.iki.elonen.NanoHTTPD;
|
||||
import fi.iki.elonen.NanoHTTPD.Response.Status;
|
||||
|
||||
public class HTTPServer extends NanoHTTPD {
|
||||
private static final String PCAP_MIME = "application/vnd.tcpdump.pcap";
|
||||
private final DateFormat mFmt = new SimpleDateFormat("HH_mm_ss");
|
||||
private boolean firstStart = true;
|
||||
private boolean mAcceptConnections = false;
|
||||
private Context mContext;
|
||||
|
||||
/* NOTE: access to mActiveResponses must be synchronized */
|
||||
private ArrayList<Response> mActiveResponses = new ArrayList<>();
|
||||
|
||||
public HTTPServer(Context context, int port) {
|
||||
super(port);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
private Response redirectToPcap() {
|
||||
String fname = "RemoteCapture_" + mFmt.format(new Date()) + ".pcap";
|
||||
Response r = newFixedLengthResponse(Status.TEMPORARY_REDIRECT, MIME_HTML, "");
|
||||
r.addHeader("Location", "/" + fname);
|
||||
return(r);
|
||||
}
|
||||
|
||||
/* Creates a new Response and add it to the active responses. */
|
||||
private synchronized Response newPcapStream() {
|
||||
/* NOTE: response length is unknown */
|
||||
Response res = newChunkedResponse(Status.OK, PCAP_MIME, new ChunkedInputStream());
|
||||
|
||||
mActiveResponses.add(res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
firstStart = true;
|
||||
}
|
||||
|
||||
public void startConnections() throws IOException {
|
||||
mAcceptConnections = true;
|
||||
|
||||
if(firstStart) {
|
||||
start();
|
||||
firstStart = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Marks data end on all the active connections */
|
||||
public synchronized void endConnections() {
|
||||
for(int i=mActiveResponses.size()-1; i >= 0; i--) {
|
||||
Response res = mActiveResponses.get(i);
|
||||
|
||||
if(res.isCloseConnection()) {
|
||||
/* Cleanup closed connections */
|
||||
mActiveResponses.remove(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
((ChunkedInputStream) res.getData()).stop();
|
||||
}
|
||||
|
||||
mActiveResponses.clear();
|
||||
mAcceptConnections = false;
|
||||
}
|
||||
|
||||
/* Dispatch PCAP data to the active connections */
|
||||
public synchronized void pushData(byte[] data) {
|
||||
for(int i=mActiveResponses.size()-1; i >= 0; i--) {
|
||||
Response res = mActiveResponses.get(i);
|
||||
|
||||
if(res.isCloseConnection()) {
|
||||
/* Cleanup closed connections */
|
||||
mActiveResponses.remove(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
((ChunkedInputStream) res.getData()).produceData(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response serve(IHTTPSession session) {
|
||||
if(!mAcceptConnections)
|
||||
return newFixedLengthResponse(Status.FORBIDDEN, MIME_PLAINTEXT,
|
||||
mContext.getString(R.string.capture_not_started));
|
||||
|
||||
if(session.getUri().endsWith("/")) {
|
||||
/* Use a redirect to provide a file name */
|
||||
return redirectToPcap();
|
||||
}
|
||||
|
||||
return newPcapStream();
|
||||
}
|
||||
}
|
||||
@ -237,16 +237,8 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
|
||||
if (requestCode == REQUEST_CODE_VPN && resultCode == RESULT_OK) {
|
||||
Intent intent = new Intent(MainActivity.this, CaptureService.class);
|
||||
Bundle bundle = new Bundle();
|
||||
String dns_server = Utils.getDnsServer(getApplicationContext());
|
||||
|
||||
Log.i("Main", "Using DNS server " + dns_server);
|
||||
|
||||
// the configuration for the VPN
|
||||
bundle.putString("dns_server", dns_server);
|
||||
bundle.putString(Prefs.PREF_COLLECTOR_IP_KEY, getCollectorIPPref());
|
||||
bundle.putInt(Prefs.PREF_COLLECTOR_PORT_KEY, Integer.parseInt(getCollectorPortPref()));
|
||||
bundle.putInt(Prefs.PREF_UID_FILTER, mFilterUid);
|
||||
bundle.putBoolean(Prefs.PREF_CAPTURE_UNKNOWN_APP_TRAFFIC, getCaptureUnknownTrafficPref());
|
||||
intent.putExtra("settings", bundle);
|
||||
|
||||
Log.d("Main", "onActivityResult -> start CaptureService");
|
||||
@ -399,16 +391,4 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa
|
||||
|
||||
alert.show();
|
||||
}
|
||||
|
||||
String getCollectorIPPref() {
|
||||
return(mPrefs.getString(Prefs.PREF_COLLECTOR_IP_KEY, getString(R.string.default_collector_ip)));
|
||||
}
|
||||
|
||||
String getCollectorPortPref() {
|
||||
return(mPrefs.getString(Prefs.PREF_COLLECTOR_PORT_KEY, getString(R.string.default_collector_port)));
|
||||
}
|
||||
|
||||
private boolean getCaptureUnknownTrafficPref() {
|
||||
return(mPrefs.getBoolean(Prefs.PREF_CAPTURE_UNKNOWN_APP_TRAFFIC, true));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.emanuelef.remote_capture;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class Prefs {
|
||||
static final String DUMP_HTTP_SERVER = "http_server";
|
||||
static final String DUMP_UDP_EXPORTER = "udp_exporter";
|
||||
@ -8,6 +10,7 @@ public class Prefs {
|
||||
static final String PREF_UID_FILTER = "uid_filter";
|
||||
static final String PREF_CAPTURE_UNKNOWN_APP_TRAFFIC = "capture_unknown_app";
|
||||
static final String PREF_HTTP_SERVER_PORT = "http_server_port";
|
||||
static final String PREF_PCAP_DUMP_MODE = "pcap_dump_mode";
|
||||
|
||||
enum DumpMode {
|
||||
NONE,
|
||||
@ -23,4 +26,11 @@ public class Prefs {
|
||||
else
|
||||
return(DumpMode.NONE);
|
||||
}
|
||||
|
||||
/* Prefs with defaults */
|
||||
static String getCollectorIp(SharedPreferences p) { return(p.getString(PREF_COLLECTOR_IP_KEY, "127.0.0.1")); }
|
||||
static int getCollectorPort(SharedPreferences p) { return(Integer.parseInt(p.getString(PREF_COLLECTOR_PORT_KEY, "1234"))); }
|
||||
static boolean getCaptureUnknownAppTraffic(SharedPreferences p) { return(p.getBoolean(PREF_CAPTURE_UNKNOWN_APP_TRAFFIC, true)); }
|
||||
static DumpMode getDumpMode(SharedPreferences p) { return(getDumpMode(p.getString(PREF_PCAP_DUMP_MODE, DUMP_HTTP_SERVER))); }
|
||||
static int getHttpServerPort(SharedPreferences p) { return(Integer.parseInt(p.getString(Prefs.PREF_HTTP_SERVER_PORT, "8080"))); }
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
||||
|
||||
mDumpModePref = findPreference("pcap_dump_mode");
|
||||
mDumpModePref = findPreference(Prefs.PREF_PCAP_DUMP_MODE);
|
||||
mDumpModePref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
package com.emanuelef.remote_capture;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
@ -24,6 +28,7 @@ public class StatusFragment extends Fragment implements AppStateListener {
|
||||
private TextView mCollectorInfo;
|
||||
private TextView mCaptureStatus;
|
||||
private MainActivity mActivity;
|
||||
private SharedPreferences mPrefs;
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
@ -35,6 +40,7 @@ public class StatusFragment extends Fragment implements AppStateListener {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mActivity.setStatusFragment(null);
|
||||
mActivity = null;
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@ -44,19 +50,40 @@ public class StatusFragment extends Fragment implements AppStateListener {
|
||||
return inflater.inflate(R.layout.status, container, false);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
mStartButton = view.findViewById(R.id.button_start);
|
||||
mCollectorInfo = view.findViewById(R.id.collector_info);
|
||||
mCaptureStatus = view.findViewById(R.id.status_view);
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(mActivity);
|
||||
|
||||
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mActivity);
|
||||
// Make URLs clickable
|
||||
mCollectorInfo.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
// Add settings icon click
|
||||
mCollectorInfo.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
Drawable mCollectorInfoDrawable = mCollectorInfo.getCompoundDrawables()[2 /* Right */];
|
||||
|
||||
if(event.getAction() == MotionEvent.ACTION_UP) {
|
||||
if(event.getRawX() >= (mCollectorInfo.getRight() - mCollectorInfoDrawable.getBounds().width())) {
|
||||
Intent intent = new Intent(mActivity, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mPrefs.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
|
||||
if(mActivity.getState() == MainActivity.AppState.ready)
|
||||
setCollectorInfo(mActivity.getCollectorIPPref(), mActivity.getCollectorPortPref());
|
||||
if((mActivity != null) && (mActivity.getState() == MainActivity.AppState.ready))
|
||||
refreshPcapDumpInfo();
|
||||
}
|
||||
});
|
||||
|
||||
@ -88,7 +115,7 @@ public class StatusFragment extends Fragment implements AppStateListener {
|
||||
mStartButton.setEnabled(true);
|
||||
mCaptureStatus.setText(R.string.ready);
|
||||
|
||||
setCollectorInfo(mActivity.getCollectorIPPref(), mActivity.getCollectorPortPref());
|
||||
refreshPcapDumpInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,8 +129,7 @@ public class StatusFragment extends Fragment implements AppStateListener {
|
||||
mStartButton.setEnabled(true);
|
||||
mCaptureStatus.setText(Utils.formatBytes(CaptureService.getBytes()));
|
||||
|
||||
setCollectorInfo(CaptureService.getCollectorAddress(),
|
||||
Integer.toString(CaptureService.getCollectorPort()));
|
||||
refreshPcapDumpInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -123,8 +149,35 @@ public class StatusFragment extends Fragment implements AppStateListener {
|
||||
mCaptureStatus.setText(Utils.formatBytes(bytes_sent + bytes_rcvd));
|
||||
}
|
||||
|
||||
private void setCollectorInfo(String collector_ip, String collector_port) {
|
||||
mCollectorInfo.setText(String.format(getResources().getString(R.string.collector_info),
|
||||
collector_ip, collector_port));
|
||||
private void refreshPcapDumpInfo() {
|
||||
String info;
|
||||
String modeName;
|
||||
|
||||
Prefs.DumpMode mode = CaptureService.isServiceActive() ? CaptureService.getDumpMode() : Prefs.getDumpMode(mPrefs);
|
||||
|
||||
switch (mode) {
|
||||
case HTTP_SERVER:
|
||||
modeName = getResources().getString(R.string.http_server);
|
||||
info = String.format(getResources().getString(R.string.http_server_status),
|
||||
Utils.getLocalIPAddress(), CaptureService.getHTTPServerPort());
|
||||
break;
|
||||
case UDP_EXPORTER:
|
||||
modeName = getResources().getString(R.string.udp_exporter);
|
||||
info = String.format(getResources().getString(R.string.collector_info),
|
||||
CaptureService.getCollectorAddress(), CaptureService.getCollectorPort());
|
||||
break;
|
||||
default:
|
||||
modeName = getResources().getString(R.string.no_dump);
|
||||
info = "";
|
||||
break;
|
||||
}
|
||||
|
||||
if(!CaptureService.isServiceActive()) {
|
||||
info = getResources().getString(R.string.dump_mode) + ": " + modeName;
|
||||
mCollectorInfo.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.settins_icon, 0);
|
||||
} else
|
||||
mCollectorInfo.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
|
||||
mCollectorInfo.setText(info);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,8 +12,10 @@ import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Utils {
|
||||
@ -107,8 +109,38 @@ public class Utils {
|
||||
return "8.8.8.8";
|
||||
}
|
||||
|
||||
public static String getLocalIPAddress() {
|
||||
// https://stackoverflow.com/questions/6064510/how-to-get-ip-address-of-the-device-from-code
|
||||
try {
|
||||
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
for (NetworkInterface intf : interfaces) {
|
||||
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
|
||||
for (InetAddress addr : addrs) {
|
||||
if (!addr.isLoopbackAddress() && addr.isSiteLocalAddress() /* Exclude public IPs */) {
|
||||
String sAddr = addr.getHostAddress();
|
||||
boolean isIPv4 = sAddr.indexOf(':')<0;
|
||||
|
||||
if(isIPv4)
|
||||
return sAddr;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) { }
|
||||
return "";
|
||||
}
|
||||
|
||||
public static long now() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
return(calendar.getTimeInMillis() / 1000);
|
||||
}
|
||||
|
||||
public static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include <sys/socket.h>
|
||||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
#include "pcap.h"
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
@ -30,27 +31,6 @@
|
||||
#define PCAP_TAG "PCAP_DUMP"
|
||||
#define SNAPLEN 65535
|
||||
|
||||
typedef uint16_t guint16_t;
|
||||
typedef uint32_t guint32_t;
|
||||
typedef int32_t gint32_t;
|
||||
|
||||
typedef struct pcap_hdr_s {
|
||||
guint32_t magic_number;
|
||||
guint16_t version_major;
|
||||
guint16_t version_minor;
|
||||
gint32_t thiszone;
|
||||
guint32_t sigfigs;
|
||||
guint32_t snaplen;
|
||||
guint32_t network;
|
||||
} __packed pcap_hdr_s;
|
||||
|
||||
typedef struct pcaprec_hdr_s {
|
||||
guint32_t ts_sec;
|
||||
guint32_t ts_usec;
|
||||
guint32_t incl_len;
|
||||
guint32_t orig_len;
|
||||
} __packed pcaprec_hdr_s;
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
static size_t frame_id = 1;
|
||||
@ -65,6 +45,30 @@ static void write_pcap(int fd, const struct sockaddr *srv, size_t srv_size, cons
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
static size_t init_pcap_rec_hdr(struct pcaprec_hdr_s *pcap_rec, int length) {
|
||||
size_t incl_len;
|
||||
struct timespec ts;
|
||||
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts))
|
||||
__android_log_print(ANDROID_LOG_ERROR, PCAP_TAG, "clock_gettime error[%d]: %s", errno, strerror(errno));
|
||||
|
||||
incl_len = (length < SNAPLEN ? length : SNAPLEN);
|
||||
|
||||
pcap_rec->ts_sec = (guint32_t) ts.tv_sec;
|
||||
pcap_rec->ts_usec = (guint32_t) (ts.tv_nsec / 1000);
|
||||
pcap_rec->incl_len = (guint32_t) incl_len;
|
||||
pcap_rec->orig_len = (guint32_t) length;
|
||||
|
||||
pcap_rec->ts_sec = (guint32_t) ts.tv_sec;
|
||||
pcap_rec->ts_usec = (guint32_t) (ts.tv_nsec / 1000);
|
||||
pcap_rec->incl_len = (guint32_t) incl_len;
|
||||
pcap_rec->orig_len = (guint32_t) length;
|
||||
|
||||
return(incl_len);
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
void write_pcap_hdr(int fd, const struct sockaddr *srv, size_t srv_size) {
|
||||
struct pcap_hdr_s pcap_hdr;
|
||||
pcap_hdr.magic_number = 0xa1b2c3d4;
|
||||
@ -80,23 +84,28 @@ void write_pcap_hdr(int fd, const struct sockaddr *srv, size_t srv_size) {
|
||||
/* ******************************************************* */
|
||||
|
||||
void write_pcap_rec(int fd, const struct sockaddr *srv, size_t srv_size, const uint8_t *buffer, size_t length) {
|
||||
size_t incl_len, tot_len;
|
||||
struct timespec ts;
|
||||
struct pcaprec_hdr_s *pcap_rec;
|
||||
struct pcaprec_hdr_s *pcap_rec = (struct pcaprec_hdr_s *) pcap_buffer;
|
||||
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts))
|
||||
__android_log_print(ANDROID_LOG_ERROR, PCAP_TAG, "clock_gettime error[%d]: %s", errno, strerror(errno));
|
||||
|
||||
incl_len = (length < SNAPLEN ? length : SNAPLEN);
|
||||
tot_len = sizeof(struct pcaprec_hdr_s) + incl_len;
|
||||
|
||||
pcap_rec = (struct pcaprec_hdr_s *) pcap_buffer;
|
||||
pcap_rec->ts_sec = (guint32_t) ts.tv_sec;
|
||||
pcap_rec->ts_usec = (guint32_t) (ts.tv_nsec / 1000);
|
||||
pcap_rec->incl_len = (guint32_t) incl_len;
|
||||
pcap_rec->orig_len = (guint32_t) length;
|
||||
size_t incl_len = init_pcap_rec_hdr(pcap_rec, length);
|
||||
size_t tot_len = sizeof(struct pcaprec_hdr_s) + incl_len;
|
||||
|
||||
// NOTE: use incl_size as the packet may be cut due to the SNAPLEN
|
||||
memcpy(pcap_buffer + sizeof(struct pcaprec_hdr_s), buffer, incl_len);
|
||||
|
||||
write_pcap(fd, srv, srv_size, pcap_rec, tot_len);
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
size_t dump_pcap_rec(uint8_t *buffer, const uint8_t *pkt, size_t pkt_len) {
|
||||
struct pcaprec_hdr_s *pcap_rec = (pcaprec_hdr_s*) buffer;
|
||||
|
||||
size_t incl_len = init_pcap_rec_hdr(pcap_rec, pkt_len);
|
||||
size_t tot_len = sizeof(struct pcaprec_hdr_s) + incl_len;
|
||||
|
||||
// NOTE: use incl_size as the packet may be cut due to the SNAPLEN
|
||||
// Assumption: there is enough available space in buffer
|
||||
memcpy(buffer + sizeof(struct pcaprec_hdr_s), pkt, incl_len);
|
||||
|
||||
return(tot_len);
|
||||
}
|
||||
@ -17,5 +17,32 @@
|
||||
Copyright 2019 by Emanuele Faranda
|
||||
*/
|
||||
|
||||
#ifndef __MY_PCAP_H__
|
||||
#define __MY_PCAP_H__
|
||||
|
||||
typedef uint16_t guint16_t;
|
||||
typedef uint32_t guint32_t;
|
||||
typedef int32_t gint32_t;
|
||||
|
||||
typedef struct pcap_hdr_s {
|
||||
guint32_t magic_number;
|
||||
guint16_t version_major;
|
||||
guint16_t version_minor;
|
||||
gint32_t thiszone;
|
||||
guint32_t sigfigs;
|
||||
guint32_t snaplen;
|
||||
guint32_t network;
|
||||
} __packed pcap_hdr_s;
|
||||
|
||||
typedef struct pcaprec_hdr_s {
|
||||
guint32_t ts_sec;
|
||||
guint32_t ts_usec;
|
||||
guint32_t incl_len;
|
||||
guint32_t orig_len;
|
||||
} __packed pcaprec_hdr_s;
|
||||
|
||||
void write_pcap_hdr(int fd, const struct sockaddr *srv, size_t srv_size);
|
||||
void write_pcap_rec(int fd, const struct sockaddr *srv, size_t srv_size, const uint8_t *buffer, size_t length);
|
||||
size_t dump_pcap_rec(uint8_t *buffer, const uint8_t *pkt, size_t pkt_len);
|
||||
|
||||
#endif // __MY_PCAP_H__
|
||||
@ -27,7 +27,9 @@
|
||||
#define VPN_TAG "VPNProxy"
|
||||
#define CAPTURE_STATS_UPDATE_FREQUENCY_MS 300
|
||||
#define CONNECTION_DUMP_UPDATE_FREQUENCY_MS 3000
|
||||
#define MAX_JAVA_DUMP_DELAY_MS 1000
|
||||
#define MAX_DPI_PACKETS 12
|
||||
#define JAVA_PCAP_BUFFER_SIZE (1*1024*1204)
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
@ -299,6 +301,27 @@ static void process_ndpi_packet(conn_data_t *data, vpnproxy_data_t *proxy, const
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
static void javaPcapDump(zdtun_t *tun, vpnproxy_data_t *proxy) {
|
||||
JNIEnv *env = proxy->env;
|
||||
jclass vpn_service_cls = (*env)->GetObjectClass(env, proxy->vpn_service);
|
||||
|
||||
jmethodID midMethod = (*env)->GetMethodID(env, vpn_service_cls, "dumpPcapData", "([B)V");
|
||||
if(!midMethod) {
|
||||
__android_log_print(ANDROID_LOG_FATAL, VPN_TAG, "GetMethodID(dumpPcapData) failed");
|
||||
return;
|
||||
}
|
||||
|
||||
jbyteArray barray = (*env)->NewByteArray(env, proxy->java_dump.buffer_idx);
|
||||
(*env)->SetByteArrayRegion(env, barray, 0, proxy->java_dump.buffer_idx, proxy->java_dump.buffer);
|
||||
|
||||
(*env)->CallVoidMethod(env, proxy->vpn_service, midMethod, barray);
|
||||
|
||||
proxy->java_dump.buffer_idx = 0;
|
||||
proxy->java_dump.last_dump_ms = proxy->now_ms;
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
static void account_packet(zdtun_t *tun, const char *packet, ssize_t size, uint8_t from_tap, const zdtun_conn_t *conn_info) {
|
||||
struct sockaddr_in servaddr = {0};
|
||||
conn_data_t *data = (conn_data_t*)conn_info->user_data;
|
||||
@ -336,9 +359,9 @@ static void account_packet(zdtun_t *tun, const char *packet, ssize_t size, uint8
|
||||
if(data->ndpi_flow)
|
||||
process_ndpi_packet(data, proxy, packet, size, from_tap);
|
||||
|
||||
if(((proxy->pcap_dump.uid_filter != -1) && (proxy->pcap_dump.uid_filter != uid))
|
||||
&& (!is_unknown_app || !proxy->pcap_dump.capture_unknown_app_traffic)) {
|
||||
//__android_log_print(ANDROID_LOG_DEBUG, VPN_TAG, "Discarding connection: UID=%d [filter=%d]", uid, proxy->pcap_dump.uid_filter);
|
||||
if(((proxy->uid_filter != -1) && (proxy->uid_filter != uid))
|
||||
&& (!is_unknown_app || !proxy->capture_unknown_app_traffic)) {
|
||||
//__android_log_print(ANDROID_LOG_DEBUG, VPN_TAG, "Discarding connection: UID=%d [filter=%d]", uid, proxy->uid_filter);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -353,21 +376,30 @@ static void account_packet(zdtun_t *tun, const char *packet, ssize_t size, uint8
|
||||
/* New stats to notify */
|
||||
proxy->capture_stats.new_stats = true;
|
||||
|
||||
if(dumper_socket <= 0)
|
||||
return;
|
||||
if(proxy->java_dump.buffer) {
|
||||
int rem_size = JAVA_PCAP_BUFFER_SIZE - proxy->java_dump.buffer_idx;
|
||||
|
||||
#if 1
|
||||
servaddr.sin_family = AF_INET;
|
||||
servaddr.sin_port = proxy->pcap_dump.collector_port;
|
||||
servaddr.sin_addr.s_addr = proxy->pcap_dump.collector_addr;
|
||||
if((size + sizeof(pcaprec_hdr_s)) > rem_size) {
|
||||
// Flush the buffer
|
||||
javaPcapDump(tun, proxy);
|
||||
}
|
||||
|
||||
if(send_header) {
|
||||
write_pcap_hdr(dumper_socket, (struct sockaddr *) &servaddr, sizeof(servaddr));
|
||||
send_header = false;
|
||||
proxy->java_dump.buffer_idx += dump_pcap_rec(proxy->java_dump.buffer + proxy->java_dump.buffer_idx, packet, size);
|
||||
}
|
||||
|
||||
write_pcap_rec(dumper_socket, (struct sockaddr *)&servaddr, sizeof(servaddr), (u_int8_t*)packet, size);
|
||||
#endif
|
||||
if(dumper_socket > 0) {
|
||||
servaddr.sin_family = AF_INET;
|
||||
servaddr.sin_port = proxy->pcap_dump.collector_port;
|
||||
servaddr.sin_addr.s_addr = proxy->pcap_dump.collector_addr;
|
||||
|
||||
if (send_header) {
|
||||
write_pcap_hdr(dumper_socket, (struct sockaddr *) &servaddr, sizeof(servaddr));
|
||||
send_header = false;
|
||||
}
|
||||
|
||||
write_pcap_rec(dumper_socket, (struct sockaddr *) &servaddr, sizeof(servaddr),
|
||||
(u_int8_t *) packet, size);
|
||||
}
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
@ -816,13 +848,16 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
|
||||
.vpn_dns = getIPv4Pref(&proxy, "getVpnDns"),
|
||||
.public_dns = getIPv4Pref(&proxy, "getPublicDns"),
|
||||
.incr_id = 0,
|
||||
.uid_filter = getIntPref(&proxy, "getPcapUidFilter"),
|
||||
.capture_unknown_app_traffic = getIntPref(&proxy, "getCaptureUnknownTraffic"),
|
||||
.java_dump = {
|
||||
.enabled = getIntPref(&proxy, "dumpPcapToJava"),
|
||||
},
|
||||
.pcap_dump = {
|
||||
.collector_addr = getIPv4Pref(&proxy, "getPcapCollectorAddress"),
|
||||
.collector_port = htons(getIntPref(&proxy, "getPcapCollectorPort")),
|
||||
.uid_filter = getIntPref(&proxy, "getPcapUidFilter"),
|
||||
.tcp_socket = false,
|
||||
.capture_unknown_app_traffic = getIntPref(&proxy, "getCaptureUnknownTraffic"),
|
||||
.enabled = true,
|
||||
.enabled = getIntPref(&proxy, "dumpPcapToUdp"),
|
||||
},
|
||||
};
|
||||
zdtun_callbacks_t callbacks = {
|
||||
@ -873,6 +908,17 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
|
||||
running = false;
|
||||
}
|
||||
|
||||
if(proxy.java_dump.enabled) {
|
||||
proxy.java_dump.buffer = malloc(JAVA_PCAP_BUFFER_SIZE);
|
||||
proxy.java_dump.buffer_idx = 0;
|
||||
|
||||
if(!proxy.java_dump.buffer) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, VPN_TAG, "malloc(java_dump.buffer) failed with code %d/%s",
|
||||
errno, strerror(errno));
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
while(running) {
|
||||
int max_fd;
|
||||
fd_set fdset;
|
||||
@ -894,6 +940,7 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
|
||||
|
||||
gettimeofday(&now_tv, NULL);
|
||||
now_ms = now_tv.tv_sec * 1000 + now_tv.tv_usec / 1000;
|
||||
proxy.now_ms = now_ms;
|
||||
|
||||
if(FD_ISSET(tapfd, &fdset)) {
|
||||
/* Packet from VPN */
|
||||
@ -920,11 +967,12 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
|
||||
sendCaptureStats(&proxy);
|
||||
proxy.capture_stats.new_stats = false;
|
||||
proxy.capture_stats.last_update_ms = now_ms;
|
||||
}
|
||||
|
||||
if((now_ms - last_connections_dump) >= CONNECTION_DUMP_UPDATE_FREQUENCY_MS) {
|
||||
} else if((now_ms - last_connections_dump) >= CONNECTION_DUMP_UPDATE_FREQUENCY_MS) {
|
||||
sendConnectionsDump(tun, &proxy);
|
||||
last_connections_dump = now_ms;
|
||||
} else if((proxy.java_dump.buffer_idx > 0)
|
||||
&& (now_ms - proxy.java_dump.last_dump_ms) >= MAX_JAVA_DUMP_DELAY_MS) {
|
||||
javaPcapDump(tun, &proxy);
|
||||
}
|
||||
}
|
||||
|
||||
@ -948,6 +996,11 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
|
||||
dumper_socket = -1;
|
||||
}
|
||||
|
||||
if(proxy.java_dump.buffer) {
|
||||
free(proxy.java_dump.buffer);
|
||||
proxy.java_dump.buffer = NULL;
|
||||
}
|
||||
|
||||
notifyServiceStatus(&proxy, "stopped");
|
||||
return(0);
|
||||
}
|
||||
|
||||
@ -49,16 +49,24 @@ typedef struct vpnproxy_data {
|
||||
zdtun_conn_t *notif_pending;
|
||||
u_int32_t cur_notif_pending;
|
||||
u_int32_t notif_pending_size;
|
||||
int uid_filter;
|
||||
bool capture_unknown_app_traffic;
|
||||
uint64_t now_ms;
|
||||
|
||||
struct {
|
||||
u_int32_t collector_addr;
|
||||
u_int16_t collector_port;
|
||||
int uid_filter;
|
||||
bool tcp_socket;
|
||||
bool capture_unknown_app_traffic;
|
||||
bool enabled;
|
||||
} pcap_dump;
|
||||
|
||||
struct {
|
||||
bool enabled;
|
||||
u_char *buffer;
|
||||
int buffer_idx;
|
||||
u_int64_t last_dump_ms;
|
||||
} java_dump;
|
||||
|
||||
capture_stats_t capture_stats;
|
||||
} vpnproxy_data_t;
|
||||
|
||||
|
||||
11
app/src/main/res/drawable-anydpi/settins_icon.xml
Normal file
11
app/src/main/res/drawable-anydpi/settins_icon.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable-hdpi/settins_icon.png
Normal file
BIN
app/src/main/res/drawable-hdpi/settins_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 534 B |
BIN
app/src/main/res/drawable-mdpi/settins_icon.png
Normal file
BIN
app/src/main/res/drawable-mdpi/settins_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 393 B |
BIN
app/src/main/res/drawable-xhdpi/settins_icon.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/settins_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 680 B |
BIN
app/src/main/res/drawable-xxhdpi/settins_icon.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/settins_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1005 B |
@ -23,7 +23,10 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="92dp"
|
||||
android:gravity="center"
|
||||
android:text="TextView"
|
||||
android:autoLink="web"
|
||||
android:drawablePadding="5dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@ -4,12 +4,14 @@
|
||||
<string name="stop_button">Stop</string>
|
||||
<string name="title_activity_settings">Settings</string>
|
||||
<string name="no_filter">No Filter</string>
|
||||
<string name="collector_info">UDP Collector: %1$s:%2$s</string>
|
||||
<string name="collector_info">UDP Collector: %1$s:%2$d</string>
|
||||
<string name="http_server_status">HTTP Server: http://%1$s:%2$d</string>
|
||||
<string name="ip_and_port">%1$s:%2$d</string>
|
||||
<string name="up_and_down">%1$s down — %2$s up</string>
|
||||
<string name="app_and_proto">%1$s (%2$s)</string>
|
||||
<string name="query">Query</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="capture_not_started">Capture not running!</string>
|
||||
|
||||
<!-- Defaults -->
|
||||
<string name="default_collector_ip">127.0.0.1</string>
|
||||
@ -45,9 +47,10 @@
|
||||
<string name="no_dump">None</string>
|
||||
<string name="no_dump_info">PCAP will not be dumped</string>
|
||||
<string name="http_server_info">Start an HTTP server for the PCAP download</string>
|
||||
<string name="udp_exporter_info">Sends the PCAP to an UDP receiver</string>
|
||||
<string name="udp_exporter_info">Sends the PCAP to a remote UDP receiver</string>
|
||||
<string name="http_server_port">HTTP Server Port</string>
|
||||
<string name="receiver_ip_address">Receiver IP Address</string>
|
||||
<string name="receiver_port">Receiver Port</string>
|
||||
<string name="dump_mode">Dump Mode</string>
|
||||
</resources>
|
||||
|
||||
|
||||
@ -14,13 +14,12 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<PreferenceCategory app:title="PCAP Dump" app:iconSpaceReserved="false">
|
||||
<ListPreference
|
||||
app:key="pcap_dump_mode"
|
||||
app:title="Dump Mode"
|
||||
app:title="@string/dump_mode"
|
||||
app:iconSpaceReserved="false"
|
||||
app:summary="@string/no_dump_info"
|
||||
app:defaultValue="http_server"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user