Support IPv6 and host names in socks5 mode

Closes #343
This commit is contained in:
emanuele-f
2023-08-20 19:58:15 +02:00
parent 958fc6ade0
commit 6d7708eec0
13 changed files with 99 additions and 20 deletions
@@ -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, "")); }
+1 -3
View File
@@ -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);
+27 -1
View File
@@ -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};
+3 -1
View File
@@ -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);
+2
View File
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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 |