From 9d427461cbce9a39f0657ce261bcfbc14c90f853 Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Thu, 1 Jul 2021 14:30:37 +0200 Subject: [PATCH] Fix UDP exporter and HTTP dump modes with root When exporting traffic over the network, we must exclude this traffic from the monitoring, otherwise the traffic will be captured in a loop --- .../remote_capture/CaptureService.java | 2 + .../remote_capture/interfaces/PcapDumper.java | 13 +++++ .../remote_capture/pcap_dump/FileDumper.java | 5 ++ .../remote_capture/pcap_dump/HTTPServer.java | 7 +++ .../remote_capture/pcap_dump/UDPDumper.java | 11 ++-- app/src/main/jni/pcapd/pcapd.c | 54 +++++++------------ app/src/main/jni/vpnproxy-jni/capture_root.c | 9 +++- app/src/main/jni/vpnproxy-jni/pcap_utils.c | 5 -- 8 files changed, 62 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java index db1dbda6..656f9359 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java +++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java @@ -617,6 +617,8 @@ public class CaptureService extends VpnService implements Runnable { return((dump_mode != Prefs.DumpMode.NONE) ? 1 : 0); } + public String getPcapDumperBpf() { return((mDumper != null) ? mDumper.getBpf() : ""); } + @Override public boolean protect(int socket) { // Do not call protect in root mode diff --git a/app/src/main/java/com/emanuelef/remote_capture/interfaces/PcapDumper.java b/app/src/main/java/com/emanuelef/remote_capture/interfaces/PcapDumper.java index 0f1832c7..492cc814 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/interfaces/PcapDumper.java +++ b/app/src/main/java/com/emanuelef/remote_capture/interfaces/PcapDumper.java @@ -6,6 +6,13 @@ import java.io.IOException; * It has the following lifecycle: * * startDumper -> ... dumpData ... -> stopDumper + * + * In order to avoid monitoring the dumper traffic (which would cause a loop), a dumper must implement + * the following policy: + * - for root capture, the getBpf method must return a BPF filter to exclude the traffic. This will + * be set at the start of the catpure. + * - for non-root capture, the dumper must pass each socket it opens to the CaptureService.protect + * method. */ public interface PcapDumper { /** @@ -20,6 +27,12 @@ public interface PcapDumper { */ void stopDumper() throws IOException; + /** Get a BPF to use to ignore the connections made by the dumper. + * + * @return the BPF string + */ + String getBpf(); + /** * 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. diff --git a/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/FileDumper.java b/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/FileDumper.java index 78eb95b5..a2055323 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/FileDumper.java +++ b/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/FileDumper.java @@ -34,6 +34,11 @@ public class FileDumper implements PcapDumper { mOutputStream.close(); } + @Override + public String getBpf() { + return ""; + } + @Override public void dumpData(byte[] data) throws IOException { if(mSendHeader) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/HTTPServer.java b/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/HTTPServer.java index ac18b734..78775e31 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/HTTPServer.java +++ b/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/HTTPServer.java @@ -34,6 +34,7 @@ import fi.iki.elonen.NanoHTTPD.Response.Status; public class HTTPServer extends NanoHTTPD implements PcapDumper { private static final String PCAP_MIME = "application/vnd.tcpdump.pcap"; private boolean mAcceptConnections = false; + private int mPort; private final Context mContext; /* NOTE: access to mActiveResponses must be synchronized */ @@ -41,6 +42,7 @@ public class HTTPServer extends NanoHTTPD implements PcapDumper { public HTTPServer(Context context, int port) { super(port); + mPort = port; mContext = context; } @@ -103,6 +105,11 @@ public class HTTPServer extends NanoHTTPD implements PcapDumper { stop(); } + @Override + public String getBpf() { + return "not (host " + Utils.getLocalIPAddress(mContext) + " and tcp port " + mPort + ")"; + } + @Override public void dumpData(byte[] data) throws IOException { synchronized (this) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/UDPDumper.java b/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/UDPDumper.java index ce784323..eafe15ab 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/UDPDumper.java +++ b/app/src/main/java/com/emanuelef/remote_capture/pcap_dump/UDPDumper.java @@ -7,16 +7,16 @@ 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.net.InetSocketAddress; import java.util.Iterator; public class UDPDumper implements PcapDumper { public static final String TAG = "UDPDumper"; - private final SocketAddress mServer; + private final InetSocketAddress mServer; private boolean mSendHeader; private DatagramSocket mSocket; - public UDPDumper(SocketAddress server) { + public UDPDumper(InetSocketAddress server) { mServer = server; mSendHeader = true; } @@ -32,6 +32,11 @@ public class UDPDumper implements PcapDumper { mSocket.close(); } + @Override + public String getBpf() { + return "not (host " + mServer.getAddress().getHostAddress() + " and udp port " + mServer.getPort() + ")"; + } + private void sendDatagram(byte[] data, int offset, int len) throws IOException { DatagramPacket request = new DatagramPacket(data, offset, len, mServer); mSocket.send(request); diff --git a/app/src/main/jni/pcapd/pcapd.c b/app/src/main/jni/pcapd/pcapd.c index a82851e6..4b2c90b9 100644 --- a/app/src/main/jni/pcapd/pcapd.c +++ b/app/src/main/jni/pcapd/pcapd.c @@ -58,6 +58,7 @@ typedef struct { int nlsock; int client; + char bpf[512]; pcap_t *pd; int dlink; int ipoffset; @@ -214,24 +215,6 @@ static int get_iface_ip6(const char *iface, struct in6_addr *ip) { /* ******************************************************* */ -static int list_interfaces() { - pcap_if_t *devs, *pd; - - if(pcap_findalldevs(&devs, errbuf) != 0) { - fprintf(stderr, "pcap_findalldevs failed: %s\n", errbuf); - return -1; - } - - for(pd = devs; pd; pd = pd->next) - printf("%s\n", pd->name); - - pcap_freealldevs(devs); - - return 0; -} - -/* ******************************************************* */ - static void sighandler(__unused int signo) { log_i("SIGTERM received, terminating"); unlink(PCAPD_PID); @@ -400,7 +383,7 @@ static void check_capture_interface(pcapd_runtime_t *rt) { struct bpf_program fcode; // Only IP traffic - if(pcap_compile(pd, &fcode, "ip or ip6", 1, PCAP_NETMASK_UNKNOWN) < 0) { + if(pcap_compile(pd, &fcode, rt->bpf, 1, PCAP_NETMASK_UNKNOWN) < 0) { log_i("[%s] could not set capture filter: %s", ifname, pcap_geterr(pd)); pcap_close(pd); return; @@ -565,7 +548,7 @@ static int is_tx_packet(pcapd_runtime_t *rt, const u_char *pkt, u_int16_t len) { /* ******************************************************* */ -static int run_pcap_dump(int uid_filter) { +static int run_pcap_dump(int uid_filter, const char *bpf) { int rv = -1; struct pcap_stat stats = {0}; pcapd_runtime_t rt = {0}; @@ -583,6 +566,13 @@ static int run_pcap_dump(int uid_filter) { if(init_pcapd_capture(&rt) < 0) goto cleanup; + int l = snprintf(rt.bpf, sizeof(rt.bpf), "ip or ip6"); + + if(bpf[0]) + snprintf(rt.bpf + l, sizeof(rt.bpf) - l, " and (%s)", bpf); + + log_d("Using BPF: %s", rt.bpf); + rt.pf = -1; rt.ifidx = -1; check_capture_interface(&rt); @@ -702,9 +692,9 @@ cleanup: static void usage() { fprintf(stderr, "pcapd - root companion for PCAPdroid\n" "Copyright 2021 Emanuele Faranda \n\n" - "Usage: pcapd [--interfaces|-d]\n" - " --interfaces list the interfaces of the system\n" - " -d [uid] daemonize and dump packets from the internet interface, possibly filtered by uid\n" + "Usage: pcapd -d uid [-b bpf]\n" + " -d [uid] daemonize and dump packets from the internet interface, filtered by uid (-1 for no filter)\n" + " -b [bpf] specify a BPF filter to apply" ); exit(1); @@ -715,20 +705,16 @@ static void usage() { int main(int argc, char *argv[]) { logtag = "pcapd"; logcallback = log_to_file; + int uid_filter; + char *bpf = ""; - if(argc < 2) + if((argc < 3) || (strcmp(argv[1], "-d") != 0)) usage(); - if(!strcmp(argv[1], "--interfaces")) - return list_interfaces(); - else if(!strcmp(argv[1], "-d")) { - int uid_filter = -1; + uid_filter = atoi(argv[2]); - if(argc >= 3) - uid_filter = atoi(argv[2]); + if((argc == 5) && (strcmp(argv[3], "-b") == 0)) + bpf = argv[4]; - return run_pcap_dump(uid_filter); - } - - usage(); + return run_pcap_dump(uid_filter, bpf); } diff --git a/app/src/main/jni/vpnproxy-jni/capture_root.c b/app/src/main/jni/vpnproxy-jni/capture_root.c index ee3b5374..684aa814 100644 --- a/app/src/main/jni/vpnproxy-jni/capture_root.c +++ b/app/src/main/jni/vpnproxy-jni/capture_root.c @@ -109,8 +109,10 @@ static void kill_pcapd(vpnproxy_data_t *proxy) { static int connectPcapd(vpnproxy_data_t *proxy) { int sock; int client = -1; + char bpf[256]; char workdir[PATH_MAX], pcapd[PATH_MAX]; + getStringPref(proxy, "getPcapDumperBpf", bpf, sizeof(bpf)); getStringPref(proxy, "getPcapdWorkingDir", workdir, PATH_MAX); get_libprog_path(proxy, "pcapd", pcapd, sizeof(pcapd)); @@ -150,9 +152,12 @@ static int connectPcapd(vpnproxy_data_t *proxy) { log_d("AF_UNIX socket listening at '%s'", addr.sun_path); + if(bpf[0]) + log_d("Using dumper BPF \"%s\"", bpf); + // Start the daemon - char args[32]; - snprintf(args, sizeof(args), "-d %d", proxy->app_filter); + char args[256]; + snprintf(args, sizeof(args), "-d %d -b \"%s\"", proxy->app_filter, bpf); su_cmd(pcapd, args); // Wait for pcapd to start diff --git a/app/src/main/jni/vpnproxy-jni/pcap_utils.c b/app/src/main/jni/vpnproxy-jni/pcap_utils.c index c54ed1a0..d8c79d78 100644 --- a/app/src/main/jni/vpnproxy-jni/pcap_utils.c +++ b/app/src/main/jni/vpnproxy-jni/pcap_utils.c @@ -59,11 +59,6 @@ static size_t init_pcap_rec_hdr(struct pcaprec_hdr_s *pcap_rec, int length) { 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); }