mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-06-16 21:10:57 +08:00
Refactor PCAP dump
PCAP dump now always occurs in Java
This commit is contained in:
parent
258930d110
commit
91cc995eff
@ -58,12 +58,15 @@ import com.emanuelef.remote_capture.model.ConnectionDescriptor;
|
||||
import com.emanuelef.remote_capture.model.ConnectionUpdate;
|
||||
import com.emanuelef.remote_capture.model.Prefs;
|
||||
import com.emanuelef.remote_capture.model.VPNStats;
|
||||
import com.emanuelef.remote_capture.pcap_dump.FileDumper;
|
||||
import com.emanuelef.remote_capture.pcap_dump.HTTPServer;
|
||||
import com.emanuelef.remote_capture.interfaces.PcapDumper;
|
||||
import com.emanuelef.remote_capture.pcap_dump.UDPDumper;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class CaptureService extends VpnService implements Runnable {
|
||||
@ -91,11 +94,9 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
private static CaptureService INSTANCE;
|
||||
private String app_filter;
|
||||
private int app_filter_uid;
|
||||
private HTTPServer mHttpServer;
|
||||
private OutputStream mOutputStream;
|
||||
private PcapDumper mDumper;
|
||||
private ConnectionsRegister conn_reg;
|
||||
private Uri mPcapUri;
|
||||
private boolean mFirstStreamWrite;
|
||||
private NotificationCompat.Builder mNotificationBuilder;
|
||||
private long mMonitoredNetwork;
|
||||
private ConnectivityManager.NetworkCallback mNetworkCallback;
|
||||
@ -139,15 +140,23 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
private int abortStart() {
|
||||
// NOTE: startForeground must be called before stopSelf, otherwise an exception will occur
|
||||
setupNotifications();
|
||||
startForeground(NOTIFY_ID_VPNSERVICE, getNotification());
|
||||
|
||||
stopSelf();
|
||||
sendServiceStatus(SERVICE_STATUS_STOPPED);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Context app_ctx = getApplicationContext();
|
||||
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
if (intent == null) {
|
||||
Log.d(CaptureService.TAG, "NULL intent onStartCommand");
|
||||
return super.onStartCommand(null, flags, startId);
|
||||
return abortStart();
|
||||
}
|
||||
|
||||
Log.d(CaptureService.TAG, "onStartCommand");
|
||||
@ -157,7 +166,7 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
|
||||
if (settings == null) {
|
||||
Log.e(CaptureService.TAG, "NULL settings");
|
||||
return super.onStartCommand(null, flags, startId);
|
||||
return abortStart();
|
||||
}
|
||||
|
||||
// Retrieve DNS server
|
||||
@ -198,40 +207,41 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
last_connections = 0;
|
||||
root_capture = Prefs.isRootCaptureEnabled(prefs);
|
||||
conn_reg = new ConnectionsRegister(CONNECTIONS_LOG_SIZE);
|
||||
|
||||
if(dump_mode != Prefs.DumpMode.HTTP_SERVER)
|
||||
mHttpServer = null;
|
||||
mOutputStream = null;
|
||||
mPcapUri = null;
|
||||
mDumper = null;
|
||||
|
||||
if(dump_mode == Prefs.DumpMode.HTTP_SERVER) {
|
||||
if (mHttpServer == null)
|
||||
mHttpServer = new HTTPServer(app_ctx, http_server_port);
|
||||
|
||||
try {
|
||||
mHttpServer.startConnections();
|
||||
} catch (IOException e) {
|
||||
Log.e(CaptureService.TAG, "Could not start the HTTP server");
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if(dump_mode == Prefs.DumpMode.PCAP_FILE) {
|
||||
// Possibly allocate the dumper
|
||||
if(dump_mode == Prefs.DumpMode.HTTP_SERVER)
|
||||
mDumper = new HTTPServer(this, http_server_port);
|
||||
else if(dump_mode == Prefs.DumpMode.PCAP_FILE) {
|
||||
String path = settings.getString(Prefs.PREF_PCAP_URI);
|
||||
|
||||
if(path != null) {
|
||||
mPcapUri = Uri.parse(path);
|
||||
mDumper = new FileDumper(this, mPcapUri);
|
||||
}
|
||||
} else if(dump_mode == Prefs.DumpMode.UDP_EXPORTER) {
|
||||
InetAddress addr;
|
||||
|
||||
try {
|
||||
Log.d(TAG, "PCAP URI: " + mPcapUri);
|
||||
mOutputStream = getContentResolver().openOutputStream(mPcapUri);
|
||||
mFirstStreamWrite = true;
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
addr = InetAddress.getByName(Prefs.getCollectorIp(prefs));
|
||||
} catch (UnknownHostException e) {
|
||||
reportError(e.getLocalizedMessage());
|
||||
e.printStackTrace();
|
||||
return abortStart();
|
||||
}
|
||||
|
||||
if(mOutputStream == null) {
|
||||
Utils.showToast(this, R.string.cannot_write_pcap_file);
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
mDumper = new UDPDumper(new InetSocketAddress(addr, Prefs.getCollectorPort(prefs)));
|
||||
}
|
||||
|
||||
if(mDumper != null) {
|
||||
try {
|
||||
mDumper.startDumper();
|
||||
} catch (IOException e) {
|
||||
reportError(e.getLocalizedMessage());
|
||||
e.printStackTrace();
|
||||
mDumper = null;
|
||||
return abortStart();
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,7 +291,7 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
String msg = String.format(getResources().getString(R.string.app_not_found), app_filter);
|
||||
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
return abortStart();
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,7 +299,7 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
mParcelFileDescriptor = builder.setSession(CaptureService.VpnSessionName).establish();
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
Utils.showToast(this, R.string.vpn_setup_failed);
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
return abortStart();
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,9 +314,7 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
|
||||
setupNotifications();
|
||||
startForeground(NOTIFY_ID_VPNSERVICE, getNotification());
|
||||
|
||||
return START_STICKY;
|
||||
//return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -326,8 +334,14 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
if(mThread != null) {
|
||||
mThread.interrupt();
|
||||
}
|
||||
if(mHttpServer != null)
|
||||
mHttpServer.stop();
|
||||
if(mDumper != null) {
|
||||
try {
|
||||
mDumper.stopDumper();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
mDumper = null;
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
@ -453,16 +467,13 @@ 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
|
||||
|
||||
if(mOutputStream != null) {
|
||||
if(mDumper != null) {
|
||||
try {
|
||||
mOutputStream.close();
|
||||
mDumper.stopDumper();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
mDumper = null;
|
||||
}
|
||||
|
||||
mPcapUri = null;
|
||||
@ -520,6 +531,12 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
INSTANCE.stop();
|
||||
}
|
||||
|
||||
public static @NonNull CaptureService requireInstance() {
|
||||
CaptureService inst = INSTANCE;
|
||||
assert(inst != null);
|
||||
return(inst);
|
||||
}
|
||||
|
||||
public static @Nullable ConnectionsRegister getConnsRegister() {
|
||||
return((INSTANCE != null) ? INSTANCE.conn_reg : null);
|
||||
}
|
||||
@ -574,14 +591,6 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
|
||||
public String getIpv6DnsServer() { return(IPV6_DNS_SERVER); }
|
||||
|
||||
public String getPcapCollectorAddress() {
|
||||
return(collector_address);
|
||||
}
|
||||
|
||||
public int getPcapCollectorPort() {
|
||||
return(collector_port);
|
||||
}
|
||||
|
||||
public String getSocks5ProxyAddress() { return(socks5_proxy_address); }
|
||||
|
||||
public int getSocks5Enabled() { return socks5_enabled ? 1 : 0; }
|
||||
@ -604,12 +613,17 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
}
|
||||
|
||||
// returns 1 if dumpPcapData should be called
|
||||
public int dumpPcapToJava() {
|
||||
return(((dump_mode == Prefs.DumpMode.HTTP_SERVER) || (dump_mode == Prefs.DumpMode.PCAP_FILE)) ? 1 : 0);
|
||||
public int pcapDumpEnabled() {
|
||||
return((dump_mode != Prefs.DumpMode.NONE) ? 1 : 0);
|
||||
}
|
||||
|
||||
public int dumpPcapToUdp() {
|
||||
return((dump_mode == Prefs.DumpMode.UDP_EXPORTER) ? 1 : 0);
|
||||
@Override
|
||||
public boolean protect(int socket) {
|
||||
// Do not call protect in root mode
|
||||
if(root_capture)
|
||||
return true;
|
||||
|
||||
return super.protect(socket);
|
||||
}
|
||||
|
||||
// from NetGuard
|
||||
@ -668,16 +682,9 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
|
||||
/* Exports a PCAP data chunk */
|
||||
public void dumpPcapData(byte[] data) {
|
||||
if(mHttpServer != null)
|
||||
mHttpServer.pushData(data);
|
||||
else if(mOutputStream != null) {
|
||||
if(mDumper != null) {
|
||||
try {
|
||||
if(mFirstStreamWrite) {
|
||||
mOutputStream.write(Utils.hexStringToByteArray(Utils.PCAP_HEADER));
|
||||
mFirstStreamWrite = false;
|
||||
}
|
||||
|
||||
mOutputStream.write(data);
|
||||
mDumper.dumpData(data);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
reportError(e.getLocalizedMessage());
|
||||
|
||||
@ -75,17 +75,20 @@ import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Utils {
|
||||
public static final String PCAP_HEADER = "d4c3b2a1020004000000000000000000ffff000065000000";
|
||||
public static final byte[] PCAP_HEADER = Utils.hexStringToByteArray("d4c3b2a1020004000000000000000000ffff000065000000");
|
||||
public static final int UID_UNKNOWN = -1;
|
||||
public static final int UID_NO_FILTER = -2;
|
||||
private static Boolean rootAvailable = null;
|
||||
@ -318,6 +321,28 @@ public class Utils {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Splits the provided data into individual PCAP records. Intended to be used with data received
|
||||
// via CaptureService::dumpPcapData
|
||||
public static Iterator<Integer> iterPcapRecords(byte[] data) {
|
||||
final ByteBuffer buf = ByteBuffer.wrap(data);
|
||||
buf.order(ByteOrder.nativeOrder());
|
||||
|
||||
return new Iterator<Integer>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
// 16: sizeof(pcaprec_hdr_s)
|
||||
return(buf.remaining() > 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer next() {
|
||||
int rec_len = buf.getInt(buf.position() + 8) + 16;
|
||||
buf.position(buf.position() + rec_len);
|
||||
return rec_len;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean hasVPNRunning(Context context) {
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package com.emanuelef.remote_capture.interfaces;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** A dumper implements the ability to dump PCAP data.
|
||||
* It has the following lifecycle:
|
||||
*
|
||||
* startDumper -> ... dumpData ... -> stopDumper
|
||||
*/
|
||||
public interface PcapDumper {
|
||||
/**
|
||||
* Starts the dumper.
|
||||
* @throws IOException
|
||||
*/
|
||||
void startDumper() throws IOException;
|
||||
|
||||
/**
|
||||
* Terminates the dumper.
|
||||
* @throws IOException
|
||||
*/
|
||||
void stopDumper() throws IOException;
|
||||
|
||||
/**
|
||||
* Dump an unspecified number of PCAP records. The dumper must check if this is the first data
|
||||
* sent, in which case it should send the Utils.PCAP_HEADER bofore the PCAP records data.
|
||||
* @throws IOException
|
||||
*/
|
||||
void dumpData(byte[] data) throws IOException;
|
||||
}
|
||||
@ -17,7 +17,9 @@
|
||||
* Copyright 2020-21 - Emanuele Faranda
|
||||
*/
|
||||
|
||||
package com.emanuelef.remote_capture;
|
||||
package com.emanuelef.remote_capture.pcap_dump;
|
||||
|
||||
import com.emanuelef.remote_capture.Utils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
@ -29,17 +31,16 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
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(Utils.PCAP_HEADER);
|
||||
public class ChunkedInputStream extends InputStream {
|
||||
final Lock mLock = new ReentrantLock();
|
||||
final Condition newData = mLock.newCondition();
|
||||
ArrayList<byte[]> mChunks = new ArrayList<byte[]>();
|
||||
int mCurChunkIndex = 0;
|
||||
boolean hasFinished = false;
|
||||
|
||||
ChunkedInputStream() {
|
||||
public ChunkedInputStream() {
|
||||
// Send the PCAP header as the first chunk
|
||||
mChunks.add(pcapHeader);
|
||||
mChunks.add(Utils.PCAP_HEADER);
|
||||
}
|
||||
|
||||
/* Mark the termination of stream */
|
||||
@ -0,0 +1,46 @@
|
||||
package com.emanuelef.remote_capture.pcap_dump;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.emanuelef.remote_capture.Utils;
|
||||
import com.emanuelef.remote_capture.interfaces.PcapDumper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class FileDumper implements PcapDumper {
|
||||
public static final String TAG = "FileDumper";
|
||||
private final Context mContext;
|
||||
private final Uri mPcapUri;
|
||||
private boolean mSendHeader;
|
||||
private OutputStream mOutputStream;
|
||||
|
||||
public FileDumper(Context ctx, Uri pcap_uri) {
|
||||
mContext = ctx;
|
||||
mPcapUri = pcap_uri;
|
||||
mSendHeader = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDumper() throws IOException {
|
||||
Log.d(TAG, "PCAP URI: " + mPcapUri);
|
||||
mOutputStream = mContext.getContentResolver().openOutputStream(mPcapUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopDumper() throws IOException {
|
||||
mOutputStream.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpData(byte[] data) throws IOException {
|
||||
if(mSendHeader) {
|
||||
mSendHeader = false;
|
||||
mOutputStream.write(Utils.PCAP_HEADER);
|
||||
}
|
||||
|
||||
mOutputStream.write(data);
|
||||
}
|
||||
}
|
||||
@ -17,19 +17,22 @@
|
||||
* Copyright 2020-21 - Emanuele Faranda
|
||||
*/
|
||||
|
||||
package com.emanuelef.remote_capture;
|
||||
package com.emanuelef.remote_capture.pcap_dump;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.emanuelef.remote_capture.R;
|
||||
import com.emanuelef.remote_capture.Utils;
|
||||
import com.emanuelef.remote_capture.interfaces.PcapDumper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import fi.iki.elonen.NanoHTTPD;
|
||||
import fi.iki.elonen.NanoHTTPD.Response.Status;
|
||||
|
||||
public class HTTPServer extends NanoHTTPD {
|
||||
public class HTTPServer extends NanoHTTPD implements PcapDumper {
|
||||
private static final String PCAP_MIME = "application/vnd.tcpdump.pcap";
|
||||
private boolean firstStart = true;
|
||||
private boolean mAcceptConnections = false;
|
||||
private final Context mContext;
|
||||
|
||||
@ -58,54 +61,6 @@ public class HTTPServer extends NanoHTTPD {
|
||||
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)
|
||||
@ -119,4 +74,50 @@ public class HTTPServer extends NanoHTTPD {
|
||||
|
||||
return newPcapStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDumper() throws IOException {
|
||||
mAcceptConnections = true;
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopDumper() throws IOException {
|
||||
synchronized (this) {
|
||||
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;
|
||||
}
|
||||
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpData(byte[] data) throws IOException {
|
||||
synchronized (this) {
|
||||
/* Dispatch PCAP data to the active connections */
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package com.emanuelef.remote_capture.pcap_dump;
|
||||
|
||||
import com.emanuelef.remote_capture.CaptureService;
|
||||
import com.emanuelef.remote_capture.Utils;
|
||||
import com.emanuelef.remote_capture.interfaces.PcapDumper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class UDPDumper implements PcapDumper {
|
||||
public static final String TAG = "UDPDumper";
|
||||
private final SocketAddress mServer;
|
||||
private boolean mSendHeader;
|
||||
private DatagramSocket mSocket;
|
||||
|
||||
public UDPDumper(SocketAddress server) {
|
||||
mServer = server;
|
||||
mSendHeader = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDumper() throws IOException {
|
||||
mSocket = new DatagramSocket();
|
||||
CaptureService.requireInstance().protect(mSocket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopDumper() throws IOException {
|
||||
mSocket.close();
|
||||
}
|
||||
|
||||
private void sendDatagram(byte[] data, int offset, int len) throws IOException {
|
||||
DatagramPacket request = new DatagramPacket(data, offset, len, mServer);
|
||||
mSocket.send(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpData(byte[] data) throws IOException {
|
||||
if(mSendHeader) {
|
||||
mSendHeader = false;
|
||||
sendDatagram(Utils.PCAP_HEADER, 0, Utils.PCAP_HEADER.length);
|
||||
}
|
||||
|
||||
Iterator<Integer> it = Utils.iterPcapRecords(data);
|
||||
int pos = 0;
|
||||
|
||||
while(it.hasNext()) {
|
||||
int rec_len = it.next();
|
||||
sendDatagram(data, pos, rec_len);
|
||||
pos += rec_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,20 +83,6 @@ void write_pcap_hdr(int fd, const struct sockaddr *srv, int srv_size) {
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
void write_pcap_rec(int fd, const struct sockaddr *srv, int srv_size, const uint8_t *buffer, int length) {
|
||||
struct pcaprec_hdr_s *pcap_rec = (struct pcaprec_hdr_s *) pcap_buffer;
|
||||
|
||||
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(u_char *buffer, const u_char *pkt, int pkt_len) {
|
||||
struct pcaprec_hdr_s *pcap_rec = (pcaprec_hdr_s*) buffer;
|
||||
|
||||
|
||||
@ -42,7 +42,6 @@ typedef struct pcaprec_hdr_s {
|
||||
} __packed pcaprec_hdr_s;
|
||||
|
||||
void write_pcap_hdr(int fd, const struct sockaddr *srv, int srv_size);
|
||||
void write_pcap_rec(int fd, const struct sockaddr *srv, int srv_size, const uint8_t *buffer, int length);
|
||||
size_t dump_pcap_rec(u_char *buffer, const u_char *pkt, int pkt_len);
|
||||
|
||||
#endif // __MY_PCAP_H__
|
||||
|
||||
@ -36,8 +36,6 @@ static ndpi_protocol_bitmask_struct_t masterProtos;
|
||||
/* ******************************************************* */
|
||||
|
||||
/* NOTE: these must be reset during each run, as android may reuse the service */
|
||||
static int dumper_socket;
|
||||
static bool send_header;
|
||||
static int netd_resolve_waiting;
|
||||
static u_int64_t last_connections_dump;
|
||||
static u_int64_t next_connections_dump;
|
||||
@ -561,18 +559,18 @@ static void process_ndpi_packet(conn_data_t *data, vpnproxy_data_t *proxy,
|
||||
static void javaPcapDump(vpnproxy_data_t *proxy) {
|
||||
JNIEnv *env = proxy->env;
|
||||
|
||||
log_d("Exporting a %d B PCAP buffer", proxy->java_dump.buffer_idx);
|
||||
log_d("Exporting a %d B PCAP buffer", proxy->pcap_dump.buffer_idx);
|
||||
|
||||
jbyteArray barray = (*env)->NewByteArray(env, proxy->java_dump.buffer_idx);
|
||||
jbyteArray barray = (*env)->NewByteArray(env, proxy->pcap_dump.buffer_idx);
|
||||
if(jniCheckException(env))
|
||||
return;
|
||||
|
||||
(*env)->SetByteArrayRegion(env, barray, 0, proxy->java_dump.buffer_idx, proxy->java_dump.buffer);
|
||||
(*env)->SetByteArrayRegion(env, barray, 0, proxy->pcap_dump.buffer_idx, proxy->pcap_dump.buffer);
|
||||
(*env)->CallVoidMethod(env, proxy->vpn_service, mids.dumpPcapData, barray);
|
||||
jniCheckException(env);
|
||||
|
||||
proxy->java_dump.buffer_idx = 0;
|
||||
proxy->java_dump.last_dump_ms = proxy->now_ms;
|
||||
proxy->pcap_dump.buffer_idx = 0;
|
||||
proxy->pcap_dump.last_dump_ms = proxy->now_ms;
|
||||
|
||||
(*env)->DeleteLocalRef(env, barray);
|
||||
}
|
||||
@ -840,36 +838,6 @@ void vpn_protect_socket(vpnproxy_data_t *proxy, socket_t sock) {
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
static int connect_dumper(vpnproxy_data_t *proxy) {
|
||||
if(proxy->pcap_dump.enabled) {
|
||||
dumper_socket = socket(AF_INET, proxy->pcap_dump.tcp_socket ? SOCK_STREAM : SOCK_DGRAM, 0);
|
||||
|
||||
if (!dumper_socket) {
|
||||
log_f("could not open UDP pcap dump socket [%d]: %s", errno, strerror(errno));
|
||||
return(-1);
|
||||
}
|
||||
|
||||
vpn_protect_socket(proxy, dumper_socket);
|
||||
|
||||
if(proxy->pcap_dump.tcp_socket) {
|
||||
struct sockaddr_in servaddr = {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(connect(dumper_socket, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
|
||||
log_f("connection to the PCAP receiver failed [%d]: %s", errno,
|
||||
strerror(errno));
|
||||
return(-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
void run_housekeeping(vpnproxy_data_t *proxy) {
|
||||
if(proxy->capture_stats.new_stats
|
||||
&& ((proxy->now_ms - proxy->capture_stats.last_update_ms) >= CAPTURE_STATS_UPDATE_FREQUENCY_MS) ||
|
||||
@ -888,8 +856,8 @@ void run_housekeeping(vpnproxy_data_t *proxy) {
|
||||
last_connections_dump = proxy->now_ms;
|
||||
next_connections_dump = proxy->now_ms + CONNECTION_DUMP_UPDATE_FREQUENCY_MS;
|
||||
netd_resolve_waiting = 0;
|
||||
} else if ((proxy->java_dump.buffer_idx > 0)
|
||||
&& (proxy->now_ms - proxy->java_dump.last_dump_ms) >= MAX_JAVA_DUMP_DELAY_MS) {
|
||||
} else if ((proxy->pcap_dump.buffer_idx > 0)
|
||||
&& (proxy->now_ms - proxy->pcap_dump.last_dump_ms) >= MAX_JAVA_DUMP_DELAY_MS) {
|
||||
javaPcapDump(proxy);
|
||||
}
|
||||
}
|
||||
@ -970,37 +938,22 @@ void account_packet(vpnproxy_data_t *proxy, const zdtun_pkt_t *pkt, uint8_t from
|
||||
data->update_type |= CONN_UPDATE_STATS;
|
||||
notify_connection(&proxy->conns_updates, conn_tuple, data);
|
||||
|
||||
if (proxy->java_dump.buffer) {
|
||||
if (proxy->pcap_dump.buffer) {
|
||||
int tot_size = pkt->len + (int) sizeof(pcaprec_hdr_s);
|
||||
|
||||
if ((JAVA_PCAP_BUFFER_SIZE - proxy->java_dump.buffer_idx) <= tot_size) {
|
||||
if ((JAVA_PCAP_BUFFER_SIZE - proxy->pcap_dump.buffer_idx) <= tot_size) {
|
||||
// Flush the buffer
|
||||
javaPcapDump(proxy);
|
||||
}
|
||||
|
||||
if ((JAVA_PCAP_BUFFER_SIZE - proxy->java_dump.buffer_idx) <= tot_size)
|
||||
if ((JAVA_PCAP_BUFFER_SIZE - proxy->pcap_dump.buffer_idx) <= tot_size)
|
||||
log_e("Invalid buffer size [size=%d, idx=%d, tot_size=%d]",
|
||||
JAVA_PCAP_BUFFER_SIZE, proxy->java_dump.buffer_idx, tot_size);
|
||||
JAVA_PCAP_BUFFER_SIZE, proxy->pcap_dump.buffer_idx, tot_size);
|
||||
else
|
||||
proxy->java_dump.buffer_idx += dump_pcap_rec(
|
||||
(u_char *) proxy->java_dump.buffer + proxy->java_dump.buffer_idx,
|
||||
proxy->pcap_dump.buffer_idx += dump_pcap_rec(
|
||||
(u_char *) proxy->pcap_dump.buffer + proxy->pcap_dump.buffer_idx,
|
||||
(u_char *) pkt->buf, pkt->len);
|
||||
}
|
||||
|
||||
if (dumper_socket > 0) {
|
||||
struct sockaddr_in servaddr = {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 *) pkt->buf, pkt->len);
|
||||
}
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
@ -1048,14 +1001,8 @@ static int run_tun(JNIEnv *env, jclass vpn, int tunfd, jint sdk) {
|
||||
.app_filter = getIntPref(env, vpn, "getAppFilterUid"),
|
||||
.root_capture = (bool) getIntPref(env, vpn, "isRootCapture"),
|
||||
.incr_id = 0,
|
||||
.java_dump = {
|
||||
.enabled = (bool) getIntPref(env, vpn, "dumpPcapToJava"),
|
||||
},
|
||||
.pcap_dump = {
|
||||
.collector_addr = getIPv4Pref(env, vpn, "getPcapCollectorAddress"),
|
||||
.collector_port = htons(getIntPref(env, vpn, "getPcapCollectorPort")),
|
||||
.tcp_socket = false,
|
||||
.enabled = (bool) getIntPref(env, vpn, "dumpPcapToUdp"),
|
||||
.enabled = (bool) getIntPref(env, vpn, "pcapDumpEnabled"),
|
||||
},
|
||||
.socks5 = {
|
||||
.enabled = (bool) getIntPref(env, vpn, "getSocks5Enabled"),
|
||||
@ -1069,8 +1016,6 @@ static int run_tun(JNIEnv *env, jclass vpn, int tunfd, jint sdk) {
|
||||
};
|
||||
|
||||
/* Important: init global state every time. Android may reuse the service. */
|
||||
dumper_socket = -1;
|
||||
send_header = true;
|
||||
running = true;
|
||||
|
||||
logcallback = log_callback;
|
||||
@ -1088,16 +1033,11 @@ static int run_tun(JNIEnv *env, jclass vpn, int tunfd, jint sdk) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
if(proxy.pcap_dump.enabled) {
|
||||
if(connect_dumper(&proxy) < 0)
|
||||
running = false;
|
||||
}
|
||||
proxy.pcap_dump.buffer = malloc(JAVA_PCAP_BUFFER_SIZE);
|
||||
proxy.pcap_dump.buffer_idx = 0;
|
||||
|
||||
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) {
|
||||
log_f("malloc(java_dump.buffer) failed with code %d/%s",
|
||||
if(!proxy.pcap_dump.buffer) {
|
||||
log_f("malloc(pcap_dump.buffer) failed with code %d/%s",
|
||||
errno, strerror(errno));
|
||||
running = false;
|
||||
}
|
||||
@ -1117,17 +1057,12 @@ static int run_tun(JNIEnv *env, jclass vpn, int tunfd, jint sdk) {
|
||||
|
||||
ndpi_exit_detection_module(proxy.ndpi);
|
||||
|
||||
if(dumper_socket > 0) {
|
||||
close(dumper_socket);
|
||||
dumper_socket = -1;
|
||||
}
|
||||
|
||||
if(proxy.java_dump.buffer) {
|
||||
if(proxy.java_dump.buffer_idx > 0)
|
||||
if(proxy.pcap_dump.buffer) {
|
||||
if(proxy.pcap_dump.buffer_idx > 0)
|
||||
javaPcapDump(&proxy);
|
||||
|
||||
free(proxy.java_dump.buffer);
|
||||
proxy.java_dump.buffer = NULL;
|
||||
free(proxy.pcap_dump.buffer);
|
||||
proxy.pcap_dump.buffer = NULL;
|
||||
}
|
||||
|
||||
notifyServiceStatus(&proxy, "stopped");
|
||||
|
||||
@ -116,19 +116,12 @@ typedef struct vpnproxy_data {
|
||||
bool root_capture;
|
||||
zdtun_statistics_t stats;
|
||||
|
||||
struct {
|
||||
u_int32_t collector_addr;
|
||||
u_int16_t collector_port;
|
||||
bool tcp_socket;
|
||||
bool enabled;
|
||||
} pcap_dump;
|
||||
|
||||
struct {
|
||||
bool enabled;
|
||||
jbyte *buffer;
|
||||
int buffer_idx;
|
||||
u_int64_t last_dump_ms;
|
||||
} java_dump;
|
||||
} pcap_dump;
|
||||
|
||||
struct {
|
||||
bool enabled;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user