Skip to content

Commit

Permalink
Support IPv6 and host names in socks5 mode
Browse files Browse the repository at this point in the history
Closes #343
  • Loading branch information
emanuele-f committed Aug 20, 2023
1 parent 958fc6a commit 6d7708e
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 20 deletions.
53 changes: 50 additions & 3 deletions app/src/main/java/com/emanuelef/remote_capture/CaptureHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
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;
Expand All @@ -34,7 +36,11 @@
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;
Expand All @@ -48,7 +54,7 @@ public CaptureHelper(ComponentActivity activity) {

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);
Expand All @@ -64,14 +70,55 @@ private void startCaptureOk() {
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();

mSettings = settings;

if(settings.root_capture || settings.readFromPcap()) {
startCaptureOk();
resolveHosts();
return;
}

Expand All @@ -93,7 +140,7 @@ public void startCapture(CaptureSettings settings) {
})
.show();
} else
startCaptureOk();
resolveHosts();
}

public void setListener(CaptureStartListener listener) {
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/emanuelef/remote_capture/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,10 @@ public static boolean isLocalNetworkAddress(InetAddress checkAddress) {
}

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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ private String checkRemoteServerNotAllowed(CaptureSettings settings) {

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ protected void onCreate(Bundle savedInstanceState) {
mCapHelper = new CaptureHelper(this);
mCapHelper.setListener(success -> {
if(!success) {
Log.w(TAG, "VPN request failed");
Log.w(TAG, "Capture start failed");
appStateReady();
}
});
Expand Down Expand Up @@ -684,7 +684,7 @@ private boolean showRemoteServerAlert() {
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import java.util.Objects;

public class Socks5Settings extends PreferenceFragmentCompat {
private EditTextPreference mProxyIp;
private EditTextPreference mProxyHost;
private EditTextPreference mProxyPort;
private EditTextPreference mUsername;
private EditTextPreference mPassword;
Expand All @@ -44,8 +44,8 @@ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable S
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));
Expand All @@ -70,7 +70,7 @@ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable S
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public CaptureSettings(Context ctx, SharedPreferences prefs) {
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) : "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public static void setPortMappingEnabled(SharedPreferences p, boolean enabled) {
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, "")); }
Expand Down
4 changes: 1 addition & 3 deletions app/src/main/jni/core/capture_vpn.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
28 changes: 27 additions & 1 deletion app/src/main/jni/core/jni_impl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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};

Expand Down
4 changes: 3 additions & 1 deletion app/src/main/jni/core/pcapdroid.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
2 changes: 1 addition & 1 deletion app/src/main/res/xml/socks5_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
4 changes: 2 additions & 2 deletions docs/app_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down

0 comments on commit 6d7708e

Please sign in to comment.