Implement ability to capture packets as root

This requires a rooted device. It allows PCAPdroid to run with other VPN
apps.
This commit is contained in:
emanuele-f 2021-05-01 12:02:48 +02:00
parent bfe4d0a796
commit c2df8436ab
43 changed files with 2522 additions and 848 deletions

5
.gitmodules vendored
View File

@ -1,6 +1,9 @@
[submodule "submodules/nDPI"]
path = submodules/nDPI
url = https://github.com/ntop/nDPI
url = https://github.com/ntop/nDPI
[submodule "submodules/zdtun"]
path = submodules/zdtun
url = https://github.com/emanuele-f/zdtun
[submodule "submodules/libpcap"]
path = submodules/libpcap
url = https://github.com/the-tcpdump-group/libpcap

View File

@ -22,7 +22,7 @@ android {
externalNativeBuild {
cmake {
path file('src/main/jni/vpnproxy-jni/CMakeLists.txt')
path file('src/main/jni/CMakeLists.txt')
}
}

View File

@ -16,13 +16,16 @@
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:banner="@drawable/banner"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:extractNativeLibs="true"
android:theme="@style/AppTheme"
android:allowBackup="true"
android:fullBackupContent="true"
tools:targetApi="m">
<activity
android:name=".activities.MainActivity"

View File

@ -78,6 +78,7 @@ public class CaptureService extends VpnService implements Runnable {
private Prefs.DumpMode dump_mode;
private boolean socks5_enabled;
private boolean ipv6_enabled;
private boolean root_capture;
private int collector_port;
private int http_server_port;
private int socks5_proxy_port;
@ -85,6 +86,7 @@ public class CaptureService extends VpnService implements Runnable {
private int last_connections;
private static CaptureService INSTANCE;
private String app_filter;
private int app_filter_uid;
private HTTPServer mHttpServer;
private OutputStream mOutputStream;
private ConnectionsRegister conn_reg;
@ -190,6 +192,7 @@ public class CaptureService extends VpnService implements Runnable {
ipv6_enabled = Prefs.getIPv6Enabled(prefs);
last_bytes = 0;
last_connections = 0;
root_capture = Prefs.isRootCaptureEnabled(prefs);
conn_reg = new ConnectionsRegister(CONNECTIONS_LOG_SIZE);
@ -228,52 +231,64 @@ public class CaptureService extends VpnService implements Runnable {
}
}
Log.i(TAG, "Using DNS server " + dns_server);
// VPN
/* In order to see the DNS packets into the VPN we must set an internal address as the DNS
* server. */
Builder builder = new Builder()
.addAddress(vpn_ipv4, 30) // using a random IP as an address is needed
.addRoute("0.0.0.0", 1)
.addRoute("128.0.0.0", 1)
.addDnsServer(vpn_dns);
if(ipv6_enabled) {
builder.addAddress(VPN_IP6_ADDRESS, 128);
// Route unicast IPv6 addresses
builder.addRoute("2000::", 3);
if ((app_filter != null) && (!app_filter.isEmpty())) {
try {
builder.addDnsServer(InetAddress.getByName(IPV6_DNS_SERVER));
} catch (UnknownHostException e) {
Log.w(TAG, "Could not set IPv6 DNS server");
}
}
if((app_filter != null) && (!app_filter.isEmpty())) {
Log.d(TAG, "Setting app filter: " + app_filter);
try {
// NOTE: the API requires a package name, however it is converted to a UID
// (see Vpn.java addUserToRanges). This means that vpn routing happens on a UID basis,
// not on a package-name basis!
builder.addAllowedApplication(app_filter);
app_filter_uid = getPackageManager().getApplicationInfo(app_filter, 0).uid;
} 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();
e.printStackTrace();
app_filter_uid = -1;
}
} else
app_filter_uid = -1;
if(!root_capture) {
Log.i(TAG, "Using DNS server " + dns_server);
// VPN
/* In order to see the DNS packets into the VPN we must set an internal address as the DNS
* server. */
Builder builder = new Builder()
.addAddress(vpn_ipv4, 30) // using a random IP as an address is needed
.addRoute("0.0.0.0", 1)
.addRoute("128.0.0.0", 1)
.addDnsServer(vpn_dns);
if (ipv6_enabled) {
builder.addAddress(VPN_IP6_ADDRESS, 128);
// Route unicast IPv6 addresses
builder.addRoute("2000::", 3);
try {
builder.addDnsServer(InetAddress.getByName(IPV6_DNS_SERVER));
} catch (UnknownHostException e) {
Log.w(TAG, "Could not set IPv6 DNS server");
}
}
if ((app_filter != null) && (!app_filter.isEmpty())) {
Log.d(TAG, "Setting app filter: " + app_filter);
try {
// NOTE: the API requires a package name, however it is converted to a UID
// (see Vpn.java addUserToRanges). This means that vpn routing happens on a UID basis,
// not on a package-name basis!
builder.addAllowedApplication(app_filter);
} 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);
}
}
try {
mParcelFileDescriptor = builder.setSession(CaptureService.VpnSessionName).establish();
} catch (IllegalArgumentException | IllegalStateException e) {
Utils.showToast(this, R.string.vpn_setup_failed);
return super.onStartCommand(intent, flags, startId);
}
}
try {
mParcelFileDescriptor = builder.setSession(CaptureService.VpnSessionName).establish();
} catch (IllegalArgumentException | IllegalStateException e) {
Utils.showToast(this, R.string.vpn_setup_failed);
return super.onStartCommand(intent, flags, startId);
}
// Stop the previous session by interrupting the thread.
if (mThread != null) {
mThread.interrupt();
@ -452,10 +467,15 @@ public class CaptureService extends VpnService implements Runnable {
stopForeground(true /* remove notification */);
}
private void stopThread() {
mThread = null;
stop();
}
/* Check if the VPN service was launched */
public static boolean isServiceActive() {
return((INSTANCE != null) &&
(INSTANCE.mParcelFileDescriptor != null));
(INSTANCE.mThread != null));
}
public static String getAppFilter() {
@ -500,8 +520,18 @@ public class CaptureService extends VpnService implements Runnable {
return((INSTANCE != null) ? INSTANCE.conn_reg : null);
}
public static boolean isCapturingAsRoot() {
return((INSTANCE != null) &&
(INSTANCE.isRootCapture() == 1));
}
@Override
public void run() {
if(root_capture) {
runPacketLoop(-1, this, Build.VERSION.SDK_INT);
return;
}
if(mParcelFileDescriptor != null) {
int fd = mParcelFileDescriptor.getFd();
int fd_setsize = getFdSetSize();
@ -509,8 +539,10 @@ public class CaptureService extends VpnService implements Runnable {
if((fd > 0) && (fd < fd_setsize)) {
Log.d(TAG, "VPN fd: " + fd + " - FD_SETSIZE: " + fd_setsize);
runPacketLoop(fd, this, Build.VERSION.SDK_INT);
} else
} else {
Log.e(TAG, "Invalid VPN fd: " + fd);
stopThread();
}
}
}
@ -546,6 +578,10 @@ public class CaptureService extends VpnService implements Runnable {
public int getIPv6Enabled() { return(ipv6_enabled ? 1 : 0); }
public int isRootCapture() { return(root_capture ? 1 : 0); }
public int getAppFilterUid() { return(app_filter_uid); }
// 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);
@ -635,6 +671,16 @@ public class CaptureService extends VpnService implements Runnable {
});
}
public String getPcapdWorkingDir() {
return getCacheDir().getAbsolutePath();
}
public String getLibprogPath(String prog_name) {
// executable binaries are stored into the /lib folder of the app
String dir = getApplicationInfo().nativeLibraryDir;
return(dir + "/lib" + prog_name + ".so");
}
public static native void runPacketLoop(int fd, CaptureService vpn, int sdk);
public static native void stopPacketLoop();
public static native void askStatsDump();

View File

@ -64,6 +64,7 @@ import com.emanuelef.remote_capture.model.AppDescriptor;
import com.emanuelef.remote_capture.model.Prefs;
import com.emanuelef.remote_capture.views.AppsListView;
import java.io.File;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.InetAddress;
@ -82,6 +83,7 @@ public class Utils {
public static final String PCAP_HEADER = "d4c3b2a1020004000000000000000000ffff000065000000";
public static final int UID_UNKNOWN = -1;
public static final int UID_NO_FILTER = -2;
private static Boolean root_available = null;
public static String formatBytes(long bytes) {
long divisor;
@ -466,4 +468,27 @@ public class Utils {
return fname;
}
public static boolean isRootAvailable() {
if(root_available == null) {
String path = System.getenv("PATH");
root_available = false;
if(path != null) {
Log.d("isRootAvailable", "PATH = " + path);
for(String part : path.split(":")) {
File f = new File(part + "/su");
if(f.exists()) {
Log.d("isRootAvailable", "'su' binary found at " + f.getAbsolutePath());
root_available = true;
break;
}
}
}
}
return root_available;
}
}

View File

@ -43,5 +43,10 @@ public class AboutActivity extends BaseActivity {
String localized = gplLicense.getText().toString();
gplLicense.setText(Html.fromHtml("<a href='https://www.gnu.org/licenses/gpl-3.0-standalone.html'>" + localized + "</a>"));
gplLicense.setMovementMethod(LinkMovementMethod.getInstance());
TextView sourceLink = findViewById(R.id.app_source_link);
localized = sourceLink.getText().toString();
sourceLink.setText(Html.fromHtml("<a href='" + MainActivity.GITHUB_PROJECT_URL + "'>" + localized + "</a>"));
sourceLink.setMovementMethod(LinkMovementMethod.getInstance());
}
}

View File

@ -98,6 +98,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
public static final String TELEGRAM_GROUP_NAME = "PCAPdroid";
public static final String GITHUB_PROJECT_URL = "https://github.com/emanuele-f/PCAPdroid";
public static final String GITHUB_DOCS_URL = "https://emanuele-f.github.io/PCAPdroid";
public static final String DONATE_URL = "https://emanuele-f.github.io/PCAPdroid/donate";
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -344,8 +345,8 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
startActivity(intent);
} else
Utils.showToast(this, R.string.capture_not_started);
} else if (id == R.id.action_open_github) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(GITHUB_PROJECT_URL));
} else if (id == R.id.action_donate) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(DONATE_URL));
startActivity(browserIntent);
} else if (id == R.id.action_open_telegram) {
openTelegram();
@ -501,6 +502,11 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
private void startCaptureService() {
appStateStarting();
if(Prefs.isRootCaptureEnabled(mPrefs)) {
onActivityResult(REQUEST_CODE_VPN, RESULT_OK, null);
return;
}
Intent vpnPrepareIntent = VpnService.prepare(MainActivity.this);
if (vpnPrepareIntent != null)
@ -519,7 +525,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
return;
}
if(Utils.hasVPNRunning(this)) {
if(!Prefs.isRootCaptureEnabled(mPrefs) && Utils.hasVPNRunning(this)) {
new AlertDialog.Builder(this)
.setMessage(R.string.existing_vpn_confirm)
.setPositiveButton(R.string.yes, (dialog, whichButton) -> startCaptureService())

View File

@ -83,9 +83,12 @@ public class SettingsActivity extends BaseActivity {
public static class SettingsFragment extends PreferenceFragmentCompat {
private SwitchPreference mTlsDecryptionEnabled; // TODO rename
private SwitchPreference mRootCaptureEnabled;
private EditTextPreference mSocks5ProxyIp;
private EditTextPreference mSocks5ProxyPort;
private Preference mTlsHelp;
private Preference mProxyPrefs;
private Preference mIpv6Enabled;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@ -97,6 +100,7 @@ public class SettingsActivity extends BaseActivity {
setupOtherPrefs();
socks5ProxyHideShow(mTlsDecryptionEnabled.isChecked());
rootCaptureHideShow(Utils.isRootAvailable() && mRootCaptureEnabled.isChecked());
}
private boolean validatePort(String value) {
@ -129,6 +133,7 @@ public class SettingsActivity extends BaseActivity {
}
private void setupSocks5ProxyPrefs() {
mProxyPrefs = findPreference("proxy_prefs");
mTlsHelp = findPreference("tls_how_to");
mTlsDecryptionEnabled = findPreference(Prefs.PREF_TLS_DECRYPTION_ENABLED_KEY);
@ -187,6 +192,23 @@ public class SettingsActivity extends BaseActivity {
return true;
});
mRootCaptureEnabled = findPreference(Prefs.PREF_ROOT_CAPTURE);
if(Utils.isRootAvailable()) {
mRootCaptureEnabled.setOnPreferenceChangeListener((preference, newValue) -> {
rootCaptureHideShow((Boolean) newValue);
return true;
});
} else
mRootCaptureEnabled.setVisible(false);
mIpv6Enabled = findPreference(Prefs.PREF_IPV6_ENABLED);
}
private void rootCaptureHideShow(boolean enabled) {
mProxyPrefs.setVisible(!enabled);
mIpv6Enabled.setVisible(!enabled);
}
}
}

View File

@ -34,6 +34,7 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.TableLayout;
import android.widget.TextView;
@ -76,6 +77,13 @@ public class StatsActivity extends BaseActivity {
mDnsQueries = findViewById(R.id.dns_queries);
mDnsServer = findViewById(R.id.dns_server);
if(CaptureService.isCapturingAsRoot()) {
findViewById(R.id.dns_server_row).setVisibility(View.GONE);
findViewById(R.id.dns_queries_row).setVisibility(View.GONE);
findViewById(R.id.open_sockets_row).setVisibility(View.GONE);
findViewById(R.id.max_fd_row).setVisibility(View.GONE);
}
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {

View File

@ -40,6 +40,7 @@ public class Prefs {
public static final String PREF_IPV6_ENABLED = "ipv6_enabled";
public static final String PREF_APP_LANGUAGE = "app_language";
public static final String PREF_APP_THEME = "app_theme";
public static final String PREF_ROOT_CAPTURE = "root_capture";
public enum DumpMode {
NONE,
@ -70,4 +71,5 @@ public class Prefs {
public static String getAppFilter(SharedPreferences p) { return(p.getString(PREF_APP_FILTER, "")); }
public static boolean getIPv6Enabled(SharedPreferences p) { return(p.getBoolean(PREF_IPV6_ENABLED, false)); }
public static boolean useEnglishLanguage(SharedPreferences p){ return("english".equals(p.getString(PREF_APP_LANGUAGE, "system")));}
public static boolean isRootCaptureEnabled(SharedPreferences p) { return(Utils.isRootAvailable() && p.getBoolean(PREF_ROOT_CAPTURE, false)); }
}

View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.10.2)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(ROOTDIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../..)
# zdtun
set(ZDTUN_ROOT ${ROOTDIR}/submodules/zdtun)
include_directories(${ZDTUN_ROOT})
add_subdirectory(${ZDTUN_ROOT} zdtun_build)
# base
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(common)
add_subdirectory(pcapd)
add_subdirectory(vpnproxy-jni)

View File

@ -0,0 +1,3 @@
project(common C)
ADD_LIBRARY(common STATIC uid_lru.c utils.c uid_resolver.c)

View File

@ -0,0 +1,122 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2020-21 - Emanuele Faranda
*/
// A simple LRU implementation based on uthash
// Inspired by https://jehiah.cz/a/uthash
#include <stdlib.h>
#include "uid_lru.h"
#include "third_party/uthash.h"
struct cache_entry {
zdtun_5tuple_t key;
int uid;
UT_hash_handle hh;
};
struct uid_lru {
int max_size;
struct cache_entry *cache;
};
/* ******************************************************* */
uid_lru_t* uid_lru_init(int max_size) {
uid_lru_t *lru = (uid_lru_t*) malloc(sizeof(uid_lru_t));
if(!lru)
return NULL;
lru->max_size = max_size;
lru->cache = NULL;
return lru;
}
/* ******************************************************* */
void uid_lru_destroy(uid_lru_t *lru) {
struct cache_entry *entry, *tmp;
HASH_ITER(hh, lru->cache, entry, tmp) {
HASH_DELETE(hh, lru->cache, entry);
free(entry);
}
free(lru);
}
/* ******************************************************* */
static struct cache_entry* uid_lru_find_entry(uid_lru_t *lru, const zdtun_5tuple_t *tuple) {
struct cache_entry *entry;
HASH_FIND(hh, lru->cache, tuple, sizeof(zdtun_5tuple_t), entry);
if(entry) {
// Bring the entry to the front of the list
HASH_DELETE(hh, lru->cache, entry);
HASH_ADD(hh, lru->cache, key, sizeof(zdtun_5tuple_t), entry);
return(entry);
}
return NULL;
}
/* ******************************************************* */
void uid_lru_add(uid_lru_t *lru, const zdtun_5tuple_t *tuple, int uid) {
struct cache_entry *entry, *tmp;
entry = malloc(sizeof(struct cache_entry));
if(!entry)
return;
entry->key = *tuple;
entry->uid = uid;
HASH_ADD(hh, lru->cache, key, sizeof(zdtun_5tuple_t), entry);
if(HASH_COUNT(lru->cache) > lru->max_size) {
// uthash guarantees that iteration order is same as insertion order
// see https://troydhanson.github.io/uthash/userguide.html#_sorting
HASH_ITER(hh, lru->cache, entry, tmp) {
// delete the oldest entry
HASH_DELETE(hh, lru->cache, entry);
free(entry);
break;
}
}
}
/* ******************************************************* */
int uid_lru_find(uid_lru_t *lru, const zdtun_5tuple_t *tuple) {
struct cache_entry *entry = uid_lru_find_entry(lru, tuple);
return(entry ? entry->uid : -2);
}
/* ******************************************************* */
int uid_lru_size(uid_lru_t *lru) {
return HASH_COUNT(lru->cache);
}

View File

@ -17,19 +17,17 @@
* Copyright 2020-21 - Emanuele Faranda
*/
#ifndef __JNI_HELPERS_H__
#define __JNI_HELPERS_H__
#ifndef __IP_LRU_H__
#define __IP_LRU_H__
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include "zdtun.h"
void init_log(int lvl, JNIEnv *env, jclass _vpnclass, jclass _vpn_inst);
void finish_log();
void log_android(int prio, const char *fmt, ...);
typedef struct uid_lru uid_lru_t;
jclass jniFindClass(JNIEnv *env, const char *name);
jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature);
int jniCheckException(JNIEnv *env);
uid_lru_t* uid_lru_init(int max_size);
void uid_lru_destroy(uid_lru_t *lru);
void uid_lru_add(uid_lru_t *lru, const zdtun_5tuple_t *tuple, int uid);
int uid_lru_find(uid_lru_t *lru, const zdtun_5tuple_t *tuple);
int uid_lru_size(uid_lru_t *lru);
#endif // __JNI_HELPERS_H__
#endif // __IP_LRU_H__

View File

@ -17,8 +17,14 @@
* Copyright 2020-21 - Emanuele Faranda
*/
#include "vpnproxy.h"
#include "jni_helpers.h"
#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <errno.h>
#include <netinet/in.h>
#include "uid_resolver.h"
#include "utils.h"
/* ******************************************************* */
@ -55,7 +61,7 @@ static jint get_uid_proc(int ipver, int ipproto, const char *conn_shex,
FILE *fd = fopen(proc, "r");
if (fd == NULL) {
log_android(ANDROID_LOG_ERROR, "fopen(%s) failed[%d]: %s", proc, errno, strerror(errno));
log_e("fopen(%s) failed[%d]: %s", proc, errno, strerror(errno));
return UID_UNKNOWN;
}
@ -75,10 +81,10 @@ static jint get_uid_proc(int ipver, int ipproto, const char *conn_shex,
if(!lines++)
continue;
//log_android(ANDROID_LOG_INFO, "[try] %s", line);
//log_i("[try] %s", line);
if(sscanf(line, fmt, shex, &sport, dhex, &dport, &uid) == 5) {
//log_android(ANDROID_LOG_DEBUG, "[try] %s:%d -> %s:%d [%d]", shex, sport, dhex, dport, uid);
//log_d("[try] %s:%d -> %s:%d [%d]", shex, sport, dhex, dport, uid);
if((sport == src_port) && (dport == dst_port)
&& (!strcmp(conn_dhex, dhex) || !strcmp(dhex, zero))
@ -145,13 +151,13 @@ static jint get_uid_slow(const zdtun_5tuple_t *conn_info) {
sprintf(shex, "%08X%08X%08X%08X", src[0], src[1], src[2], src[3]);
sprintf(dhex, "%08X%08X%08X%08X", dst[0], dst[1], dst[2], dst[3]);
//log_android(ANDROID_LOG_INFO, "HEX %s %s", shex, dhex);
//log_i("HEX %s %s", shex, dhex);
rv = get_uid_proc(6, conn_info->ipproto, shex, dhex, sport, dport);
}
//double cpu_time_used = ((double) (clock() - start)) / CLOCKS_PER_SEC;
//log_android(ANDROID_LOG_DEBUG, "cpu_time_used %f", cpu_time_used);
//log_d("cpu_time_used %f", cpu_time_used);
return rv;
}
@ -209,7 +215,7 @@ uid_resolver_t* init_uid_resolver(jint sdk_version, JNIEnv *env, jobject vpn) {
uid_resolver_t *rv = calloc(1, sizeof(uid_resolver_t));
if(!rv) {
log_android(ANDROID_LOG_ERROR, "calloc uid_resolver_t failed");
log_e("calloc uid_resolver_t failed");
return NULL;
}
@ -220,6 +226,10 @@ uid_resolver_t* init_uid_resolver(jint sdk_version, JNIEnv *env, jobject vpn) {
return rv;
}
uid_resolver_t* init_uid_resolver_from_proc() {
return(init_uid_resolver(0, NULL, 0));
}
/* ******************************************************* */
void destroy_uid_resolver(uid_resolver_t *resolver) {

View File

@ -23,9 +23,12 @@
#include <jni.h>
#include "zdtun.h"
#define UID_UNKNOWN -1
typedef struct uid_resolver uid_resolver_t;
uid_resolver_t* init_uid_resolver(jint sdk_version, JNIEnv *env, jobject vpn);
uid_resolver_t* init_uid_resolver_from_proc();
void destroy_uid_resolver(uid_resolver_t *resolver);
jint get_uid(uid_resolver_t *resolver, const zdtun_5tuple_t *conn_info);

View File

@ -14,41 +14,28 @@
* You should have received a copy of the GNU General Public License
* along with PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2020-21 - Emanuele Faranda
* Copyright 2021 - Emanuele Faranda
*/
#include <malloc.h>
#include "jni_helpers.h"
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include "utils.h"
static int loglevel = 0;
static JNIEnv *cur_env = NULL;
static jclass vpnclass = 0;
static jclass vpn_inst = 0;
static jmethodID reportError = NULL;
int loglevel = 0;
const char *logtag = "VPNProxy";
void (*logcallback)(int lvl, const char *msg) = NULL;
/* ******************************************************* */
void init_log(int lvl, JNIEnv *env, jclass _vpnclass, jclass _vpn_inst) {
void set_log_level(int lvl) {
loglevel = lvl;
cur_env = env;
vpnclass = _vpnclass;
vpn_inst = _vpn_inst;
reportError = jniGetMethodID(cur_env, vpnclass, "reportError", "(Ljava/lang/String;)V");
}
/* ******************************************************* */
void finish_log() {
cur_env = NULL;
vpnclass = 0;
vpn_inst = 0;
reportError = 0;
}
/* ******************************************************* */
void log_android(int prio, const char *fmt, ...) {
if (prio >= loglevel) {
if(prio >= loglevel) {
char line[1024];
va_list argptr;
@ -56,25 +43,64 @@ void log_android(int prio, const char *fmt, ...) {
vsnprintf(line, sizeof(line), fmt, argptr);
va_end(argptr);
__android_log_print(prio, "VPNProxy", "%s", line);
__android_log_print(prio, logtag, "%s", line);
if((prio >= ANDROID_LOG_FATAL) && (cur_env != NULL) && (reportError != NULL)) {
// This is a fatal error, report it to the gui
jobject info_string = (*cur_env)->NewStringUTF(cur_env, line);
if((jniCheckException(cur_env) != 0) || (info_string == NULL))
return;
(*cur_env)->CallVoidMethod(cur_env, vpn_inst, reportError, info_string);
jniCheckException(cur_env);
(*cur_env)->DeleteLocalRef(cur_env, info_string);
}
if(logcallback != NULL)
logcallback(prio, line);
}
}
/* ******************************************************* */
ssize_t xwrite(int fd, const void *buf, size_t count) {
size_t sofar = 0;
ssize_t ret;
do {
ret = write(fd, (u_char*)buf + sofar, count - sofar);
if(ret < 0) {
if(errno == EINTR)
continue;
return ret;
}
sofar += ret;
} while((sofar != count) && (ret != 0));
if(sofar != count)
return -1;
return 0;
}
/* ******************************************************* */
ssize_t xread(int fd, void *buf, size_t count) {
size_t sofar = 0;
ssize_t rv;
do {
rv = read(fd, (char*)buf + sofar, count - sofar);
if(rv < 0) {
if(errno == EINTR)
continue;
return rv;
}
sofar += rv;
} while((sofar != count) && (rv != 0));
if(sofar != count)
return -1;
return 0;
}
/* ******************************************************* */
int jniCheckException(JNIEnv *env) {
jthrowable ex = (*env)->ExceptionOccurred(env);
if (ex) {

View File

@ -0,0 +1,45 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2021 - Emanuele Faranda
*/
#ifndef __LOG_UTILS_H__
#define __LOG_UTILS_H__
#include <jni.h>
#include <sys/types.h>
#include <android/log.h>
extern int loglevel;
extern const char* logtag;
extern void (*logcallback)(int lvl, const char *msg);
#define log_d(...) log_android(ANDROID_LOG_DEBUG, __VA_ARGS__)
#define log_i(...) log_android(ANDROID_LOG_INFO, __VA_ARGS__)
#define log_w(...) log_android(ANDROID_LOG_WARN, __VA_ARGS__)
#define log_e(...) log_android(ANDROID_LOG_ERROR, __VA_ARGS__)
#define log_f(...) log_android(ANDROID_LOG_FATAL, __VA_ARGS__)
void log_android(int prio, const char *fmt, ...);
ssize_t xwrite(int fd, const void *buf, size_t count);
ssize_t xread(int fd, void *buf, size_t count);
jclass jniFindClass(JNIEnv *env, const char *name);
jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature);
int jniCheckException(JNIEnv *env);
#endif // __LOG_UTILS_H__

View File

@ -0,0 +1,36 @@
project(pcapd C)
# libpcap
set(LIBPCAP_ROOT ${ROOTDIR}/submodules/libpcap)
add_definitions(-DPACKAGE_VERSION="1.10.1-PRE-GIT")
add_definitions(-DHAVE_STRERROR)
add_definitions(-DHAVE_SOCKLEN_T)
include_directories(${LIBPCAP_ROOT})
add_custom_command(OUTPUT ${LIBPCAP_ROOT}/grammar.c ${LIBPCAP_ROOT}/scanner.c
COMMAND cd ${LIBPCAP_ROOT} && ./configure && make grammar.c scanner.c
DEPENDS ${LIBPCAP_ROOT}/grammar.y.in)
ADD_LIBRARY(pcap STATIC
${LIBPCAP_ROOT}/pcap.c
${LIBPCAP_ROOT}/pcap-common.c
${LIBPCAP_ROOT}/sf-pcap.c
${LIBPCAP_ROOT}/sf-pcapng.c
${LIBPCAP_ROOT}/pcap-linux.c
${LIBPCAP_ROOT}/fad-gifc.c
${LIBPCAP_ROOT}/nametoaddr.c
${LIBPCAP_ROOT}/etherent.c
${LIBPCAP_ROOT}/savefile.c
${LIBPCAP_ROOT}/fmtutils.c
${LIBPCAP_ROOT}/gencode.c
${LIBPCAP_ROOT}/grammar.c
${LIBPCAP_ROOT}/scanner.c
${LIBPCAP_ROOT}/bpf_filter.c
${LIBPCAP_ROOT}/optimize.c
${LIBPCAP_ROOT}/missing/strlcpy.c)
# Executables must be names as libraries to be stored into corresponding native folder
add_executable(libpcapd.so pcapd.c nl_utils.c)
# Better to link static libs to avoid changing the library path
target_link_libraries(libpcapd.so pcap zdtun common)

View File

@ -0,0 +1,148 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2021 - Emanuele Faranda
*/
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netinet/in.h>
#include <net/if.h>
#include "nl_utils.h"
int nl_socket(uint32_t groups) {
struct sockaddr_nl snl;
int sock;
sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if(sock < 0)
return -1;
memset(&snl, 0, sizeof(snl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = 0; // managed by the kernel, see man 7 netlink
snl.nl_groups = groups;
if(bind(sock, (struct sockaddr *)&snl, sizeof(snl)) < 0) {
close(sock);
return -1;
}
return sock;
}
/* ******************************************************* */
// adapted from libdnet/route-linux.c
int nl_get_route(int af, const addr_t *addr, route_info_t *out) {
static int seq = 0;
struct nlmsghdr *nmsg;
struct rtmsg *rmsg;
struct rtattr *rta;
struct sockaddr_nl snl;
struct iovec iov;
struct msghdr msg;
u_char buf[512];
int i, alen, ok, nlsock, rv = -1;
nlsock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if(nlsock < 0)
return(-1);
alen = (af == AF_INET) ? 4 : 16;
memset(buf, 0, sizeof(buf));
nmsg = (struct nlmsghdr *)buf;
nmsg->nlmsg_len = NLMSG_LENGTH(sizeof(*nmsg)) + RTA_LENGTH(alen);
nmsg->nlmsg_flags = NLM_F_REQUEST;
nmsg->nlmsg_type = RTM_GETROUTE;
nmsg->nlmsg_seq = ++seq;
rmsg = (struct rtmsg *)(nmsg + 1);
rmsg->rtm_family = af;
rmsg->rtm_dst_len = alen;
rta = RTM_RTA(rmsg);
rta->rta_type = RTA_DST;
rta->rta_len = RTA_LENGTH(alen);
/* XXX - gross hack for default route */
if (af == AF_INET && (addr->v4 == INADDR_ANY)) {
i = htonl(0x60060606);
memcpy(RTA_DATA(rta), &i, alen);
} else
memcpy(RTA_DATA(rta), &addr, alen);
memset(&snl, 0, sizeof(snl));
snl.nl_family = AF_NETLINK;
iov.iov_base = nmsg;
iov.iov_len = nmsg->nlmsg_len;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &snl;
msg.msg_namelen = sizeof(snl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if(sendmsg(nlsock, &msg, 0) < 0)
goto out;
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
if ((i = recvmsg(nlsock, &msg, 0)) <= 0)
goto out;
if(nmsg->nlmsg_len < (int)sizeof(*nmsg) || nmsg->nlmsg_len > i ||
nmsg->nlmsg_seq != seq) {
errno = EINVAL;
goto out;
}
if(nmsg->nlmsg_type == NLMSG_ERROR)
goto out;
i -= NLMSG_LENGTH(sizeof(*nmsg));
out->gw_len = 0;
for(rta = RTM_RTA(rmsg); RTA_OK(rta, i); rta = RTA_NEXT(rta, i)) {
if(rta->rta_type == RTA_GATEWAY) {
// may noy be present
memcpy(&out->gateway, RTA_DATA(rta), alen);
out->gw_len = alen;
} else if (rta->rta_type == RTA_OIF) {
rv = 0;
out->ifidx = *(int *) RTA_DATA(rta);
}
}
if(rv != 0) {
errno = ESRCH;
goto out;
}
out:
close(nlsock);
return(rv);
}

View File

@ -0,0 +1,41 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2021 - Emanuele Faranda
*/
#ifndef __NL_UTILS_H__
#define __NL_UTILS_H__
#include <stdint.h>
typedef struct {
union {
uint32_t v4;
uint8_t v6[8];
};
} __attribute__((packed)) addr_t;
typedef struct {
addr_t gateway;
int ifidx;
int gw_len;
} route_info_t;
int nl_get_route(int af, const addr_t *addr, route_info_t *out);
int nl_socket(uint32_t groups);
#endif

View File

@ -0,0 +1,576 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2021 - Emanuele Faranda
*/
/*
* A daemon to capture network packets and send them to a UNIX socket.
* When running as daemon, the PCAPD_PID file is created, storing the the daemon pid. It is
* automatically deleted when the daemon exits. The daemon captures the packets from the internet
* interface (the interface of the default gateway) and detects its changes.
* The daemon expects the PCAPD_SOCKET_PATH UNIX socket to be present. It will connect and
* start dumping the packets to it. When the socket is closed, the daemon exits.
*/
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <net/if.h>
#include <time.h>
#include <pcap.h>
#include "pcapd.h"
#include "nl_utils.h"
#include "common/uid_resolver.h"
#include "common/uid_lru.h"
#include "common/utils.h"
#include "zdtun.h"
/* ******************************************************* */
typedef struct {
int nlsock;
int client;
pcap_t *pd;
int dlink;
int pf;
int ifidx;
char ifname[IFNAMSIZ];
uint64_t mac;
uint32_t ip;
char nlbuf[8192]; /* >= 8192 to avoid truncation, see "man 7 netlink" */
} pcapd_runtime_t;
static char errbuf[PCAP_ERRBUF_SIZE];
/* ******************************************************* */
static uint64_t bytes2mac(const uint8_t *buf) {
uint64_t m = 0;
memcpy(&m, buf, 6);
return m;
}
/* ******************************************************* */
static int get_iface_mac(const char *iface, uint64_t *mac) {
struct ifreq ifr;
int fd;
int rv;
fd = socket(AF_INET, SOCK_DGRAM, 0);
ifr.ifr_addr.sa_family = AF_INET;
strncpy((char *)ifr.ifr_name, iface, IFNAMSIZ-1);
if((rv = ioctl(fd, SIOCGIFHWADDR, &ifr)) != -1)
*mac = bytes2mac((uint8_t*)ifr.ifr_hwaddr.sa_data);
close(fd);
return(rv);
}
/* ******************************************************* */
int get_iface_ip(const char *iface, uint32_t *ip, uint32_t *netmask) {
struct ifreq ifr;
int fd;
int rv;
fd = socket(AF_INET, SOCK_DGRAM, 0);
ifr.ifr_addr.sa_family = AF_INET;
strncpy((char *)ifr.ifr_name, iface, IFNAMSIZ-1);
if((rv = ioctl(fd, SIOCGIFADDR, &ifr)) != -1) {
*ip = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr;
if((rv = ioctl(fd, SIOCGIFNETMASK, &ifr)) != -1)
*netmask = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr;
}
close(fd);
return(rv);
}
/* ******************************************************* */
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);
exit(0);
}
/* ******************************************************* */
static int create_pid_file() {
FILE *f = fopen(PCAPD_PID, "w");
if(!f)
return -1;
fprintf(f, "%d\n", getpid());
fclose(f);
return 0;
}
/* ******************************************************* */
static void finish_pcapd_capture(pcapd_runtime_t *rt) {
if(rt->client)
close(rt->client);
if(rt->pd)
pcap_close(rt->pd);
if(rt->nlsock > 0)
close(rt->nlsock);
unlink(PCAPD_PID);
}
/* ******************************************************* */
static int init_pcapd_capture(pcapd_runtime_t *rt) {
// daemonize
int pid = fork();
if(pid < 0) {
fprintf(stderr, "fork failed[%d]: %s\n", errno, strerror(errno));
return -1;
} else if(pid != 0) {
// parent
exit(0);
}
rt->nlsock = -1;
rt->client = -1;
rt->pd = NULL;
rt->pf = -1;
rt->ifidx = -1;
rt->ifname[0] = '\0';
rt->mac = 0;
rt->ip = 0;
if(create_pid_file() < 0) {
log_e("pid file creation failed[%d]: %s", errno, strerror(errno));
finish_pcapd_capture(rt);
return -1;
}
rt->nlsock = nl_socket(RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_RULE); // TODO IPv6
if(rt->nlsock < 0) {
log_e("could not create netlink socket[%d]: %s", errno, strerror(errno));
finish_pcapd_capture(rt);
return -1;
}
rt->client = socket(AF_UNIX, SOCK_STREAM, 0);
if(rt->client < 0) {
log_e("socket creation failed[%d]: %s", errno, strerror(errno));
finish_pcapd_capture(rt);
return -1;
}
signal(SIGTERM, &sighandler);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, PCAPD_SOCKET_PATH);
log_i("Connecting to client...");
if(connect(rt->client, (struct sockaddr*) &addr, sizeof(addr)) != 0) {
log_e("client connection failed[%d]: %s", errno, strerror(errno));
finish_pcapd_capture(rt);
return -1;
}
log_i("Connected to client");
unlink(PCAPD_SOCKET_PATH);
return 0;
}
/* ******************************************************* */
static void check_capture_interface(pcapd_runtime_t *rt) {
addr_t pubaddr = {.v4 = 0x60060606}; // arbitrary IPv4 public address
route_info_t ri = {.ifidx = -1};
char ifname[IFNAMSIZ];
if((nl_get_route(AF_INET, &pubaddr, &ri) < 0) || (ri.ifidx == rt->ifidx)) {
//log_i("check_capture_interface: nope [%s] - %d", rt->ifname, ri.ifidx);
return;
}
if(if_indextoname(ri.ifidx, ifname) == NULL) {
log_i("could not get ifidx %d ifname", ri.ifidx);
return;
}
log_i("interface changed [%d -> %d], (re)starting capture", rt->ifidx, ri.ifidx);
// TODO support larger MTU
pcap_t *pd = pcap_open_live(ifname, 1500, 0, 1, errbuf);
if(!pd) {
log_i("pcap_open_live(%s) failed: %s", ifname, errbuf);
return;
}
int dlink = pcap_datalink(pd);
if((dlink != DLT_EN10MB) && (dlink != DLT_RAW)) {
log_i("[%s] unsupported datalink: %d", ifname, dlink);
pcap_close(pd);
return;
}
struct bpf_program fcode;
// Only IP traffic
if(pcap_compile(pd, &fcode, "ip", 1, PCAP_NETMASK_UNKNOWN) < 0) {
log_i("[%s] could not set capture filter: %s", ifname, pcap_geterr(pd));
pcap_close(pd);
return;
}
if(pcap_setfilter(pd, &fcode) < 0) {
log_e("[%s] pcap_setfilter failed: %s", ifname, pcap_geterr(pd));
pcap_freecode(&fcode);
pcap_close(pd);
return;
}
pcap_freecode(&fcode);
// Success
if(rt->pd)
pcap_close(rt->pd);
rt->pd = pd;
rt->mac = 0;
if((dlink == DLT_EN10MB) && (get_iface_mac(ifname, &rt->mac) < 0))
log_i("Could not get interface %s MAC[%d]: %s", ifname, errno, strerror(errno));
uint32_t netmask;
rt->ip = 0;
if(get_iface_ip(ifname, &rt->ip, &netmask) < 0)
log_i("Could not get interface %s IP[%d]: %s", ifname, errno, strerror(errno));
rt->dlink = dlink;
rt->pf = pcap_get_selectable_fd(pd);
rt->ifidx = ri.ifidx;
memcpy(rt->ifname, ifname, sizeof(ifname));
log_i("Capturing packets from %s", ifname);
}
/* ******************************************************* */
static int handle_nl_message(pcapd_runtime_t *rt) {
struct iovec iov = {
.iov_base = rt->nlbuf,
.iov_len = sizeof(rt->nlbuf)
};
struct sockaddr_nl snl;
struct msghdr msg = {
.msg_name = (void *)&snl,
.msg_namelen = sizeof(snl),
.msg_iov = &iov,
.msg_iovlen = 1
};
ssize_t len = recvmsg(rt->nlsock, &msg, 0);
uint8_t found = 0;
if(len <= 0) {
log_e("netlink recvmsg failed [%d]: %s\n", errno, sizeof(errno));
return -1;
}
for(struct nlmsghdr *nh = (struct nlmsghdr *)rt->nlbuf; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
uint8_t do_break = 0;
switch(nh->nlmsg_type) {
case NLMSG_DONE:
do_break = 1;
break;
case RTM_NEWROUTE:
case RTM_NEWRULE:
found = 1;
do_break = 1;
break;
case RTM_NEWADDR:
if(rt->ifidx == ((struct ifaddrmsg *) NLMSG_DATA(nh))->ifa_index) {
log_i("Detected possible IP address change");
uint32_t netmask;
if(get_iface_ip(rt->ifname, &rt->ip, &netmask) < 0)
log_i("Could not get interface %s IP[%d]: %s", rt->ifname, errno, strerror(errno));
break;
}
break;
}
if(do_break)
break;
}
if(found)
check_capture_interface(rt);
return 0;
}
/* ******************************************************* */
// try to determine the packet direction as it is only available in SLL / SLL2 ("any" interface)
static int is_tx_packet(pcapd_runtime_t *rt, const u_char *pkt, u_int16_t len) {
// TODO check for broadcast / multicast
if((rt->dlink == DLT_EN10MB) && (len >= 14)) {
// Ethernet header present
struct ethhdr *eth = (struct ethhdr *) pkt;
uint64_t smac = bytes2mac(eth->h_source);
uint64_t dmac = bytes2mac(eth->h_dest);
if(smac != dmac) {
if(smac == rt->mac)
return 1; // TX
else if(dmac == rt->mac)
return 0; // RX
}
len -= 14;
pkt += 14;
}
// NOTE: this must be IP traffic due to the PCAP filter
if(len < 20)
return 0;
struct iphdr *ip = (struct iphdr *) pkt;
if(ip->version != 4) // TODO IPv6 support
return 0;
if(ip->saddr == rt->ip)
return 1; // TX
return 0;
}
/* ******************************************************* */
static int run_pcap_dump(int uid_filter) {
int rv = -1;
pcapd_runtime_t rt = {0};
time_t next_interface_recheck = 0;
uid_resolver_t *resolver = NULL;
uid_lru_t *lru = NULL;
if(!(resolver = init_uid_resolver_from_proc()))
goto cleanup;
if(!(lru = uid_lru_init(64)))
goto cleanup;
if(init_pcapd_capture(&rt) < 0)
goto cleanup;
check_capture_interface(&rt);
rv = 0;
while(1) {
struct timeval timeout = {.tv_sec = 1, .tv_usec = 0};
fd_set fds = {0};
int maxfd = (rt.client > rt.nlsock) ? rt.client : rt.nlsock;
FD_SET(rt.client, &fds);
FD_SET(rt.nlsock, &fds);
if(rt.pf != -1) {
FD_SET(rt.pf, &fds);
maxfd = (maxfd > rt.pf) ? maxfd : rt.pf;
}
if(select(maxfd + 1, &fds, NULL, NULL, &timeout) < 0) {
log_e("select failed[%d]: %s", errno, strerror(errno));
rv = -1;
break;
}
if(FD_ISSET(rt.client, &fds)) {
log_i("client closed");
break;
}
if(FD_ISSET(rt.nlsock, &fds)) {
if(handle_nl_message(&rt) < 0) {
rv = -1;
break;
}
}
if((rt.pf != -1) && FD_ISSET(rt.pf, &fds)) {
struct pcap_pkthdr *hdr;
const u_char *pkt;
int to_skip = (rt.dlink == DLT_EN10MB) ? 14 : 0;
int rv1 = pcap_next_ex(rt.pd, &hdr, &pkt);
if(rv1 == PCAP_ERROR) {
log_i("pcap_next_ex failed: %s", pcap_geterr(rt.pd));
// Do not abort, just wait for route changes
pcap_close(rt.pd);
rt.pd = NULL;
rt.pf = -1;
rt.ifidx = -1;
rt.ifname[0] = '\0';
} else if((rv1 == 1) && (hdr->caplen >= to_skip)) {
pcapd_hdr_t phdr;
zdtun_pkt_t zpkt;
uint8_t is_tx = is_tx_packet(&rt, pkt, hdr->caplen);
pkt += to_skip;
hdr->caplen -= to_skip;
if(zdtun_parse_pkt((const char*)pkt, hdr->caplen, &zpkt) == 0) {
if(!is_tx) {
// Packet from the internet, swap src and dst
uint16_t tmp = zpkt.tuple.dst_port;
zpkt.tuple.dst_port = zpkt.tuple.src_port;
zpkt.tuple.src_port = tmp;
zdtun_ip_t tmp1 = zpkt.tuple.dst_ip;
zpkt.tuple.dst_ip = zpkt.tuple.src_ip;
zpkt.tuple.src_ip = tmp1;
}
int uid = uid_lru_find(lru, &zpkt.tuple);
if(uid == -2) {
uid = get_uid(resolver, &zpkt.tuple);
uid_lru_add(lru, &zpkt.tuple, uid);
}
if((uid_filter == -1) || (uid_filter == uid)) {
phdr.ts = hdr->ts;
phdr.len = hdr->caplen;
phdr.uid = uid;
phdr.flags = is_tx ? PCAPD_FLAG_TX : 0;
// Send the pcapd_hdr_t first, then the packet data. The packet data always starts with
// the IP header.
if((xwrite(rt.client, &phdr, sizeof(phdr)) < 0) ||
(xwrite(rt.client, pkt, phdr.len) < 0)) {
log_e("write failed[%d]: %s", errno, strerror(errno));
rv = -1;
break;
}
}
}
}
}
if((rt.pd == NULL) && (time(NULL) >= next_interface_recheck)) {
check_capture_interface(&rt);
next_interface_recheck = time(NULL) + 5;
}
}
log_i("Terminating...");
cleanup:
finish_pcapd_capture(&rt);
if(resolver)
destroy_uid_resolver(resolver);
if(lru)
uid_lru_destroy(lru);
return rv;
}
/* ******************************************************* */
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"
);
exit(1);
}
/* ******************************************************* */
int main(int argc, char *argv[]) {
logtag = "pcapd";
if(argc < 2)
usage();
if(!strcmp(argv[1], "--interfaces"))
return list_interfaces();
else if(!strcmp(argv[1], "-d")) {
int uid_filter = -1;
if(argc >= 3)
uid_filter = atoi(argv[2]);
return run_pcap_dump(uid_filter);
}
usage();
}

View File

@ -0,0 +1,38 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2021 - Emanuele Faranda
*/
#ifndef __PCAPD_H__
#define __PCAPD_H__
#define PCAPD_SOCKET_PATH "pcapsock"
#define PCAPD_PID "pcapd.pid"
#define PCAPD_FLAG_TX (1 << 0)
#include <time.h>
#include <stdint.h>
typedef struct {
struct timeval ts;
uid_t uid;
uint16_t len;
uint8_t flags;
} __attribute__((packed)) pcapd_hdr_t;
#endif

View File

@ -1,18 +1,12 @@
cmake_minimum_required(VERSION 3.10.2)
project(vpnproxy-jni)
include(ExternalProject)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(ROOTDIR ../../../../..)
include_directories(../../../..)
add_library(vpnproxy-jni SHARED
vpnproxy.c
uid_resolver.c
jni_helpers.c
capture_proxy.c
capture_root.c
ip_lru.c
pcap)
ndpi_master_protos.c
pcap_utils.c)
# nDPI
set(NDPI_ROOT ${ROOTDIR}/submodules/nDPI)
@ -28,15 +22,10 @@ add_custom_command(OUTPUT ${NDPI_ROOT}/src/include/ndpi_api.h
ADD_LIBRARY(ndpi SHARED ${ndpiSources} ${NDPI_ROOT}/src/include/ndpi_api.h)
# zdtun
set(ZDTUN_ROOT ${ROOTDIR}/submodules/zdtun)
include_directories(${ZDTUN_ROOT})
add_subdirectory(${ZDTUN_ROOT} build)
# Link
find_library(log-lib log)
target_link_libraries(vpnproxy-jni
zdtun
ndpi
common
${log-lib})

View File

@ -0,0 +1,438 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2021 - Emanuele Faranda
*/
#include "vpnproxy.h"
#include "common/utils.h"
static void protectSocketCallback(zdtun_t *tun, socket_t sock) {
vpnproxy_data_t *proxy = ((vpnproxy_data_t*)zdtun_userdata(tun));
protectSocket(proxy, sock);
}
/* ******************************************************* */
static bool shouldIgnoreConn(vpnproxy_data_t *proxy, const zdtun_5tuple_t *tuple) {
#if 0
int uid = data.uid;
bool is_unknown_app = ((uid == UID_UNKNOWN) || (uid == 1051 /* netd DNS resolver */));
if(((proxy->uid_filter != UID_UNKNOWN) && (proxy->uid_filter != uid))
&& (!is_unknown_app || !proxy->capture_unknown_app_traffic))
return true;
#endif
// ignore some internal communications, e.g. DNS-over-TLS check on port 853
if((tuple->ipver == 4) && (tuple->dst_ip.ip4 == proxy->vpn_dns) && (ntohs(tuple->dst_port) != 53))
return true;
return false;
}
/* ******************************************************* */
static void add_known_dns_server(vpnproxy_data_t *proxy, const char *ip) {
ndpi_ip_addr_t parsed;
if(ndpi_parse_ip_string(ip, &parsed) < 0) {
log_e("ndpi_parse_ip_string(%s) failed", ip);
return;
}
ndpi_ptree_insert(proxy->known_dns_servers, &parsed, ndpi_is_ipv6(&parsed) ? 128 : 32, 1);
}
/* ******************************************************* */
static int net2tun(zdtun_t *tun, char *pkt_buf, int pkt_size, const zdtun_conn_t *conn_info) {
if(!running)
return 0;
vpnproxy_data_t *proxy = (vpnproxy_data_t*) zdtun_userdata(tun);
int rv = write(proxy->tunfd, pkt_buf, pkt_size);
if(rv < 0) {
if(errno == ENOBUFS) {
char buf[256];
// Do not abort, the connection will be terminated
log_e("Got ENOBUFS %s", zdtun_5tuple2str(zdtun_conn_get_5tuple(conn_info), buf, sizeof(buf)));
} else if(errno == EIO) {
log_i("Got I/O error (terminating?)");
running = false;
} else {
log_f("tun write (%d) failed [%d]: %s", pkt_size, errno, strerror(errno));
running = false;
}
} else if(rv != pkt_size) {
log_f("partial tun write (%d / %d)", rv, pkt_size);
rv = -1;
} else
rv = 0;
return rv;
}
/* ******************************************************* */
static void check_socks5_redirection(struct vpnproxy_data *proxy, zdtun_pkt_t *pkt, zdtun_conn_t *conn) {
conn_data_t *data = zdtun_conn_get_userdata(conn);
if(shouldIgnoreConn(proxy, zdtun_conn_get_5tuple(conn)))
return;
if((pkt->tuple.ipproto == IPPROTO_TCP) && (((data->sent_pkts + data->rcvd_pkts) == 0)))
zdtun_conn_proxy(conn);
}
/* ******************************************************* */
/*
* If the packet contains a DNS request then rewrite server address
* with public DNS server. Non UDP DNS connections are dropped to block DoH queries which do not
* allow us to extract the requested domain name.
*/
static bool check_dns_req_allowed(struct vpnproxy_data *proxy, zdtun_conn_t *conn) {
const zdtun_5tuple_t *tuple = zdtun_conn_get_5tuple(conn);
if(new_dns_server != 0) {
// Reload DNS server
proxy->dns_server = new_dns_server;
new_dns_server = 0;
zdtun_ip_t ip = {0};
ip.ip4 = proxy->dns_server;
zdtun_set_dnat_info(proxy->tun, &ip, htons(53), 4);
log_d("Using new DNS server");
}
bool is_internal_dns = (tuple->ipver == 4) && (tuple->dst_ip.ip4 == proxy->vpn_dns);
bool is_dns_server = is_internal_dns
|| ((tuple->ipver == 6) && (memcmp(&tuple->dst_ip.ip6, &proxy->ipv6.dns_server, 16) == 0));
if(!is_dns_server) {
// try with known DNS servers
u_int32_t matched = 0;
ndpi_ip_addr_t addr = {0};
if(tuple->ipver == 4)
addr.ipv4 = tuple->dst_ip.ip4;
else
memcpy(&addr.ipv6, &tuple->dst_ip.ip6, 16);
ndpi_ptree_match_addr(proxy->known_dns_servers, &addr, &matched);
if(matched) {
char ip[INET6_ADDRSTRLEN];
int family = (tuple->ipver == 4) ? AF_INET : AF_INET6;
is_dns_server = true;
ip[0] = '\0';
inet_ntop(family, &tuple->dst_ip, (char *)&ip, sizeof(ip));
log_d("Matched known DNS server: %s", ip);
}
}
if(!is_dns_server)
return(true);
if((tuple->ipproto == IPPROTO_UDP) && (ntohs(tuple->dst_port) == 53) && (proxy->last_pkt != NULL)) {
zdtun_pkt_t *pkt = proxy->last_pkt;
int dns_length = pkt->l7_len;
if(dns_length >= sizeof(struct dns_packet)) {
struct dns_packet *dns_data = (struct dns_packet*) pkt->l7;
if((dns_data->flags & DNS_FLAGS_MASK) != DNS_TYPE_REQUEST)
return(true);
log_d("Detected DNS query[%u]", dns_length);
proxy->num_dns_requests++;
if(is_internal_dns) {
/*
* Direct the packet to the public DNS server. Checksum recalculation is not strictly necessary
* here as zdtun will proxy the connection.
*/
zdtun_conn_dnat(conn);
}
return(true);
}
}
log_i("blocking packet directed to the DNS server");
// block everything else (e.g. DoH)
return(false);
}
/* ******************************************************* */
static int handle_new_connection(zdtun_t *tun, zdtun_conn_t *conn_info) {
vpnproxy_data_t *proxy = ((vpnproxy_data_t *) zdtun_userdata(tun));
const zdtun_5tuple_t *tuple = zdtun_conn_get_5tuple(conn_info);
if(!check_dns_req_allowed(proxy, conn_info)) {
// block connection
proxy->last_conn_blocked = true;
return (1);
}
conn_data_t *data = new_connection(proxy, tuple, resolve_uid(proxy, tuple));
if(!data) {
/* reject connection */
return (1);
}
zdtun_conn_set_userdata(conn_info, data);
if(!shouldIgnoreConn(proxy, tuple)) {
// Important: only set the incr_id on registered connections since
// ConnectionsRegister::connectionsUpdates does not allow gaps
data->incr_id = proxy->incr_id++;
conns_add(&proxy->new_conns, tuple, data);
}
/* accept connection */
return(0);
}
/* ******************************************************* */
static void destroy_connection(zdtun_t *tun, const zdtun_conn_t *conn_info) {
vpnproxy_data_t *proxy = (vpnproxy_data_t*) zdtun_userdata(tun);
conn_data_t *data = zdtun_conn_get_userdata(conn_info);
if(!data) {
log_e("Missing data in connection");
return;
}
const zdtun_5tuple_t *tuple = zdtun_conn_get_5tuple(conn_info);
if(!shouldIgnoreConn(proxy, tuple)) {
// Will free the data in sendConnectionsDump
if(!data->pending_notification) {
// Send last notification
conns_add(&proxy->conns_updates, tuple, data);
}
data->status = zdtun_conn_get_status(conn_info);
} else
free_connection_data(data);
}
/* ******************************************************* */
static void on_packet(zdtun_t *tun, const char *packet, int size, uint8_t from_tun, const zdtun_conn_t *conn_info) {
conn_data_t *data = zdtun_conn_get_userdata(conn_info);
if(!data) {
log_e("Missing data in connection");
return;
}
vpnproxy_data_t *proxy = ((vpnproxy_data_t*)zdtun_userdata(tun));
const zdtun_5tuple_t *tuple = zdtun_conn_get_5tuple(conn_info);
data->status = zdtun_conn_get_status(conn_info);
if(shouldIgnoreConn(proxy, tuple)) {
/* NOTE: account connection stats also for non-matched connections */
if(from_tun) {
data->sent_pkts++;
data->sent_bytes += size;
} else {
data->rcvd_pkts++;
data->rcvd_bytes += size;
}
data->last_seen = time(NULL);
//log_d("Ignoring connection: UID=%d [filter=%d]", data->uid, proxy->uid_filter);
return;
}
account_packet(proxy, packet, size, from_tun, tuple, data);
}
/* ******************************************************* */
int run_proxy(vpnproxy_data_t *proxy) {
zdtun_t *tun;
char buffer[32768];
u_int64_t next_purge_ms;
int flags = fcntl(proxy->tunfd, F_GETFL, 0);
if (flags < 0 || fcntl(proxy->tunfd, F_SETFL, flags & ~O_NONBLOCK) < 0) {
log_f("fcntl ~O_NONBLOCK error [%d]: %s", errno,
strerror(errno));
return (-1);
}
zdtun_callbacks_t callbacks = {
.send_client = net2tun,
.account_packet = on_packet,
.on_socket_open = protectSocketCallback,
.on_connection_open = handle_new_connection,
.on_connection_close = destroy_connection,
};
// List of known DNS servers
add_known_dns_server(proxy, "8.8.8.8");
add_known_dns_server(proxy, "8.8.4.4");
add_known_dns_server(proxy, "1.1.1.1");
add_known_dns_server(proxy, "1.0.0.1");
add_known_dns_server(proxy, "2001:4860:4860::8888");
add_known_dns_server(proxy, "2001:4860:4860::8844");
add_known_dns_server(proxy, "2606:4700:4700::64");
add_known_dns_server(proxy, "2606:4700:4700::6400");
tun = zdtun_init(&callbacks, proxy);
if(tun == NULL) {
log_f("zdtun_init failed");
return(-2);
}
proxy->tun = tun;
new_dns_server = 0;
if(proxy->socks5.enabled) {
zdtun_ip_t dnatip = {0};
dnatip.ip4 = proxy->socks5.proxy_ip;
zdtun_set_socks5_proxy(tun, &dnatip, proxy->socks5.proxy_port, 4);
}
zdtun_ip_t ip = {0};
ip.ip4 = proxy->dns_server;
zdtun_set_dnat_info(tun, &ip, ntohs(53), 4);
refreshTime(proxy);
next_purge_ms = proxy->now_ms + PERIODIC_PURGE_TIMEOUT_MS;
log_d("Starting packet loop [tunfd=%d]", proxy->tunfd);
while(running) {
int max_fd;
fd_set fdset;
fd_set wrfds;
int size;
struct timeval timeout = {.tv_sec = 0, .tv_usec = 500*1000}; // wake every 500 ms
zdtun_fds(tun, &max_fd, &fdset, &wrfds);
FD_SET(proxy->tunfd, &fdset);
max_fd = max(max_fd, proxy->tunfd);
if(select(max_fd + 1, &fdset, &wrfds, NULL, &timeout) < 0) {
log_e("select failed[%d]: %s", errno, strerror(errno));
break;
}
if(!running)
break;
refreshTime(proxy);
if(FD_ISSET(proxy->tunfd, &fdset)) {
/* Packet from VPN */
size = read(proxy->tunfd, buffer, sizeof(buffer));
if (size > 0) {
zdtun_pkt_t pkt;
if (zdtun_parse_pkt(buffer, size, &pkt) != 0) {
log_d("zdtun_parse_pkt failed");
goto housekeeping;
}
proxy->last_pkt = &pkt;
proxy->last_conn_blocked = false;
if((pkt.tuple.ipver == 6) && (!proxy->ipv6.enabled)) {
char buf[512];
log_d("ignoring IPv6 packet: %s",
zdtun_5tuple2str(&pkt.tuple, buf, sizeof(buf)));
goto housekeeping;
}
// Skip established TCP connections
uint8_t is_tcp_established = ((pkt.tuple.ipproto == IPPROTO_TCP) &&
(!(pkt.tcp->th_flags & TH_SYN) || (pkt.tcp->th_flags & TH_ACK)));
zdtun_conn_t *conn = zdtun_lookup(tun, &pkt.tuple, !is_tcp_established);
if (!conn) {
if(proxy->last_conn_blocked) {
;
} else if(!is_tcp_established) {
char buf[512];
proxy->num_dropped_connections++;
log_e("zdtun_lookup failed: %s",
zdtun_5tuple2str(&pkt.tuple, buf, sizeof(buf)));
} else {
char buf[512];
log_d("skipping established TCP: %s",
zdtun_5tuple2str(&pkt.tuple, buf, sizeof(buf)));
}
goto housekeeping;
}
if(proxy->socks5.enabled)
check_socks5_redirection(proxy, &pkt, conn);
if(zdtun_forward(tun, &pkt, conn) != 0) {
char buf[512];
log_e("zdtun_forward failed: %s",
zdtun_5tuple2str(&pkt.tuple, buf, sizeof(buf)));
proxy->num_dropped_connections++;
zdtun_destroy_conn(tun, conn);
goto housekeeping;
}
} else if (size < 0)
log_e("recv(tunfd) returned error [%d]: %s", errno,
strerror(errno));
} else
zdtun_handle_fd(tun, &fdset, &wrfds);
housekeeping:
run_housekeeping(proxy);
if(proxy->now_ms >= next_purge_ms) {
zdtun_purge_expired(tun, proxy->now_ms / 1000);
next_purge_ms = proxy->now_ms + PERIODIC_PURGE_TIMEOUT_MS;
}
}
ztdun_finalize(tun);
return(0);
}

View File

@ -0,0 +1,377 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2021 - Emanuele Faranda
*/
#include <sys/un.h>
#include <linux/limits.h>
#include "vpnproxy.h"
#include "pcapd/pcapd.h"
#include "common/utils.h"
#include "third_party/uthash.h"
// Keep in sync with zdtun.c
#define ICMP_TIMEOUT_SEC 5
#define UDP_TIMEOUT_SEC 30
#define TCP_TIMEOUT_SEC 60
/* ******************************************************* */
typedef struct {
zdtun_5tuple_t tuple;
conn_data_t *data;
UT_hash_handle hh;
} pcap_conn_t;
/* ******************************************************* */
static int su_cmd(vpnproxy_data_t *proxy, const char *prog, const char *args) {
char cmd[1024];
int rv;
JNIEnv *env = proxy->env;
if(snprintf(cmd, sizeof(cmd), "su -c '%s %s'", prog, args) >= sizeof(cmd)) {
log_e("su cmd buffer too small");
return -1;
}
log_d("su_cmd: %s", cmd);
rv = system(cmd);
return rv;
}
/* ******************************************************* */
static void get_libprog_path(vpnproxy_data_t *proxy, const char *prog_name, char *buf, int bufsize) {
JNIEnv *env = proxy->env;
jobject prog_str = (*env)->NewStringUTF(env, prog_name);
buf[0] = '\0';
if((prog_str == NULL) || jniCheckException(env)) {
log_e("could not allocate get_libprog_path string");
return;
}
jstring obj = (*env)->CallObjectMethod(env, proxy->vpn_service, mids.getLibprogPath, prog_str);
if(!jniCheckException(env)) {
const char *value = (*env)->GetStringUTFChars(env, obj, 0);
strncpy(buf, value, bufsize);
buf[bufsize - 1] = '\0';
(*env)->ReleaseStringUTFChars(env, obj, value);
}
(*env)->DeleteLocalRef(env, obj);
}
/* ******************************************************* */
static void kill_pcapd(vpnproxy_data_t *proxy) {
int pid;
char pid_s[8];
FILE *f = fopen(PCAPD_PID, "r");
if(f == NULL)
return;
fgets(pid_s, sizeof(pid_s), f);
pid = atoi(pid_s);
if(pid != 0) {
log_d("Killing old pcapd with pid %d", pid);
su_cmd(proxy, "kill", pid_s);
}
fclose(f);
}
/* ******************************************************* */
static int connectPcapd(vpnproxy_data_t *proxy) {
int sock;
int client = -1;
char workdir[PATH_MAX], pcapd[PATH_MAX];
getStringPref(proxy, "getPcapdWorkingDir", workdir, PATH_MAX);
get_libprog_path(proxy, "pcapd", pcapd, sizeof(pcapd));
if(!pcapd[0])
return(-1);
if(chdir(workdir) < 0) {
log_f("chdir to %s failed [%d]: %s", workdir,
errno, strerror(errno));
return (-1);
}
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if(sock < 0) {
log_f("AF_UNIX socket creation failed [%d]: %s", errno,
strerror(errno));
return (-1);
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, PCAPD_SOCKET_PATH);
kill_pcapd(proxy);
unlink(PCAPD_PID);
unlink(PCAPD_SOCKET_PATH);
if(bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
log_f("AF_UNIX bind failed [%d]: %s", errno,
strerror(errno));
goto cleanup;
}
listen(sock, 1);
log_d("AF_UNIX socket listening at '%s'", addr.sun_path);
// Start the daemon
char args[32];
snprintf(args, sizeof(args), "-d %d", proxy->app_filter);
su_cmd(proxy, pcapd, args);
// Wait for pcapd to start
struct timeval timeout = {.tv_sec = 1, .tv_usec = 0};
fd_set selfds = {0};
FD_SET(sock, &selfds);
select(sock + 1, &selfds, NULL, NULL, &timeout);
if(!FD_ISSET(sock, &selfds)) {
log_f("pcapd daemon did not spawn");
goto cleanup;
}
if((client = accept(sock, NULL, NULL)) < 0) {
log_f("AF_UNIX accept failed [%d]: %s", errno,
strerror(errno));
goto cleanup;
}
log_d("Connected to pcapd");
unlink(PCAPD_SOCKET_PATH);
cleanup:
close(sock);
return client;
}
/* ******************************************************* */
static void handle_packet(vpnproxy_data_t *proxy, pcap_conn_t **connections, pcapd_hdr_t *hdr, const char *buffer) {
zdtun_5tuple_t search;
zdtun_pkt_t pkt;
pcap_conn_t *conn = NULL;
uint8_t from_tun = (hdr->flags & PCAPD_FLAG_TX);
// NOTE: only IP packets supported
if(zdtun_parse_pkt(buffer, hdr->len, &pkt) != 0) {
log_d("zdtun_parse_pkt failed");
return;
}
if(!from_tun) {
// Packet from the internet, swap src and dst
search.ipver = pkt.tuple.ipver;
search.ipproto = pkt.tuple.ipproto;
search.dst_port = pkt.tuple.src_port;
search.src_port = pkt.tuple.dst_port;
search.dst_ip = pkt.tuple.src_ip;
search.src_ip = pkt.tuple.dst_ip;
} else
search = pkt.tuple;
HASH_FIND(hh, *connections, &search, sizeof(zdtun_5tuple_t), conn);
if(!conn) {
conn_data_t *data = new_connection(proxy, &search, hdr->uid);
if (!data)
return;
conn = malloc(sizeof(pcap_conn_t));
if (!conn) {
log_e("malloc(pcap_conn_t) failed with code %d/%s",
errno, strerror(errno));
return;
}
conn->tuple = search;
conn->data = data;
// TODO read from linux?
data->status = CONN_STATUS_CONNECTED;
HASH_ADD(hh, *connections, tuple, sizeof(search), conn);
data->incr_id = proxy->incr_id++;
conns_add(&proxy->new_conns, &search, data);
switch(conn->tuple.ipproto) {
case IPPROTO_TCP:
proxy->stats.num_tcp_conn++;
proxy->stats.num_tcp_opened++;
break;
case IPPROTO_UDP:
proxy->stats.num_udp_conn++;
proxy->stats.num_udp_opened++;
break;
case IPPROTO_ICMP:
proxy->stats.num_icmp_conn++;
proxy->stats.num_icmp_opened++;
break;
}
}
account_packet(proxy, buffer, hdr->len, from_tun, &conn->tuple, conn->data);
}
/* ******************************************************* */
static void purge_expired_connections(vpnproxy_data_t *proxy, pcap_conn_t **connections, uint8_t purge_all) {
pcap_conn_t *conn, *tmp;
time_t now = proxy->now_ms / 1000;
HASH_ITER(hh, *connections, conn, tmp) {
time_t timeout = 0;
// TODO: sync with linux?
switch(conn->tuple.ipproto) {
case IPPROTO_TCP:
timeout = TCP_TIMEOUT_SEC;
break;
case IPPROTO_UDP:
timeout = UDP_TIMEOUT_SEC;
break;
case IPPROTO_ICMP:
timeout = ICMP_TIMEOUT_SEC;
break;
}
if(purge_all || (conn->data->status >= CONN_STATUS_CLOSED) || (now >= (timeout + conn->data->last_seen))) {
log_d("IDLE (type=%d)", conn->tuple.ipproto);
// Will free the data in sendConnectionsDump
if(!conn->data->pending_notification)
conns_add(&proxy->conns_updates, &conn->tuple, conn->data);
conn->data->status = CONN_STATUS_CLOSED;
switch (conn->tuple.ipproto) {
case IPPROTO_TCP:
proxy->stats.num_tcp_conn--;
break;
case IPPROTO_UDP:
proxy->stats.num_udp_conn--;
break;
case IPPROTO_ICMP:
proxy->stats.num_icmp_conn--;
break;
}
HASH_DELETE(hh, *connections, conn);
free(conn);
}
}
}
/* ******************************************************* */
int run_root(vpnproxy_data_t *proxy) {
int sock = connectPcapd(proxy);
int rv = -1;
char buffer[16384];
pcap_conn_t *connections = NULL;
u_int64_t next_purge_ms;
if(sock < 0)
return(-1);
refreshTime(proxy);
next_purge_ms = proxy->now_ms + PERIODIC_PURGE_TIMEOUT_MS;
log_d("Starting packet loop");
while(running) {
pcapd_hdr_t hdr;
fd_set fdset = {0};
FD_SET(sock, &fdset);
struct timeval timeout = {.tv_sec = 0, .tv_usec = 500*1000}; // wake every 500 ms
if(select(sock + 1, &fdset, NULL, NULL, &timeout) < 0) {
log_e("select failed[%d]: %s", errno, strerror(errno));
goto cleanup;
}
refreshTime(proxy);
if(!running)
break;
if(!FD_ISSET(sock, &fdset))
goto housekeeping;
if(xread(sock, &hdr, sizeof(hdr)) < 0) {
log_e("read hdr from pcapd failed[%d]: %s", errno, strerror(errno));
goto cleanup;
}
if(hdr.len > sizeof(buffer)) {
log_e("packet too big (%d B)", hdr.len);
goto cleanup;
}
if(xread(sock, buffer, hdr.len) < 0) {
log_e("read %d B packet from pcapd failed[%d]: %s", hdr.len, errno, strerror(errno));
goto cleanup;
}
handle_packet(proxy, &connections, &hdr, buffer);
housekeeping:
run_housekeeping(proxy);
if(proxy->now_ms >= next_purge_ms) {
purge_expired_connections(proxy, &connections, 0);
next_purge_ms = proxy->now_ms + PERIODIC_PURGE_TIMEOUT_MS;
}
}
rv = 0;
cleanup:
purge_expired_connections(proxy, &connections, 1 /* purge_all */);
close(sock);
return rv;
}

View File

@ -23,7 +23,7 @@
#include <sys/socket.h>
#include <android/log.h>
#include <errno.h>
#include "pcap.h"
#include "pcap_utils.h"
/* ******************************************************* */

View File

@ -1,59 +0,0 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2020-21 - Emanuele Faranda
*/
#include <stdint.h>
/* ******************************************************* */
// from DHCPd
static u_int16_t in_cksum(const char *buf, size_t nbytes, u_int32_t sum) {
u_int16_t i;
/* Checksum all the pairs of bytes first... */
for (i = 0; i < (nbytes & ~1U); i += 2) {
sum += (u_int16_t) ntohs(*((u_int16_t *)(buf + i)));
/* Add carry. */
if(sum > 0xFFFF)
sum -= 0xFFFF;
}
/* If there's a single byte left over, checksum it, too. Network
byte order is big-endian, so the remaining byte is the high byte. */
if(i < nbytes) {
sum += buf [i] << 8;
/* Add carry. */
if(sum > 0xFFFF)
sum -= 0xFFFF;
}
return sum;
}
/* ******************************************************* */
static inline u_int16_t wrapsum(u_int32_t sum) {
sum = ~sum & 0xFFFF;
return htons(sum);
}
/* ******************************************************* */
static u_int16_t ip_checksum(const void *buf, size_t hdr_len) {
return wrapsum(in_cksum(buf, hdr_len, 0));
}

File diff suppressed because it is too large Load Diff

View File

@ -17,17 +17,27 @@
* Copyright 2020-21 - Emanuele Faranda
*/
#ifndef __PCAPDROID_H__
#define __PCAPDROID_H__
#include <jni.h>
#include <stdbool.h>
#include "zdtun.h"
#include "uid_resolver.h"
#include "ip_lru.h"
#include <ndpi_api.h>
#include "ndpi_api.h"
#include "common/uid_resolver.h"
#ifndef REMOTE_CAPTURE_VPNPROXY_H
#define REMOTE_CAPTURE_VPNPROXY_H
#define CAPTURE_STATS_UPDATE_FREQUENCY_MS 300
#define CONNECTION_DUMP_UPDATE_FREQUENCY_MS 1000
#define MAX_JAVA_DUMP_DELAY_MS 1000
#define MAX_DPI_PACKETS 12
#define MAX_HOST_LRU_SIZE 128
#define JAVA_PCAP_BUFFER_SIZE (512*1024) // 512K
#define PERIODIC_PURGE_TIMEOUT_MS 5000
#define UID_UNKNOWN -1
#define DNS_FLAGS_MASK 0x8000
#define DNS_TYPE_REQUEST 0x0000
#define DNS_TYPE_RESPONSE 0x8000
typedef struct capture_stats {
jlong sent_bytes;
@ -77,6 +87,7 @@ typedef struct vpnproxy_data {
jint sdk;
JNIEnv *env;
jobject vpn_service;
jint app_filter;
u_int32_t vpn_dns;
u_int32_t dns_server;
u_int32_t vpn_ipv4;
@ -90,7 +101,10 @@ typedef struct vpnproxy_data {
conn_array_t new_conns;
conn_array_t conns_updates;
zdtun_pkt_t *last_pkt;
zdtun_t *tun;
bool last_conn_blocked;
bool root_capture;
zdtun_statistics_t stats;
struct {
u_int32_t collector_addr;
@ -120,4 +134,64 @@ typedef struct vpnproxy_data {
capture_stats_t capture_stats;
} vpnproxy_data_t;
#endif //REMOTE_CAPTURE_H
/* ******************************************************* */
typedef struct dns_packet {
uint16_t transaction_id;
uint16_t flags;
uint16_t questions;
uint16_t answ_rrs;
uint16_t auth_rrs;
uint16_t additional_rrs;
uint8_t initial_dot; // just skip
uint8_t queries[];
} __attribute__((packed)) dns_packet_t;
/* ******************************************************* */
typedef struct jni_methods {
jmethodID reportError;
jmethodID getApplicationByUid;
jmethodID protect;
jmethodID dumpPcapData;
jmethodID sendConnectionsDump;
jmethodID connInit;
jmethodID connSetData;
jmethodID sendServiceStatus;
jmethodID sendStatsDump;
jmethodID statsInit;
jmethodID statsSetData;
jmethodID getLibprogPath;
} jni_methods_t;
typedef struct jni_classes {
jclass vpn_service;
jclass conn;
jclass stats;
} jni_classes_t;
/* ******************************************************* */
extern jni_methods_t mids;
extern jni_classes_t cls;
extern bool running;
extern uint32_t new_dns_server;
extern bool dump_vpn_stats_now;
void free_ndpi(conn_data_t *data);
void conns_add(conn_array_t *arr, const zdtun_5tuple_t *tuple, conn_data_t *data);
void end_ndpi_detection(conn_data_t *data, vpnproxy_data_t *proxy, const zdtun_5tuple_t *tuple);
void run_housekeeping(vpnproxy_data_t *proxy);
void account_packet(vpnproxy_data_t *proxy, const char *packet, int size, uint8_t from_tun, const zdtun_5tuple_t *tuple, conn_data_t *data);
int resolve_uid(vpnproxy_data_t *proxy, const zdtun_5tuple_t *conn_info);
void free_connection_data(conn_data_t *data);
void protectSocket(vpnproxy_data_t *proxy, socket_t sock);
char* getStringPref(vpnproxy_data_t *proxy, const char *key, char *buf, int bufsize);
void refreshTime(vpnproxy_data_t *proxy);
void initMasterProtocolsBitmap(ndpi_protocol_bitmask_struct_t *b);
conn_data_t* new_connection(vpnproxy_data_t *proxy, const zdtun_5tuple_t *tuple, int uid);
int run_proxy(vpnproxy_data_t *proxy);
int run_root(vpnproxy_data_t *proxy);
#endif //__PCAPDROID_H__

View File

@ -1,8 +0,0 @@
<!--
Source https://github.com/encharm/Font-Awesome-SVG-PNG/blob/master/black/svg/github.svg
All brand icons are trademarks of their respective owners.
-->
<vector android:height="24dp" android:viewportHeight="1792"
android:viewportWidth="1792" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M896,128q209,0 385.5,103t279.5,279.5 103,385.5q0,251 -146.5,451.5t-378.5,277.5q-27,5 -40,-7t-13,-30q0,-3 0.5,-76.5t0.5,-134.5q0,-97 -52,-142 57,-6 102.5,-18t94,-39 81,-66.5 53,-105 20.5,-150.5q0,-119 -79,-206 37,-91 -8,-204 -28,-9 -81,11t-92,44l-38,24q-93,-26 -192,-26t-192,26q-16,-11 -42.5,-27t-83.5,-38.5 -85,-13.5q-45,113 -8,204 -79,87 -79,206 0,85 20.5,150t52.5,105 80.5,67 94,39 102.5,18q-39,36 -49,103 -21,10 -45,15t-57,5 -65.5,-21.5 -55.5,-62.5q-19,-32 -48.5,-52t-49.5,-24l-20,-3q-21,0 -29,4.5t-5,11.5 9,14 13,12l7,5q22,10 43.5,38t31.5,51l10,23q13,38 44,61.5t67,30 69.5,7 55.5,-3.5l23,-4q0,38 0.5,88.5t0.5,54.5q0,18 -13,30t-40,7q-232,-77 -378.5,-277.5t-146.5,-451.5q0,-209 103,-385.5t279.5,-279.5 385.5,-103zM419,1231q3,-7 -7,-12 -10,-3 -13,2 -3,7 7,12 9,6 13,-2zM450,1265q7,-5 -2,-16 -10,-9 -16,-3 -7,5 2,16 10,10 16,3zM480,1310q9,-7 0,-19 -8,-13 -17,-6 -9,5 0,18t17,7zM522,1352q8,-8 -4,-19 -12,-12 -20,-3 -9,8 4,19 12,12 20,3zM579,1377q3,-11 -13,-16 -15,-4 -19,7t13,15q15,6 19,-6zM642,1382q0,-13 -17,-11 -16,0 -16,11 0,13 17,11 16,0 16,-11zM700,1372q-2,-11 -18,-9 -16,3 -14,15t18,8 14,-14z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
</vector>

View File

@ -68,5 +68,14 @@
android:layout_alignParentStart="true"
android:layout_marginTop="20dp"
android:text="@string/gpl_license_link"/>
<TextView
android:id="@+id/app_source_link"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/app_license_link"
android:layout_alignParentStart="true"
android:layout_marginTop="20dp"
android:text="@string/source_code"/>
</RelativeLayout>
</ScrollView>

View File

@ -140,7 +140,8 @@ android:layout_height="fill_parent">
<TableRow
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="4dp">
android:layout_marginBottom="4dp"
android:id="@+id/dns_server_row">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -158,7 +159,8 @@ android:layout_height="fill_parent">
<TableRow
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="4dp">
android:layout_marginBottom="4dp"
android:id="@+id/dns_queries_row">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -176,7 +178,8 @@ android:layout_height="fill_parent">
<TableRow
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="4dp">
android:layout_marginBottom="4dp"
android:id="@+id/open_sockets_row">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -194,7 +197,8 @@ android:layout_height="fill_parent">
<TableRow
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="4dp">
android:layout_marginBottom="4dp"
android:id="@+id/max_fd_row">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"

View File

@ -33,10 +33,15 @@
android:id="@+id/app_name"
android:textStyle="bold"
android:textSize="14sp"
app:layout_constrainedWidth="true"
android:ellipsize="end"
android:singleLine="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/status_ind"
app:layout_constraintHorizontal_bias="0"
tools:text="Android" />
<TextView

View File

@ -28,9 +28,9 @@
android:title="@string/open_telegram_group"
android:icon="@drawable/ic_telegram" />
<item
android:id="@+id/action_open_github"
android:title="@string/source_code"
android:icon="@drawable/ic_github" />
android:id="@+id/action_donate"
android:title="@string/donate"
android:icon="@drawable/ic_money" />
<item
android:id="@+id/action_about"

View File

@ -111,5 +111,13 @@
<string name="theme_light">Chiaro</string>
<string name="theme_dark">Scuro</string>
<string name="app_theme">Tema</string>
<string name="enable_socks5_proxy">Abilita Proxy SOCKS5</string>
<string name="enable_socks5_proxy_summary">Se abilitato, tutte le connessioni TCP saranno redirette al proxy SOCKS5 configurato.</string>
<string name="proxy">Proxy</string>
<string name="proxy_ip_address">Indiritto IP del Proxy</string>
<string name="proxy_port">Porta del Proxy</string>
<string name="root_capture">Cattura come Root</string>
<string name="root_capture_summary">Catturare i pacchetti come root permette a PCAPdroid di funzionare assieme ad altre app VPN.</string>
<string name="donate">Donazioni</string>
</resources>

View File

@ -125,5 +125,8 @@
<string name="proxy">Proxy</string>
<string name="proxy_ip_address">Proxy IP Address</string>
<string name="proxy_port">Proxy Port</string>
<string name="root_capture">Capture as Root</string>
<string name="root_capture_summary">Capturing packets as root allows PCAPdroid to run with other VPN apps.</string>
<string name="donate">Donate</string>
</resources>

View File

@ -43,7 +43,7 @@
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/proxy" app:iconSpaceReserved="false">
<PreferenceCategory app:title="@string/proxy" app:iconSpaceReserved="false" app:key="proxy_prefs">
<SwitchPreference
app:key="tls_decryption_enabled"
app:title="@string/enable_socks5_proxy"
@ -74,14 +74,7 @@
</Preference>
</PreferenceCategory>
<PreferenceCategory app:title="@string/other_prefs" app:iconSpaceReserved="false">
<SwitchPreference
app:key="ipv6_enabled"
app:title="@string/enable_ipv6"
app:iconSpaceReserved="false"
app:summary="@string/enable_ipv6_summary"
app:defaultValue="false" />
<PreferenceCategory app:title="@string/other_prefs" app:iconSpaceReserved="false" >
<DropDownPreference
app:key="app_theme"
app:title="@string/app_theme"
@ -99,5 +92,19 @@
app:iconSpaceReserved="false"
app:defaultValue="system"
app:useSimpleSummaryProvider="true"/>
<SwitchPreference
app:key="root_capture"
app:title="@string/root_capture"
app:iconSpaceReserved="false"
app:summary="@string/root_capture_summary"
app:defaultValue="false" />
<SwitchPreference
app:key="ipv6_enabled"
app:title="@string/enable_ipv6"
app:iconSpaceReserved="false"
app:summary="@string/enable_ipv6_summary"
app:defaultValue="false" />
</PreferenceCategory>
</PreferenceScreen>

1
submodules/libpcap Submodule

@ -0,0 +1 @@
Subproject commit f8e410a8feaa1fb40aa4b833a3fa8af2bd5bca1e

@ -1 +1 @@
Subproject commit c0d06e4996564c1a7295930148661ac2971aa086
Subproject commit 0196fee9ff41637da3721c18796eac93eee98b90