mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-06-11 21:01:45 +08:00
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:
parent
bfe4d0a796
commit
c2df8436ab
5
.gitmodules
vendored
5
.gitmodules
vendored
@ -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
|
||||
|
||||
@ -22,7 +22,7 @@ android {
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path file('src/main/jni/vpnproxy-jni/CMakeLists.txt')
|
||||
path file('src/main/jni/CMakeLists.txt')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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)); }
|
||||
}
|
||||
|
||||
16
app/src/main/jni/CMakeLists.txt
Normal file
16
app/src/main/jni/CMakeLists.txt
Normal 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)
|
||||
3
app/src/main/jni/common/CMakeLists.txt
Normal file
3
app/src/main/jni/common/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
project(common C)
|
||||
|
||||
ADD_LIBRARY(common STATIC uid_lru.c utils.c uid_resolver.c)
|
||||
122
app/src/main/jni/common/uid_lru.c
Normal file
122
app/src/main/jni/common/uid_lru.c
Normal 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);
|
||||
}
|
||||
@ -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__
|
||||
@ -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) {
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
45
app/src/main/jni/common/utils.h
Normal file
45
app/src/main/jni/common/utils.h
Normal 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__
|
||||
36
app/src/main/jni/pcapd/CMakeLists.txt
Normal file
36
app/src/main/jni/pcapd/CMakeLists.txt
Normal 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)
|
||||
148
app/src/main/jni/pcapd/nl_utils.c
Normal file
148
app/src/main/jni/pcapd/nl_utils.c
Normal 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);
|
||||
}
|
||||
41
app/src/main/jni/pcapd/nl_utils.h
Normal file
41
app/src/main/jni/pcapd/nl_utils.h
Normal 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
|
||||
576
app/src/main/jni/pcapd/pcapd.c
Normal file
576
app/src/main/jni/pcapd/pcapd.c
Normal 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();
|
||||
}
|
||||
38
app/src/main/jni/pcapd/pcapd.h
Normal file
38
app/src/main/jni/pcapd/pcapd.h
Normal 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
|
||||
@ -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})
|
||||
|
||||
438
app/src/main/jni/vpnproxy-jni/capture_proxy.c
Normal file
438
app/src/main/jni/vpnproxy-jni/capture_proxy.c
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
377
app/src/main/jni/vpnproxy-jni/capture_root.c
Normal file
377
app/src/main/jni/vpnproxy-jni/capture_root.c
Normal 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;
|
||||
}
|
||||
@ -23,7 +23,7 @@
|
||||
#include <sys/socket.h>
|
||||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
#include "pcap.h"
|
||||
#include "pcap_utils.h"
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
@ -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
@ -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__
|
||||
|
||||
@ -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>
|
||||
5
app/src/main/res/drawable/ic_money.xml
Normal file
5
app/src/main/res/drawable/ic_money.xml
Normal 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>
|
||||
@ -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>
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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
1
submodules/libpcap
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit f8e410a8feaa1fb40aa4b833a3fa8af2bd5bca1e
|
||||
@ -1 +1 @@
|
||||
Subproject commit c0d06e4996564c1a7295930148661ac2971aa086
|
||||
Subproject commit 0196fee9ff41637da3721c18796eac93eee98b90
|
||||
Loading…
Reference in New Issue
Block a user