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
This commit is contained in:
emanuele-f 2021-07-01 14:30:37 +02:00
parent 91cc995eff
commit 9d427461cb
8 changed files with 62 additions and 44 deletions

View File

@ -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

View File

@ -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.

View File

@ -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) {

View File

@ -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) {

View File

@ -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);

View File

@ -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 <black.silver@hotmail.it>\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);
}

View File

@ -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

View File

@ -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);
}