Add ability to unblock connections from context menu

This commit is contained in:
emanuele-f 2022-05-19 22:50:49 +02:00
parent 3be18fb2e5
commit af2df846fd
12 changed files with 149 additions and 50 deletions

View File

@ -166,4 +166,8 @@ public class Billing {
return rv;
}
public boolean canUseFirewall() {
return isRedeemed(Billing.FIREWALL_SKU) && !CaptureService.isCapturingAsRoot();
}
}

View File

@ -1139,7 +1139,7 @@ public class CaptureService extends VpnService implements Runnable {
}
public void reloadBlocklist() {
if(!mBilling.isRedeemed(Billing.FIREWALL_SKU) || mSettings.root_capture)
if(!mBilling.canUseFirewall())
return;
Log.d(TAG, "reloading firewall blocklist");

View File

@ -116,7 +116,7 @@ public class EditFilterActivity extends BaseActivity {
if(!Prefs.isMalwareDetectionEnabled(this, prefs))
mOnlyBlacklisted.setVisibility(View.GONE);
if(!billing.isRedeemed(Billing.FIREWALL_SKU) || Prefs.isRootCaptureEnabled(prefs))
if(!billing.canUseFirewall())
mOnlyBlocked.setVisibility(View.GONE);
ConnectionsRegister reg = CaptureService.getConnsRegister();

View File

@ -211,7 +211,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
Menu navMenu = mNavView.getMenu();
navMenu.findItem(R.id.open_root_log).setVisible(Prefs.isRootCaptureEnabled(mPrefs));
navMenu.findItem(R.id.malware_detection).setVisible(Prefs.isMalwareDetectionEnabled(this, mPrefs));
navMenu.findItem(R.id.firewall).setVisible(mIab.isRedeemed(Billing.FIREWALL_SKU) && !Prefs.isRootCaptureEnabled(mPrefs));
navMenu.findItem(R.id.firewall).setVisible(mIab.canUseFirewall());
}
private void setupNavigationDrawer() {

View File

@ -20,7 +20,6 @@
package com.emanuelef.remote_capture.adapters;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
@ -30,10 +29,9 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.Billing;
import com.emanuelef.remote_capture.PCAPdroid;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils;
@ -52,10 +50,10 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
private final LayoutInflater mLayoutInflater;
private final Drawable mUnknownIcon;
private final MatchList mBlocklist;
private final boolean mFirewallAvailable;
private View.OnClickListener mListener;
private List<AppStats> mStats;
private final AppsResolver mApps;
private final SharedPreferences mPrefs;
private int mClickedPosition;
public class ViewHolder extends RecyclerView.ViewHolder {
@ -102,7 +100,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
mBlocklist = PCAPdroid.getInstance().getBlocklist();
mListener = null;
mStats = new ArrayList<>();
mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
mFirewallAvailable = Billing.newInstance(context).canUseFirewall();
setHasStableIds(true);
}
@ -121,7 +119,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
ViewHolder holder = new ViewHolder(view);
if(CaptureService.isFirewallEnabled(mContext, mPrefs)) {
if(mFirewallAvailable) {
// Enable the ability to show the context menu
view.setLongClickable(true);
@ -130,8 +128,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter<AppsStatsAdapter.View
mClickedPosition = holder.getAbsoluteAdapterPosition();
return false;
});
} else
view.setLongClickable(false);
}
return holder;
}

View File

@ -151,9 +151,9 @@ public class AppsFragment extends Fragment implements ConnectionsListener {
if(stats == null)
return;
// TODO unblock
menu.findItem(R.id.block_app).setVisible(true);
menu.findItem(R.id.unblock_app).setVisible(false);
boolean isBlocked = PCAPdroid.getInstance().getBlocklist().matchesApp(stats.getUid());
menu.findItem(R.id.block_app).setVisible(!isBlocked);
menu.findItem(R.id.unblock_app).setVisible(isBlocked);
}
@Override
@ -165,6 +165,8 @@ public class AppsFragment extends Fragment implements ConnectionsListener {
if(id == R.id.block_app)
blocklist.addApp(app.getUid());
else if(id == R.id.unblock_app)
blocklist.removeApp(app.getUid());
else
return super.onContextItemSelected(item);

View File

@ -305,14 +305,19 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
AppDescriptor app = mApps.get(conn.uid, 0);
Context ctx = requireContext();
Billing billing = Billing.newInstance(ctx);
MenuItem item;
menu.findItem(R.id.block_menu).setVisible(billing.isRedeemed(Billing.FIREWALL_SKU) && !CaptureService.isCapturingAsRoot());
boolean firewallAvailable = Billing.newInstance(ctx).canUseFirewall();
boolean blockVisible = false;
boolean unblockVisible = false;
MatchList blocklist = PCAPdroid.getInstance().getBlocklist();
if(app != null) {
item = menu.findItem(R.id.hide_app);
boolean appBlocked = blocklist.matchesApp(app.getUid());
blockVisible = !appBlocked;
unblockVisible = appBlocked;
item = menu.findItem(R.id.hide_app);
String label = Utils.shorten(MatchList.getRuleLabel(ctx, RuleType.APP, app.getPackageName()), max_length);
item.setTitle(label);
item.setVisible(true);
@ -323,7 +328,11 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
item = menu.findItem(R.id.block_app);
item.setTitle(label);
item.setVisible(true);
item.setVisible(!appBlocked);
item = menu.findItem(R.id.unblock_app);
item.setTitle(label);
item.setVisible(appBlocked);
if(conn.isBlacklisted()) {
item = menu.findItem(R.id.whitelist_app);
@ -333,14 +342,22 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
}
if((conn.info != null) && (!conn.info.isEmpty())) {
item = menu.findItem(R.id.hide_host);
boolean hostBlocked = blocklist.matchesExactHost(conn.info);
String label = Utils.shorten(MatchList.getRuleLabel(ctx, RuleType.HOST, conn.info), max_length);
blockVisible |= !hostBlocked;
unblockVisible |= hostBlocked;
item = menu.findItem(R.id.hide_host);
item.setTitle(label);
item.setVisible(true);
item = menu.findItem(R.id.block_host);
item.setTitle(label);
item.setVisible(true);
item.setVisible(!hostBlocked);
item = menu.findItem(R.id.unblock_host);
item.setTitle(label);
item.setVisible(hostBlocked);
item = menu.findItem(R.id.search_host);
item.setTitle(label);
@ -351,10 +368,13 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
item.setVisible(true);
String dm_clean = Utils.cleanDomain(conn.info);
String rootDomain = Utils.getSecondLevelDomain(dm_clean);
String domain = Utils.getSecondLevelDomain(dm_clean);
if(!rootDomain.equals(dm_clean)) {
label = Utils.shorten(MatchList.getRuleLabel(ctx, RuleType.HOST, rootDomain), max_length);
if(!domain.equals(dm_clean)) {
boolean domainBlocked = blocklist.matchesExactHost(domain);
label = Utils.shorten(MatchList.getRuleLabel(ctx, RuleType.HOST, domain), max_length);
blockVisible |= !domainBlocked;
unblockVisible |= domainBlocked;
item = menu.findItem(R.id.hide_domain);
item.setTitle(label);
@ -362,7 +382,11 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
item = menu.findItem(R.id.block_domain);
item.setTitle(label);
item.setVisible(true);
item.setVisible(!domainBlocked);
item = menu.findItem(R.id.unblock_domain);
item.setTitle(label);
item.setVisible(domainBlocked);
}
if(conn.isBlacklistedHost()) {
@ -388,7 +412,18 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
menu.findItem(R.id.hide_ip).setTitle(label);
menu.findItem(R.id.copy_ip).setTitle(label);
menu.findItem(R.id.search_ip).setTitle(label);
menu.findItem(R.id.block_ip).setTitle(label);
boolean ipBlocked = blocklist.matchesIP(conn.dst_ip);
blockVisible |= !ipBlocked;
unblockVisible |= ipBlocked;
menu.findItem(R.id.block_ip)
.setTitle(label)
.setVisible(!ipBlocked);
menu.findItem(R.id.unblock_ip)
.setTitle(label)
.setVisible(ipBlocked);
if(conn.isBlacklistedIp())
menu.findItem(R.id.whitelist_ip).setTitle(label).setVisible(true);
@ -401,6 +436,9 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
menu.findItem(R.id.hide_proto).setTitle(label);
menu.findItem(R.id.search_proto).setTitle(label);
menu.findItem(R.id.block_menu).setVisible(firewallAvailable && blockVisible);
menu.findItem(R.id.unblock_menu).setVisible(firewallAvailable && unblockVisible);
if(!conn.isBlacklisted())
menu.findItem(R.id.whitelist_menu).setVisible(false);
}
@ -468,6 +506,18 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
} else if(id == R.id.block_domain) {
blocklist.addHost(Utils.getSecondLevelDomain(conn.info));
blocklist_changed = true;
} else if(id == R.id.unblock_app) {
blocklist.removeApp(conn.uid);
blocklist_changed = true;
} else if(id == R.id.unblock_ip) {
blocklist.removeIp(conn.dst_ip);
blocklist_changed = true;
} else if(id == R.id.unblock_host) {
blocklist.removeHost(conn.info);
blocklist_changed = true;
} else if(id == R.id.unblock_domain) {
blocklist.removeHost(Utils.getSecondLevelDomain(conn.info));
blocklist_changed = true;
} else if(id == R.id.open_app_details) {
Intent intent = new Intent(requireContext(), AppDetailsActivity.class);
intent.putExtra(AppDetailsActivity.APP_UID_EXTRA, conn.uid);

View File

@ -255,7 +255,8 @@ public class EditListFragment extends Fragment {
}
if(toRemove.size() > 0) {
mList.removeRules(toRemove);
for(MatchList.Rule rule: toRemove)
mList.removeRule(rule);
mList.save();
}
}

View File

@ -241,12 +241,11 @@ public class MatchList {
return true;
}
public void addApp(String pkg) { addRule(new Rule(RuleType.APP, pkg)); }
public void addIp(String ip) { addRule(new Rule(RuleType.IP, ip)); }
public void addHost(String info) { addRule(new Rule(RuleType.HOST, Utils.cleanDomain(info))); }
public void addProto(String proto) { addRule(new Rule(RuleType.PROTOCOL, proto)); }
public void addCountry(String country_code) { addRule(new Rule(RuleType.COUNTRY, country_code)); }
public void addApp(String pkg) { addRule(new Rule(RuleType.APP, pkg)); }
public void addApp(int uid) {
AppDescriptor app = mResolver.get(uid, 0);
if(app == null) {
@ -258,6 +257,21 @@ public class MatchList {
addApp(app.getPackageName());
}
public void removeIp(String ip) { removeRule(new Rule(RuleType.IP, ip)); }
public void removeHost(String info) { removeRule(new Rule(RuleType.HOST, Utils.cleanDomain(info))); }
public void removeProto(String proto) { removeRule(new Rule(RuleType.PROTOCOL, proto)); }
public void removeCountry(String country_code) { removeRule(new Rule(RuleType.COUNTRY, country_code)); }
public void removeApp(String pkg) { removeRule(new Rule(RuleType.APP, pkg)); }
public void removeApp(int uid) {
AppDescriptor app = mResolver.get(uid, 0);
if(app == null) {
Log.e(TAG, "could not resolve UID " + uid);
return;
}
removeApp(app.getPackageName());
}
static private String matchKey(RuleType tp, Object val) {
return tp + "@" + val;
}
@ -283,24 +297,6 @@ public class MatchList {
return true;
}
public void removeRules(List<Rule> rules) {
mRules.removeAll(rules);
for(Rule rule: rules) {
String val = rule.getValue().toString();
String key = matchKey(rule.getType(), val);
mMatches.remove(key);
if(rule.getType() == RuleType.APP) {
int uid = mResolver.getUid(val);
if(uid != Utils.UID_NO_FILTER)
mUids.remove(uid);
else
Log.w(TAG, "removeRules: no uid found for package " + val);
}
}
}
public int addRules(MatchList to_add) {
int num_added = 0;
@ -314,6 +310,21 @@ public class MatchList {
return num_added;
}
public void removeRule(Rule rule) {
String val = rule.getValue().toString();
String key = matchKey(rule.getType(), val);
mRules.remove(rule);
mMatches.remove(key);
if(rule.getType() == RuleType.APP) {
int uid = mResolver.getUid(val);
if(uid != Utils.UID_NO_FILTER)
mUids.remove(uid);
else
Log.w(TAG, "removeRule: no uid found for package " + val);
}
}
public boolean matchesApp(int uid) {
// match apps based on their uid (faster) rather than their package name
return mUids.contains(uid);
@ -327,12 +338,17 @@ public class MatchList {
return mMatches.containsKey(matchKey(RuleType.PROTOCOL, l7proto));
}
public boolean matchesExactHost(String host) {
host = Utils.cleanDomain(host);
return mMatches.containsKey(matchKey(RuleType.HOST, host));
}
public boolean matchesHost(String host) {
// Keep in sync with the native blacklist_match_domain
host = Utils.cleanDomain(host);
// exact domain match
if(mMatches.containsKey(matchKey(RuleType.HOST, host)))
if(matchesExactHost(host))
return true;
// 2nd-level domain match

View File

@ -115,8 +115,8 @@ public class Prefs {
&& p.getBoolean(PREF_MALWARE_DETECTION, true));
}
public static boolean isFirewallEnabled(Context ctx, SharedPreferences p) {
return(Billing.newInstance(ctx).isRedeemed(Billing.FIREWALL_SKU)
&& !isRootCaptureEnabled(p)
// NOTE: firewall can be disabled at runtime
return(Billing.newInstance(ctx).canUseFirewall()
&& p.getBoolean(PREF_FIREWALL, true));
}
public static boolean startAtBoot(SharedPreferences p) { return(p.getBoolean(PREF_START_AT_BOOT, false)); }

View File

@ -58,7 +58,8 @@
<item
android:id="@+id/block_host"
android:title=""
tools:title="@string/host_val" />
tools:title="@string/host_val"
android:visible="false" />
<item
android:id="@+id/block_domain"
@ -68,6 +69,33 @@
</menu>
</item>
<item android:id="@+id/unblock_menu" android:title="@string/unblock">
<menu>
<item
android:id="@+id/unblock_app"
android:title=""
tools:title="@string/app_val"
android:visible="false" />
<item
android:id="@+id/unblock_ip"
android:title=""
tools:title="@string/ip_address_val" />
<item
android:id="@+id/unblock_host"
android:title=""
tools:title="@string/host_val"
android:visible="false" />
<item
android:id="@+id/unblock_domain"
android:title=""
tools:title="@string/host_val"
android:visible="false" />
</menu>
</item>
<item android:title="@string/search">
<menu>
<item

View File

@ -235,6 +235,7 @@
<string name="private_dns_hinders_detection">Private DNS hinders detection</string>
<string name="decryption_no_filter_warn">Select a target app when decrypting TLS to avoid losing your connection to the Internet</string>
<string name="block">Block…</string>
<string name="unblock">Unblock…</string>
<string name="firewall">Firewall</string>
<string name="firewall_rules">Firewall rules</string>
<string name="blocked_pkts">Blocked</string>