Skip to content

Commit

Permalink
Show pcapd errors in the app UI
Browse files Browse the repository at this point in the history
pcapd is now run without the daemonize option, allowing PCAPdroid to retrieve the
exit code and avoiding an unnecessary fork. Code-based error reporting is now
implemented in pcapd. Errors are now shown in the UI and common ones are localized.
  • Loading branch information
emanuele-f committed Aug 17, 2023
1 parent 85c2fda commit ba7291c
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1416,7 +1416,35 @@ public void stopPcapDump() {

public void reportError(String msg) {
HAS_ERROR = true;
mHandler.post(() -> Toast.makeText(this, msg, Toast.LENGTH_LONG).show());

mHandler.post(() -> {
String err = msg;

// Try to get a translated string (see errors.h)
switch (msg) {
case "Invalid PCAP file":
err = getString(R.string.invalid_pcap_file);
break;
case "Could not open the capture interface":
err = getString(R.string.capture_interface_open_error);
break;
case "Unsupported datalink":
err = getString(R.string.unsupported_pcap_datalink);
break;
case "The specified PCAP file does not exist":
err = getString(R.string.pcap_file_not_exists);
break;
case "pcapd daemon did not spawn":
if(mSettings.root_capture)
err = getString(R.string.root_capture_start_failed);
break;
case "PCAP read error":
err = getString(R.string.pcap_read_error);
break;
}

Toast.makeText(this, err, Toast.LENGTH_LONG).show();
});
}

public String getWorkingDir() {
Expand Down
70 changes: 43 additions & 27 deletions app/src/main/jni/common/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,12 @@ void hexdump(const char *buf, size_t bufsize) {

/* ******************************************************* */

int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_error) {
// Start a sub-process, running a command with some arguments, either as root or as the current user.
// On success, the out_fd parameter will receive an open file descriptor to read the command output
// Returns the pid of the child process, or -1 on failure
// NOTE: the caller MUST call waitpid or equivalent to prevent process zombification and close the out_fd
int start_subprocess(const char *prog, const char *args, bool as_root, int* out_fd) {
int in_p[2], out_p[2];
int rv = -1;
pid_t pid;

if((pipe(in_p) != 0) || (pipe(out_p) != 0)) {
Expand All @@ -225,7 +228,7 @@ int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_e
exit(1);
} else if(pid > 0) {
// parent
int out = out_p[0];
*out_fd = out_p[0];
close(in_p[0]);
close(out_p[1]);

Expand All @@ -236,30 +239,6 @@ int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_e
write(in_p[1], args, strlen(args));
write(in_p[1], "\n", 1);
close(in_p[1]);

waitpid(pid, &rv, 0);

if(check_error && (rv != 0)) {
char buf[128];
struct timeval timeout = {0};
fd_set fds;

buf[0] = '\0';
FD_ZERO(&fds);
FD_SET(out, &fds);

select(out + 1, &fds, NULL, NULL, &timeout);
if (FD_ISSET(out, &fds)) {
int num = read(out, buf, sizeof(buf) - 1);
if (num > 0)
buf[num] = '\0';
}

log_f("su \"%s\" invocation failed: %s", prog, buf);
rv = -1;
}

close(out_p[0]);
} else {
log_f("fork() failed[%d]: %s", errno, strerror(errno));
close(in_p[0]);
Expand All @@ -269,5 +248,42 @@ int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_e
return -1;
}

return pid;
}

/* ******************************************************* */

int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_error) {
int out_fd;
int pid = start_subprocess(prog, args, as_root, &out_fd);
if(pid <= 0)
return -1;

int rv;
if(waitpid(pid, &rv, 0) <= 0) {
log_f("waitpid %d failed[%d]: %s", pid, errno, strerror(errno));
return -1;
}

if(check_error && (rv != 0)) {
char buf[128];
struct timeval timeout = {0};
fd_set fds;

buf[0] = '\0';
FD_ZERO(&fds);
FD_SET(out_fd, &fds);

select(out_fd + 1, &fds, NULL, NULL, &timeout);
if (FD_ISSET(out_fd, &fds)) {
int num = read(out_fd, buf, sizeof(buf) - 1);
if (num > 0)
buf[num] = '\0';
}

log_f("\"%s\" invocation failed: %s", prog, buf);
}

close(out_fd);
return rv;
}
1 change: 1 addition & 0 deletions app/src/main/jni/common/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ void tupleSwapPeers(zdtun_5tuple_t *tuple);
char loglvl2char(int lvl);
char* humanSize(char *buf, int bufsize, double bytes);
void hexdump(const char *buf, size_t bufsize);
int start_subprocess(const char *prog, const char *args, bool as_root, int* out_fd);
int run_shell_cmd(const char *prog, const char *args, bool as_root, bool check_error);

#endif // __LOG_UTILS_H__
78 changes: 64 additions & 14 deletions app/src/main/jni/core/capture_libpcap.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
*/

#include <sys/un.h>
#include <sys/wait.h>
#include <linux/limits.h>
#include "pcapdroid.h"
#include "errors.h"
#include "pcapd/pcapd.h"
#include "common/utils.h"
#include "third_party/uthash.h"
Expand Down Expand Up @@ -60,15 +62,11 @@ static int get_pcapd_pid() {

/* ******************************************************* */

static void kill_pcapd(pcapdroid_t *pd) {
int pid = get_pcapd_pid();
if(pid > 0) {
char pid_s[8];
snprintf(pid_s, sizeof(pid_s), "%d", pid);
static void kill_process(int pid, bool as_root, int signum) {
char args[16];

log_i("Killing old pcapd with pid %d", pid);
run_shell_cmd("kill", pid_s, pd->pcap.as_root, false);
}
snprintf(args, sizeof(args), "-%d %d", signum, pid);
run_shell_cmd("kill", args, as_root, false);
}

/* ******************************************************* */
Expand Down Expand Up @@ -138,7 +136,13 @@ static int connectPcapd(pcapdroid_t *pd) {
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, PCAPD_SOCKET_PATH);

kill_pcapd(pd);
int pid = get_pcapd_pid();
if(pid > 0) {
log_i("Killing old pcapd with pid %d", pid);
kill_process(pid, pd->pcap.as_root, SIGTERM);
pid = -1;
}

unlink(PCAPD_PID);
unlink(PCAPD_SOCKET_PATH);

Expand All @@ -164,7 +168,7 @@ static int connectPcapd(pcapdroid_t *pd) {
if(pd->pcap_file_capture) {
// must be a file path
if(access(pd->pcap.capture_interface, F_OK)) {
log_f("The specified PCAP file does not exist: %s", pd->pcap.capture_interface);
log_f(PD_ERR_PCAP_DOES_NOT_EXIST);
goto cleanup;
}
} else {
Expand All @@ -177,10 +181,14 @@ static int connectPcapd(pcapdroid_t *pd) {

// Start the daemon
char args[256];
snprintf(args, sizeof(args), "-l pcapd.log -L %u -i '%s' -d -u %d -t -b '%s'",
snprintf(args, sizeof(args), "-l pcapd.log -L %u -i '%s' -u %d -t -b '%s'",
getuid(), pd->pcap.capture_interface, pd->tls_decryption.enabled ? -1 : pd->app_filter, bpf);
if(run_shell_cmd(pcapd, args, pd->pcap.as_root, true) != 0)

int pcapd_out;
pid = start_subprocess(pcapd, args, pd->pcap.as_root, &pcapd_out);
if(pid <= 0)
goto cleanup;
close(pcapd_out);

// Wait for pcapd to start
struct timeval timeout = {.tv_sec = 3, .tv_usec = 0};
Expand All @@ -190,7 +198,7 @@ static int connectPcapd(pcapdroid_t *pd) {
select(sock + 1, &selfds, NULL, NULL, &timeout);

if(!FD_ISSET(sock, &selfds)) {
log_f("pcapd daemon did not spawn");
log_f(PD_ERR_PCAPD_NOT_SPAWNED);
goto cleanup;
}

Expand All @@ -200,9 +208,15 @@ static int connectPcapd(pcapdroid_t *pd) {
goto cleanup;
}

log_i("Connected to pcapd");
log_i("Connected to pcapd (pid=%d)", pid);
pd->pcap.pcapd_pid = pid;

cleanup:
if((client < 0) && (pid > 0)) {
int rv;
kill_process(pid, pd->pcap.as_root, SIGKILL);
waitpid(pid, &rv, 0);
}
unlink(PCAPD_SOCKET_PATH);
close(sock);

Expand Down Expand Up @@ -487,6 +501,32 @@ void libpcap_iter_connections(pcapdroid_t *pd, conn_cb cb) {

/* ******************************************************* */

static void process_pcapd_rv(pcapdroid_t *pd, int rv) {
pcapd_rv rrv = (pcapd_rv) rv;
log_i("pcapd exit code: %d", rv);

switch (rrv) {
case PCAPD_OK:
break;
case PCAPD_INTERFACE_OPEN_FAILED:
if(pd->pcap_file_capture)
log_f(PD_ERR_INVALID_PCAP_FILE);
else
log_f(PD_ERR_INTERFACE_OPEN_ERROR);
break;
case PCAPD_UNSUPPORTED_DATALINK:
log_f(PD_ERR_UNSUPPORTED_DATALINK);
break;
case PCAPD_PCAP_READ_ERROR:
log_f(PD_ERR_PCAP_READ);
break;
default:
log_f("pcapd daemon exited with code %d", rv);
}
}

/* ******************************************************* */

int run_libpcap(pcapdroid_t *pd) {
int sock = -1;
int rv = -1;
Expand Down Expand Up @@ -619,5 +659,15 @@ int run_libpcap(pcapdroid_t *pd) {
run_shell_cmd("iptables", get_mitm_redirection_args(pd, args, false), true, false);
}

if(pd->pcap.pcapd_pid > 0) {
int status = PCAPD_ERROR;

if(waitpid(pd->pcap.pcapd_pid, &status, 0) <= 0)
log_e("waitpid %d failed[%d]: %s", pd->pcap.pcapd_pid, errno, strerror(errno));

if(WIFEXITED(status))
process_pcapd_rv(pd, WEXITSTATUS(status));
}

return rv;
}
14 changes: 14 additions & 0 deletions app/src/main/jni/core/errors.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef __PCAPDROID_ERRORS_H__
#define __PCAPDROID_ERRORS_H__

// This file contains a set of error strings which may be returned from the native code.
// These should be translated in CaptureService.reportError

#define PD_ERR_INVALID_PCAP_FILE "Invalid PCAP file"
#define PD_ERR_INTERFACE_OPEN_ERROR "Could not open the capture interface"
#define PD_ERR_UNSUPPORTED_DATALINK "Unsupported datalink"
#define PD_ERR_PCAP_DOES_NOT_EXIST "The specified PCAP file does not exist"
#define PD_ERR_PCAPD_NOT_SPAWNED "pcapd daemon did not spawn"
#define PD_ERR_PCAP_READ "PCAP read error"

#endif
1 change: 1 addition & 0 deletions app/src/main/jni/core/pcapdroid.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ typedef struct pcapdroid {
bool as_root;
char *bpf;
char *capture_interface;
int pcapd_pid;
} pcap;
};

Expand Down
Loading

0 comments on commit ba7291c

Please sign in to comment.