mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-05-08 21:12:26 +00:00
@@ -23,6 +23,8 @@ import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.VpnService;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.emanuelef.remote_capture.interfaces.CaptureStartListener;
|
||||
import com.emanuelef.remote_capture.model.CaptureSettings;
|
||||
@@ -34,7 +36,11 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class CaptureHelper {
|
||||
private static final String TAG = "CaptureHelper";
|
||||
private final ComponentActivity mActivity;
|
||||
private final ActivityResultLauncher<Intent> mLauncher;
|
||||
private CaptureSettings mSettings;
|
||||
@@ -48,7 +54,7 @@ public class CaptureHelper {
|
||||
|
||||
private void captureServiceResult(final ActivityResult result) {
|
||||
if(result.getResultCode() == Activity.RESULT_OK)
|
||||
startCaptureOk();
|
||||
resolveHosts();
|
||||
else if(mListener != null) {
|
||||
Utils.showToastLong(mActivity, R.string.vpn_setup_failed);
|
||||
mListener.onCaptureStartResult(false);
|
||||
@@ -64,6 +70,47 @@ public class CaptureHelper {
|
||||
mListener.onCaptureStartResult(true);
|
||||
}
|
||||
|
||||
private String resolveHost(String host) {
|
||||
Log.d(TAG, "Resolving host: " + host);
|
||||
|
||||
try {
|
||||
return InetAddress.getByName(host).getHostAddress();
|
||||
} catch (UnknownHostException ignored) {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String doResolveHosts() {
|
||||
// NOTE: hosts must be resolved before starting the VPN and in a separate thread
|
||||
String resolved;
|
||||
|
||||
if((resolved = resolveHost(mSettings.socks5_proxy_address)) == null)
|
||||
return mSettings.socks5_proxy_address;
|
||||
else if(!resolved.equals(mSettings.socks5_proxy_address)) {
|
||||
Log.i(TAG, "Resolved SOCKS5 proxy address: " + resolved);
|
||||
mSettings.socks5_proxy_address = resolved;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void resolveHosts() {
|
||||
final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
(new Thread(() -> {
|
||||
String failed_host = doResolveHosts();
|
||||
|
||||
handler.post(() -> {
|
||||
if(failed_host == null)
|
||||
startCaptureOk();
|
||||
else {
|
||||
Utils.showToastLong(mActivity, R.string.host_resolution_failed, failed_host);
|
||||
mListener.onCaptureStartResult(false);
|
||||
}
|
||||
});
|
||||
})).start();
|
||||
}
|
||||
|
||||
public void startCapture(CaptureSettings settings) {
|
||||
if(CaptureService.isServiceActive())
|
||||
CaptureService.stopService();
|
||||
@@ -71,7 +118,7 @@ public class CaptureHelper {
|
||||
mSettings = settings;
|
||||
|
||||
if(settings.root_capture || settings.readFromPcap()) {
|
||||
startCaptureOk();
|
||||
resolveHosts();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,7 +140,7 @@ public class CaptureHelper {
|
||||
})
|
||||
.show();
|
||||
} else
|
||||
startCaptureOk();
|
||||
resolveHosts();
|
||||
}
|
||||
|
||||
public void setListener(CaptureStartListener listener) {
|
||||
|
||||
@@ -484,6 +484,10 @@ public class Utils {
|
||||
}
|
||||
|
||||
public static boolean isLocalNetworkAddress(String checkAddress) {
|
||||
// this check is necessary as otherwise host resolution would be triggered on the main thread
|
||||
if(!validateIpAddress(checkAddress))
|
||||
return false;
|
||||
|
||||
try {
|
||||
return isLocalNetworkAddress(InetAddress.getByName(checkAddress));
|
||||
} catch (UnknownHostException ignored) {
|
||||
|
||||
@@ -220,7 +220,7 @@ public class CaptureCtrl extends AppCompatActivity {
|
||||
|
||||
if(settings.socks5_enabled &&
|
||||
!Utils.isLocalNetworkAddress(settings.socks5_proxy_address) &&
|
||||
!Prefs.getSocks5ProxyAddress(prefs).equals(settings.socks5_proxy_address))
|
||||
!Prefs.getSocks5ProxyHost(prefs).equals(settings.socks5_proxy_address))
|
||||
return settings.socks5_proxy_address;
|
||||
|
||||
// ok
|
||||
|
||||
@@ -158,7 +158,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
|
||||
mCapHelper = new CaptureHelper(this);
|
||||
mCapHelper.setListener(success -> {
|
||||
if(!success) {
|
||||
Log.w(TAG, "VPN request failed");
|
||||
Log.w(TAG, "Capture start failed");
|
||||
appStateReady();
|
||||
}
|
||||
});
|
||||
@@ -684,7 +684,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
|
||||
return false; // already acknowledged
|
||||
|
||||
if(((Prefs.getDumpMode(mPrefs) == Prefs.DumpMode.UDP_EXPORTER) && !Utils.isLocalNetworkAddress(Prefs.getCollectorIp(mPrefs))) ||
|
||||
(Prefs.getSocks5Enabled(mPrefs) && !Utils.isLocalNetworkAddress(Prefs.getSocks5ProxyAddress(mPrefs)))) {
|
||||
(Prefs.getSocks5Enabled(mPrefs) && !Utils.isLocalNetworkAddress(Prefs.getSocks5ProxyHost(mPrefs)))) {
|
||||
Log.i(TAG, "Showing possible scan notice");
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
|
||||
@@ -33,7 +33,7 @@ import com.emanuelef.remote_capture.model.Prefs;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Socks5Settings extends PreferenceFragmentCompat {
|
||||
private EditTextPreference mProxyIp;
|
||||
private EditTextPreference mProxyHost;
|
||||
private EditTextPreference mProxyPort;
|
||||
private EditTextPreference mUsername;
|
||||
private EditTextPreference mPassword;
|
||||
@@ -44,8 +44,8 @@ public class Socks5Settings extends PreferenceFragmentCompat {
|
||||
setPreferencesFromResource(R.xml.socks5_preferences, rootKey);
|
||||
|
||||
/* SOCKS5 Proxy IP validation */
|
||||
mProxyIp = Objects.requireNonNull(findPreference(Prefs.PREF_SOCKS5_PROXY_IP_KEY));
|
||||
mProxyIp.setOnPreferenceChangeListener((preference, newValue) -> Utils.validateIpAddress(newValue.toString()));
|
||||
mProxyHost = Objects.requireNonNull(findPreference(Prefs.PREF_SOCKS5_PROXY_IP_KEY));
|
||||
mProxyHost.setOnPreferenceChangeListener((preference, newValue) -> Utils.validateHost(newValue.toString()));
|
||||
|
||||
/* SOCKS5 Proxy port validation */
|
||||
mProxyPort = Objects.requireNonNull(findPreference(Prefs.PREF_SOCKS5_PROXY_PORT_KEY));
|
||||
@@ -70,7 +70,7 @@ public class Socks5Settings extends PreferenceFragmentCompat {
|
||||
}
|
||||
|
||||
private void toggleVisisiblity(boolean socks5_enabled, boolean auth_enabled) {
|
||||
mProxyIp.setVisible(socks5_enabled);
|
||||
mProxyHost.setVisible(socks5_enabled);
|
||||
mProxyPort.setVisible(socks5_enabled);
|
||||
mSocks5AuthEnabled.setVisible(socks5_enabled);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public class CaptureSettings implements Serializable {
|
||||
collector_port = Prefs.getCollectorPort(prefs);
|
||||
http_server_port = Prefs.getHttpServerPort(prefs);
|
||||
socks5_enabled = Prefs.getSocks5Enabled(prefs);
|
||||
socks5_proxy_address = Prefs.getSocks5ProxyAddress(prefs);
|
||||
socks5_proxy_address = Prefs.getSocks5ProxyHost(prefs);
|
||||
socks5_proxy_port = Prefs.getSocks5ProxyPort(prefs);
|
||||
socks5_username = Prefs.isSocks5AuthEnabled(prefs) ? Prefs.getSocks5Username(prefs) : "";
|
||||
socks5_password = Prefs.isSocks5AuthEnabled(prefs) ? Prefs.getSocks5Password(prefs) : "";
|
||||
|
||||
@@ -170,7 +170,7 @@ public class Prefs {
|
||||
public static int getHttpServerPort(SharedPreferences p) { return(Integer.parseInt(p.getString(Prefs.PREF_HTTP_SERVER_PORT, "8080"))); }
|
||||
public static boolean getTlsDecryptionEnabled(SharedPreferences p) { return(p.getBoolean(PREF_TLS_DECRYPTION_KEY, false)); }
|
||||
public static boolean getSocks5Enabled(SharedPreferences p) { return(p.getBoolean(PREF_SOCKS5_ENABLED_KEY, false)); }
|
||||
public static String getSocks5ProxyAddress(SharedPreferences p) { return(p.getString(PREF_SOCKS5_PROXY_IP_KEY, "0.0.0.0")); }
|
||||
public static String getSocks5ProxyHost(SharedPreferences p) { return(p.getString(PREF_SOCKS5_PROXY_IP_KEY, "0.0.0.0")); }
|
||||
public static int getSocks5ProxyPort(SharedPreferences p) { return(Integer.parseInt(p.getString(Prefs.PREF_SOCKS5_PROXY_PORT_KEY, "8080"))); }
|
||||
public static boolean isSocks5AuthEnabled(SharedPreferences p) { return(p.getBoolean(PREF_SOCKS5_AUTH_ENABLED_KEY, false)); }
|
||||
public static String getSocks5Username(SharedPreferences p) { return(p.getString(PREF_SOCKS5_USERNAME_KEY, "")); }
|
||||
|
||||
@@ -473,9 +473,7 @@ int run_vpn(pcapdroid_t *pd) {
|
||||
new_dns_server = 0;
|
||||
|
||||
if(pd->socks5.enabled) {
|
||||
zdtun_ip_t dnatip = {0};
|
||||
dnatip.ip4 = pd->socks5.proxy_ip;
|
||||
zdtun_set_socks5_proxy(zdt, &dnatip, pd->socks5.proxy_port, 4);
|
||||
zdtun_set_socks5_proxy(zdt, &pd->socks5.proxy_ip, pd->socks5.proxy_port, pd->socks5.proxy_ipver);
|
||||
|
||||
if(pd->socks5.proxy_user[0] && pd->socks5.proxy_pass[0])
|
||||
zdtun_set_socks5_userpass(zdt, pd->socks5.proxy_user, pd->socks5.proxy_pass);
|
||||
|
||||
@@ -599,7 +599,7 @@ Java_com_emanuelef_remote_1capture_CaptureService_runPacketLoop(JNIEnv *env, jcl
|
||||
},
|
||||
.socks5 = {
|
||||
.enabled = (bool) getIntPref(env, vpn, "getSocks5Enabled"),
|
||||
.proxy_ip = getIPv4Pref(env, vpn, "getSocks5ProxyAddress"),
|
||||
.proxy_ip = getIPPref(env, vpn, "getSocks5ProxyAddress", &pd.socks5.proxy_ipver),
|
||||
.proxy_port = htons(getIntPref(env, vpn, "getSocks5ProxyPort")),
|
||||
},
|
||||
.malware_detection = {
|
||||
@@ -1149,6 +1149,32 @@ u_int32_t getIPv4Pref(JNIEnv *env, jobject vpn_inst, const char *key) {
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
zdtun_ip_t getIPPref(JNIEnv *env, jobject vpn_inst, const char *key, int *ip_ver) {
|
||||
zdtun_ip_t rv = {};
|
||||
|
||||
jmethodID midMethod = jniGetMethodID(env, cls.vpn_service, key, "()Ljava/lang/String;");
|
||||
jstring obj = (*env)->CallObjectMethod(env, vpn_inst, midMethod);
|
||||
|
||||
if(!jniCheckException(env)) {
|
||||
const char *value = (*env)->GetStringUTFChars(env, obj, 0);
|
||||
log_d("getIPPref(%s) = %s", key, value);
|
||||
|
||||
if(*value) {
|
||||
*ip_ver = zdtun_parse_ip(value, &rv);
|
||||
|
||||
if(*ip_ver < 0)
|
||||
log_e("%s() returned invalid IP address: %s", key, value);
|
||||
}
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, obj, value);
|
||||
}
|
||||
|
||||
(*env)->DeleteLocalRef(env, obj);
|
||||
return(rv);
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
struct in6_addr getIPv6Pref(JNIEnv *env, jobject vpn_inst, const char *key) {
|
||||
struct in6_addr addr = {0};
|
||||
|
||||
|
||||
@@ -239,8 +239,9 @@ typedef struct pcapdroid {
|
||||
|
||||
struct {
|
||||
bool enabled;
|
||||
u_int32_t proxy_ip;
|
||||
zdtun_ip_t proxy_ip;
|
||||
u_int32_t proxy_port;
|
||||
int proxy_ipver;
|
||||
char proxy_user[32];
|
||||
char proxy_pass[32];
|
||||
} socks5;
|
||||
@@ -395,6 +396,7 @@ uint16_t pd_ndpi2proto(ndpi_protocol proto);
|
||||
|
||||
char* getStringPref(pcapdroid_t *pd, const char *key, char *buf, int bufsize);
|
||||
int getIntPref(JNIEnv *env, jobject vpn_inst, const char *key);
|
||||
zdtun_ip_t getIPPref(JNIEnv *env, jobject vpn_inst, const char *key, int *ip_ver);
|
||||
uint32_t getIPv4Pref(JNIEnv *env, jobject vpn_inst, const char *key);
|
||||
struct in6_addr getIPv6Pref(JNIEnv *env, jobject vpn_inst, const char *key);
|
||||
void getApplicationByUid(pcapdroid_t *pd, jint uid, char *buf, int bufsize);
|
||||
|
||||
@@ -130,6 +130,7 @@
|
||||
<string name="socks5_auth">SOCKS5 authentication</string>
|
||||
<string name="socks5_auth_summary">Authenticate to the proxy via username and password</string>
|
||||
<string name="proxy_ip_address">Proxy IP address</string>
|
||||
<string name="proxy_host">Proxy host</string>
|
||||
<string name="proxy_port">Proxy port</string>
|
||||
<string name="root_capture">Capture as root</string>
|
||||
<string name="root_capture_summary">Allows PCAPdroid to run with other VPN apps</string>
|
||||
@@ -486,4 +487,5 @@
|
||||
<string name="root_capture_start_failed">Capture start failure. Ensure that the device is rooted with Magisk</string>
|
||||
<string name="pcap_read_error">PCAP read error</string>
|
||||
<string name="pcap_file_load_aborted">PCAP file loading aborted</string>
|
||||
<string name="host_resolution_failed">"Could not resolve host %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<EditTextPreference
|
||||
app:key="socks5_proxy_ip_address"
|
||||
app:title="@string/proxy_ip_address"
|
||||
app:title="@string/proxy_host"
|
||||
app:defaultValue="0.0.0.0"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
+2
-2
@@ -85,8 +85,8 @@ As shown above, the capture settings can be specified by using intent extras. Th
|
||||
| http_server_port | int | | | the HTTP server port in http_server mode |
|
||||
| pcap_uri | string | | | the URI for the PCAP dump in pcap_file mode (overrides pcap_name) |
|
||||
| socks5_enabled | bool | | vpn | true to redirect the TCP connections to a SOCKS5 proxy |
|
||||
| socks5_proxy_ip_address | string | | vpn | the IP address of the SOCKS5 proxy |
|
||||
| socks5_proxy_port | int | | vpn | the TCP port of the SOCKS5 proxy |
|
||||
| socks5_proxy_ip_address | string | | vpn | the SOCKS5 proxy host |
|
||||
| socks5_proxy_port | int | | vpn | the SOCKS5 proxy port |
|
||||
| root_capture | bool | | | true to capture packets in root mode, false to use the VPNService |
|
||||
| pcapdroid_trailer | bool | | | true to enable the PCAPdroid trailer |
|
||||
| capture_interface | string | | root | @inet \| any \| ifname - network interface to use in root mode |
|
||||
|
||||
Reference in New Issue
Block a user