Ability to block QUIC always or only on decryption

Commit ddec1a8 limited the ability to block QUIC to only
connections matching the decryption rules. Some users may still want
to always block QUIC for different reasons, so this commit makes it
possible to choose the block policy to apply.

See #369
This commit is contained in:
emanuele-f 2024-01-28 20:04:51 +01:00
parent 26287640fd
commit 4a7a55d504
10 changed files with 70 additions and 24 deletions

View File

@ -1309,7 +1309,7 @@ public class CaptureService extends VpnService implements Runnable {
public int getVpnMTU() { return VPN_MTU; }
public int blockQuick() { return(mSettings.block_quic ? 1 : 0); }
public int getBlockQuickMode() { return mSettings.block_quic_mode.ordinal(); }
// returns 1 if dumpPcapData should be called
public int pcapDumpEnabled() {

View File

@ -132,13 +132,13 @@ public class SettingsActivity extends BaseActivity implements PreferenceFragment
public static class SettingsFragment extends PreferenceFragmentCompat {
private SwitchPreference mTlsDecryption;
private SwitchPreference mBlockQuic;
private SwitchPreference mFullPayloadEnabled;
private SwitchPreference mRootCaptureEnabled;
private SwitchPreference mAutoBlockPrivateDNS;
private EditTextPreference mMitmproxyOpts;
private DropDownPreference mIpMode;
private DropDownPreference mCapInterface;
private DropDownPreference mBlockQuic;
private Preference mVpnExceptions;
private Preference mSocks5Settings;
private Preference mDnsSettings;
@ -167,7 +167,8 @@ public class SettingsActivity extends BaseActivity implements PreferenceFragment
setupSecurityPrefs();
setupOtherPrefs();
socks5ProxyAndQuicHideShow(mTlsDecryption.isChecked(), rootCaptureEnabled());
socks5ProxyHideShow(mTlsDecryption.isChecked(), rootCaptureEnabled());
mBlockQuic.setVisible(!rootCaptureEnabled());
rootCaptureHideShow(rootCaptureEnabled());
Intent intent = requireActivity().getIntent();
@ -309,7 +310,7 @@ public class SettingsActivity extends BaseActivity implements PreferenceFragment
mMitmWizard.setVisible((boolean) newValue);
mMitmproxyOpts.setVisible((boolean) newValue);
socks5ProxyAndQuicHideShow((boolean) newValue, rootCaptureEnabled());
socks5ProxyHideShow((boolean) newValue, rootCaptureEnabled());
return true;
});
@ -343,9 +344,8 @@ public class SettingsActivity extends BaseActivity implements PreferenceFragment
mSocks5Settings = requirePreference("socks5_settings");
}
private void socks5ProxyAndQuicHideShow(boolean tlsDecryption, boolean rootEnabled) {
private void socks5ProxyHideShow(boolean tlsDecryption, boolean rootEnabled) {
mSocks5Settings.setVisible(!tlsDecryption && !rootEnabled);
mBlockQuic.setVisible(tlsDecryption && !rootEnabled);
}
private void setupOtherPrefs() {
@ -406,7 +406,7 @@ public class SettingsActivity extends BaseActivity implements PreferenceFragment
} else {
mAutoBlockPrivateDNS.setVisible(true);
mBlockQuic.setVisible(true);
socks5ProxyAndQuicHideShow(mTlsDecryption.isChecked(), false);
socks5ProxyHideShow(mTlsDecryption.isChecked(), false);
}
mIpMode.setVisible(!enabled);

View File

@ -26,7 +26,7 @@ public class CaptureSettings implements Serializable {
public boolean root_capture;
public boolean pcapdroid_trailer;
public boolean full_payload;
public boolean block_quic;
public Prefs.BlockQuicMode block_quic_mode;
public boolean auto_block_private_dns;
public boolean pcapng_format;
public String capture_interface;
@ -54,7 +54,7 @@ public class CaptureSettings implements Serializable {
capture_interface = Prefs.getCaptureInterface(prefs);
tls_decryption = Prefs.getTlsDecryptionEnabled(prefs);
full_payload = Prefs.getFullPayloadMode(prefs);
block_quic = Prefs.blockQuic(prefs);
block_quic_mode = Prefs.getBlockQuicMode(prefs);
auto_block_private_dns = Prefs.isPrivateDnsBlockingEnabled(prefs);
mitmproxy_opts = Prefs.getMitmproxyOpts(prefs);
pcapng_format = Prefs.isPcapngEnabled(ctx, prefs);
@ -82,7 +82,7 @@ public class CaptureSettings implements Serializable {
max_dump_size = getInt(intent, Prefs.PREF_MAX_DUMP_SIZE, 0);
tls_decryption = getBool(intent, Prefs.PREF_TLS_DECRYPTION_KEY, false);
full_payload = false;
block_quic = getBool(intent, Prefs.PREF_BLOCK_QUIC, false);
block_quic_mode = Prefs.getBlockQuicMode(getString(intent, Prefs.PREF_BLOCK_QUIC, Prefs.BLOCK_QUIC_MODE_DEFAULT));
auto_block_private_dns = getBool(intent, Prefs.PREF_AUTO_BLOCK_PRIVATE_DNS, true);
mitmproxy_opts = getString(intent, Prefs.PREF_MITMPROXY_OPTS, "");
pcapng_format = getBool(intent, Prefs.PREF_PCAPNG_ENABLED, false) && Billing.newInstance(ctx).isPurchased(Billing.PCAPNG_SKU);

View File

@ -41,6 +41,11 @@ public class Prefs {
public static final String IP_MODE_BOTH = "both";
public static final String IP_MODE_DEFAULT = IP_MODE_IPV4_ONLY;
public static final String BLOCK_QUIC_MODE_NEVER = "never";
public static final String BLOCK_QUIC_MODE_ALWAYS = "always";
public static final String BLOCK_QUIC_MODE_TO_DECRYPT = "to_decrypt";
public static final String BLOCK_QUIC_MODE_DEFAULT = BLOCK_QUIC_MODE_NEVER;
public static final String PAYLOAD_MODE_NONE = "none";
public static final String PAYLOAD_MODE_MINIMAL = "minimal";
public static final String PAYLOAD_MODE_FULL = "full";
@ -83,7 +88,7 @@ public class Prefs {
public static final String PREF_TLS_DECRYPTION_SETUP_DONE = "tls_decryption_setup_ok";
public static final String PREF_CA_INSTALLATION_SKIPPED = "ca_install_skipped";
public static final String PREF_FULL_PAYLOAD = "full_payload";
public static final String PREF_BLOCK_QUIC = "block_quic";
public static final String PREF_BLOCK_QUIC = "block_quic_mode";
public static final String PREF_AUTO_BLOCK_PRIVATE_DNS = "auto_block_private_dns";
public static final String PREF_APP_VERSION = "appver";
public static final String PREF_LOCKDOWN_VPN_NOTICE_SHOWN = "vpn_lockdown_notice";
@ -113,6 +118,12 @@ public class Prefs {
BOTH,
}
public enum BlockQuicMode {
NEVER,
ALWAYS,
TO_DECRYPT
}
public enum PayloadMode {
NONE,
MINIMAL,
@ -136,6 +147,14 @@ public class Prefs {
}
}
public static BlockQuicMode getBlockQuicMode(String pref) {
switch (pref) {
case BLOCK_QUIC_MODE_ALWAYS: return BlockQuicMode.ALWAYS;
case BLOCK_QUIC_MODE_TO_DECRYPT: return BlockQuicMode.TO_DECRYPT;
default: return BlockQuicMode.NEVER;
}
}
public static PayloadMode getPayloadMode(String pref) {
switch (pref) {
case PAYLOAD_MODE_MINIMAL: return PayloadMode.MINIMAL;
@ -182,6 +201,7 @@ public class Prefs {
public static String getSocks5Password(SharedPreferences p) { return(p.getString(PREF_SOCKS5_PASSWORD_KEY, "")); }
public static String getAppFilter(SharedPreferences p) { return(p.getString(PREF_APP_FILTER, "")); }
public static IpMode getIPMode(SharedPreferences p) { return(getIPMode(p.getString(PREF_IP_MODE, IP_MODE_DEFAULT))); }
public static BlockQuicMode getBlockQuicMode(SharedPreferences p) { return(getBlockQuicMode(p.getString(PREF_BLOCK_QUIC, BLOCK_QUIC_MODE_DEFAULT))); }
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)); }
public static boolean isPcapdroidTrailerEnabled(SharedPreferences p) { return(p.getBoolean(PREF_PCAPDROID_TRAILER, false)); }
@ -202,7 +222,6 @@ public class Prefs {
public static boolean startAtBoot(SharedPreferences p) { return(p.getBoolean(PREF_START_AT_BOOT, false)); }
public static boolean isTLSDecryptionSetupDone(SharedPreferences p) { return(p.getBoolean(PREF_TLS_DECRYPTION_SETUP_DONE, false)); }
public static boolean getFullPayloadMode(SharedPreferences p) { return(p.getBoolean(PREF_FULL_PAYLOAD, false)); }
public static boolean blockQuic(SharedPreferences p) { return(p.getBoolean(PREF_BLOCK_QUIC, false)); }
public static boolean isPrivateDnsBlockingEnabled(SharedPreferences p) { return(p.getBoolean(PREF_AUTO_BLOCK_PRIVATE_DNS, true)); }
public static boolean lockdownVpnNoticeShown(SharedPreferences p) { return(p.getBoolean(PREF_LOCKDOWN_VPN_NOTICE_SHOWN, false)); }
public static boolean trailerNoticeShown(SharedPreferences p) { return(p.getBoolean(PREF_PCAPDROID_TRAILER_NOTICE_SHOWN, false)); }
@ -224,7 +243,7 @@ public class Prefs {
"\nTLSDecryption: " + getTlsDecryptionEnabled(p) +
"\nTLSSetupOk: " + isTLSDecryptionSetupDone(p) +
"\nCAInstallSkipped: " + MitmAddon.isCAInstallationSkipped(ctx) +
"\nBlockQuic: " + blockQuic(p) +
"\nBlockQuic: " + getBlockQuicMode(p) +
"\nRootCapture: " + isRootCaptureEnabled(p) +
"\nSocks5: " + getSocks5Enabled(p) +
"\nBlockPrivateDns: " + isPrivateDnsBlockingEnabled(p) +

View File

@ -385,10 +385,14 @@ static bool should_proxify(pcapdroid_t *pd, const zdtun_5tuple_t *tuple, pd_conn
/* ******************************************************* */
void vpn_process_ndpi(pcapdroid_t *pd, const zdtun_5tuple_t *tuple, pd_conn_t *data) {
if(pd->vpn.block_quic && (data->l7proto == NDPI_PROTOCOL_QUIC) &&
pd->tls_decryption.enabled && matches_decryption_whitelist(pd, tuple, data)) {
data->blacklisted_internal = true;
data->to_block = true;
if(data->l7proto == NDPI_PROTOCOL_QUIC) {
block_quic_mode_t block_mode = pd->vpn.block_quic_mode;
if ((block_mode == BLOCK_QUIC_MODE_ALWAYS) ||
((block_mode == BLOCK_QUIC_MODE_TO_DECRYPT) && matches_decryption_whitelist(pd, tuple, data))) {
data->blacklisted_internal = true;
data->to_block = true;
}
}
if(block_private_dns && !data->to_block &&
@ -449,7 +453,7 @@ int run_vpn(pcapdroid_t *pd) {
#if ANDROID
pd->vpn.resolver = init_uid_resolver(pd->sdk_ver, pd->env, pd->capture_service);
pd->vpn.known_dns_servers = blacklist_init();
pd->vpn.block_quic = getIntPref(pd->env, pd->capture_service, "blockQuick");
pd->vpn.block_quic_mode = getIntPref(pd->env, pd->capture_service, "getBlockQuickMode");
pd->vpn.ipv4.enabled = (bool) getIntPref(pd->env, pd->capture_service, "getIPv4Enabled");
pd->vpn.ipv4.dns_server = getIPv4Pref(pd->env, pd->capture_service, "getDnsServer");

View File

@ -65,6 +65,13 @@ typedef enum {
PAYLOAD_MODE_FULL
} payload_mode_t;
// NOTE: sync with Prefs.BlockQuicMode
typedef enum {
BLOCK_QUIC_MODE_NEVER = 0,
BLOCK_QUIC_MODE_ALWAYS,
BLOCK_QUIC_MODE_TO_DECRYPT
} block_quic_mode_t;
typedef struct {
jint incr_id; // an incremental number which identifies a specific connection
@ -205,7 +212,7 @@ typedef struct pcapdroid {
union {
struct {
int tunfd;
bool block_quic;
block_quic_mode_t block_quic_mode;
blacklist_t *known_dns_servers;
uid_resolver_t *resolver;

View File

@ -49,4 +49,15 @@
<item>@string/theme_light</item>
<item>@string/theme_dark</item>
</string-array>
<string-array name="block_quic">
<item>never</item>
<item>always</item>
<item>to_decrypt</item>
</string-array>
<string-array name="block_quic_labels">
<item>@string/never</item>
<item>@string/always</item>
<item>@string/for_connections_to_decrypt</item>
</string-array>
</resources>

View File

@ -497,4 +497,7 @@
<string name="redirected">redirected</string>
<string name="connection_redirected_port_map">This connection has been redirected due to a port mapping rule</string>
<string name="dnscrypt_how_to">How to use DoH / DNSCrypt with PCAPdroid</string>
<string name="never">Never</string>
<string name="always">Always</string>
<string name="for_connections_to_decrypt">Only for connections to decrypt</string>
</resources>

View File

@ -94,12 +94,14 @@
app:summary="@string/pcapng_format_summary"
app:defaultValue="true" />
<SwitchPreference
app:key="block_quic"
<DropDownPreference
app:key="block_quic_mode"
app:title="@string/block_quick"
android:entries="@array/block_quic_labels"
android:entryValues="@array/block_quic"
app:iconSpaceReserved="false"
app:summary="@string/block_quick_summary"
app:defaultValue="false" />
app:defaultValue="never"
app:useSimpleSummaryProvider="true" />
<SwitchPreference
app:key="full_payload"

View File

@ -94,7 +94,6 @@ As shown above, the capture settings can be specified by using intent extras. Th
| max_pkts_per_flow | int | 43 | | only dump the first max_pkts_per_flow packets per flow |
| max_dump_size | int | 43 | | max size in bytes for the PCAP dump |
| tls_decryption | bool | 49 | vpn | true to enable the built-in TLS decryption |
| block_quic | bool | 51 | vpn | true to block QUIC traffic (73+: matching the decryption whitelist)|
| auto_block_private_dns | bool | 51 | vpn | true to detect and possibly block private DNS to inspect traffic |
| ip_mode | string | 56 | vpn | which IP addresses to use for the VPN: ipv4 \| ipv6 \| both |
| mitmproxy_opts | string | 62 | | additional options to provide to mitmproxy in decryption mode |
@ -102,6 +101,7 @@ As shown above, the capture settings can be specified by using intent extras. Th
| pcapng_format | bool | 62 | | true to use the PCAPNG dump format (overrides pcapdroid_trailer)* |
| socks5_username | string | 64 | vpn | username for the optional SOCKS5 proxy authentication |
| socks5_password | string | 64 | vpn | password for the optional SOCKS5 proxy authentication |
| block_quic_mode | string | 73 | vpn | never | always | to_decrypt (matching the decryption whitelist) |
\*: paid feature