diff --git a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java
index 31ab3d21..60d0faba 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java
@@ -142,7 +142,8 @@ public class CaptureService extends VpnService implements Runnable {
private boolean mStopping;
private Blacklists mBlacklists;
private Blocklist mBlocklist;
- private MatchList mWhitelist;
+ private MatchList mMalwareWhitelist;
+ private MatchList mFirewallWhitelist;
private SparseArray mIfIndexToName;
private boolean mSocks5Enabled;
private String mSocks5Address;
@@ -451,13 +452,14 @@ public class CaptureService extends VpnService implements Runnable {
}
}
- mWhitelist = PCAPdroid.getInstance().getMalwareWhitelist();
+ mMalwareWhitelist = PCAPdroid.getInstance().getMalwareWhitelist();
mBlacklists = PCAPdroid.getInstance().getBlacklists();
if(mMalwareDetectionEnabled && !mBlacklists.needsUpdate())
reloadBlacklists();
checkBlacklistsUpdates();
mBlocklist = PCAPdroid.getInstance().getBlocklist();
+ mFirewallWhitelist = PCAPdroid.getInstance().getFirewallWhitelist();
mConnUpdateThread = new Thread(this::connUpdateWork, "UpdateListener");
mConnUpdateThread.start();
@@ -1302,6 +1304,7 @@ public class CaptureService extends VpnService implements Runnable {
if(mMalwareDetectionEnabled)
reloadMalwareWhitelist();
reloadBlocklist();
+ reloadFirewallWhitelist();
}
}
@@ -1377,12 +1380,20 @@ public class CaptureService extends VpnService implements Runnable {
reloadBlocklist(mBlocklist.toListDescriptor());
}
+ public void reloadFirewallWhitelist() {
+ if(!mBilling.isFirewallVisible())
+ return;
+
+ Log.i(TAG, "reloading firewall whitelist");
+ reloadFirewallWhitelist(Prefs.isFirewallWhitelistMode(mPrefs) ? mFirewallWhitelist.toListDescriptor() : null);
+ }
+
public static void reloadMalwareWhitelist() {
if((INSTANCE == null) || !INSTANCE.mMalwareDetectionEnabled)
return;
Log.i(TAG, "reloading malware whitelist");
- reloadMalwareWhitelist(INSTANCE.mWhitelist.toListDescriptor());
+ reloadMalwareWhitelist(INSTANCE.mMalwareWhitelist.toListDescriptor());
}
public static void setFirewallEnabled(boolean enabled) {
@@ -1433,6 +1444,7 @@ public class CaptureService extends VpnService implements Runnable {
private static native void setDnsServer(String server);
private static native void reloadBlacklists();
private static native boolean reloadBlocklist(MatchList.ListDescriptor blocklist);
+ private static native boolean reloadFirewallWhitelist(MatchList.ListDescriptor whitelist);
private static native boolean reloadMalwareWhitelist(MatchList.ListDescriptor whitelist);
public static native void askStatsDump();
public static native byte[] getPcapHeader();
diff --git a/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java b/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java
index 38f608ad..dfda636e 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java
@@ -48,6 +48,7 @@ public class PCAPdroid extends Application {
private static final String TAG = "PCAPdroid";
private MatchList mVisMask;
private MatchList mMalwareWhitelist;
+ private MatchList mFirewallWhitelist;
private Blocklist mBlocklist;
private Blacklists mBlacklists;
private CtrlPermissions mCtrlPermissions;
@@ -125,6 +126,31 @@ public class PCAPdroid extends Application {
return mBlocklist;
}
+ // use some safe defaults to guarantee basic services
+ private void initFirewallWhitelist() {
+ mFirewallWhitelist.addApp(0 /* root */);
+ mFirewallWhitelist.addApp(1000 /* android */);
+ mFirewallWhitelist.addApp(getPackageName() /* PCAPdroid */);
+ mFirewallWhitelist.addApp("com.google.android.gms" /* Google Play Services */);
+ mFirewallWhitelist.addApp("com.google.android.gsf" /* Google Services Framework (push notifications) */);
+ mFirewallWhitelist.addApp("com.google.android.ims" /* Carrier Services */);
+ mFirewallWhitelist.addApp("com.sec.spp.push" /* Samsung Push Service */);
+ mFirewallWhitelist.save();
+ }
+
+ public MatchList getFirewallWhitelist() {
+ if(mFirewallWhitelist == null) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mFirewallWhitelist = new MatchList(mLocalizedContext, Prefs.PREF_FIREWALL_WHITELIST);
+
+ if(!Prefs.isFirewallWhitelistInitialized(prefs)) {
+ initFirewallWhitelist();
+ Prefs.setFirewallWhitelistInitialized(prefs);
+ }
+ }
+ return mFirewallWhitelist;
+ }
+
public CtrlPermissions getCtrlPermissions() {
if(mCtrlPermissions == null)
mCtrlPermissions = new CtrlPermissions(this);
diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/FirewallActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/FirewallActivity.java
index 5da556cb..76ea2cd5 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/activities/FirewallActivity.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/activities/FirewallActivity.java
@@ -19,11 +19,14 @@
package com.emanuelef.remote_capture.activities;
+import android.annotation.SuppressLint;
+import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
+import androidx.preference.PreferenceManager;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
@@ -32,15 +35,20 @@ import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.fragments.EditListFragment;
import com.emanuelef.remote_capture.fragments.FirewallStatus;
import com.emanuelef.remote_capture.model.ListInfo;
+import com.emanuelef.remote_capture.model.Prefs;
import com.google.android.material.tabs.TabLayoutMediator;
public class FirewallActivity extends BaseActivity {
private static final String TAG = "Firewall";
private ViewPager2 mPager;
+ private StateAdapter mPagerAdapter;
+ private boolean mHasWhitelist = false;
+ private SharedPreferences mPrefs;
private static final int POS_STATUS = 0;
private static final int POS_BLOCKLIST = 1;
- private static final int TOTAL_COUNT = 2;
+ private static final int POS_WHITELIST = 2;
+ private static final int TOTAL_COUNT = 3;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -48,11 +56,12 @@ public class FirewallActivity extends BaseActivity {
setTitle(R.string.firewall);
setContentView(R.layout.tabs_activity);
+ mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mPager = findViewById(R.id.pager);
setupTabs();
}
- private static class StateAdapter extends FragmentStateAdapter {
+ private class StateAdapter extends FragmentStateAdapter {
StateAdapter(final FragmentActivity fa) { super(fa); }
@NonNull
@@ -66,11 +75,13 @@ public class FirewallActivity extends BaseActivity {
return new FirewallStatus();
case POS_BLOCKLIST:
return EditListFragment.newInstance(ListInfo.Type.BLOCKLIST);
+ case POS_WHITELIST:
+ return EditListFragment.newInstance(ListInfo.Type.FIREWALL_WHITELIST);
}
}
@Override
- public int getItemCount() { return TOTAL_COUNT; }
+ public int getItemCount() { return mHasWhitelist ? TOTAL_COUNT : (TOTAL_COUNT - 1); }
public int getPageTitle(final int position) {
switch (position) {
@@ -79,18 +90,32 @@ public class FirewallActivity extends BaseActivity {
return R.string.status;
case POS_BLOCKLIST:
return R.string.blocklist;
+ case POS_WHITELIST:
+ return R.string.whitelist;
}
}
}
+ @SuppressLint("NotifyDataSetChanged")
+ public void recheckTabs() {
+ boolean whitelist_mode = Prefs.isFirewallWhitelistMode(mPrefs);
+ if(mHasWhitelist == whitelist_mode)
+ return;
+
+ mHasWhitelist = whitelist_mode;
+ mPagerAdapter.notifyDataSetChanged();
+ }
+
private void setupTabs() {
- final StateAdapter stateAdapter = new StateAdapter(this);
- mPager.setAdapter(stateAdapter);
+ mPagerAdapter = new StateAdapter(this);
+ mPager.setAdapter(mPagerAdapter);
new TabLayoutMediator(findViewById(R.id.tablayout), mPager, (tab, position) ->
- tab.setText(getString(stateAdapter.getPageTitle(position)))
+ tab.setText(getString(mPagerAdapter.getPageTitle(position)))
).attach();
+ recheckTabs();
+
// TODO fix DPAD navigation on Android TV, see MainActivity.onKeyDown
}
}
diff --git a/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java b/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java
index 9fee995e..777435a8 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java
@@ -39,6 +39,7 @@ import com.emanuelef.remote_capture.model.AppDescriptor;
import com.emanuelef.remote_capture.model.AppStats;
import com.emanuelef.remote_capture.AppsResolver;
import com.emanuelef.remote_capture.model.Blocklist;
+import com.emanuelef.remote_capture.model.MatchList;
import java.util.ArrayList;
import java.util.Collections;
@@ -50,6 +51,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter mStats;
@@ -59,6 +61,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter();
mFirewallAvailable = Billing.newInstance(context).isFirewallVisible();
diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java
index 1390cdba..2f2a19ef 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java
@@ -20,6 +20,7 @@
package com.emanuelef.remote_capture.fragments;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -34,6 +35,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceManager;
import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.ConnectionsRegister;
@@ -46,6 +48,8 @@ import com.emanuelef.remote_capture.interfaces.ConnectionsListener;
import com.emanuelef.remote_capture.model.AppStats;
import com.emanuelef.remote_capture.model.Blocklist;
import com.emanuelef.remote_capture.model.ConnectionDescriptor;
+import com.emanuelef.remote_capture.model.MatchList;
+import com.emanuelef.remote_capture.model.Prefs;
import com.emanuelef.remote_capture.views.EmptyRecyclerView;
public class AppsFragment extends Fragment implements ConnectionsListener {
@@ -126,9 +130,16 @@ public class AppsFragment extends Fragment implements ConnectionsListener {
if(stats == null)
return;
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(requireContext());
boolean isBlocked = PCAPdroid.getInstance().getBlocklist().matchesApp(stats.getUid());
menu.findItem(R.id.block_app).setVisible(!isBlocked);
+ if(Prefs.isFirewallWhitelistMode(prefs)) {
+ boolean isWhitelisted = PCAPdroid.getInstance().getFirewallWhitelist().matchesApp(stats.getUid());
+ menu.findItem(R.id.add_to_whitelist).setVisible(!isWhitelisted);
+ menu.findItem(R.id.remove_from_whitelist).setVisible(isWhitelisted);
+ }
+
menu.findItem(R.id.unblock_app_permanently).setVisible(isBlocked);
menu.findItem(R.id.unblock_app_10m).setVisible(isBlocked)
.setTitle(getString(R.string.unblock_for_n_minutes, 10));
@@ -142,6 +153,8 @@ public class AppsFragment extends Fragment implements ConnectionsListener {
public boolean onContextItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
Blocklist blocklist = PCAPdroid.getInstance().getBlocklist();
+ MatchList whitelist = PCAPdroid.getInstance().getFirewallWhitelist();
+ boolean whitelistChanged = false;
AppStats app = mAdapter.getSelectedItem();
if(id == R.id.block_app)
@@ -154,10 +167,21 @@ public class AppsFragment extends Fragment implements ConnectionsListener {
blocklist.unblockAppForMinutes(app.getUid(), 60);
else if(id == R.id.unblock_app_8h)
blocklist.unblockAppForMinutes(app.getUid(), 480);
- else
+ else if(id == R.id.add_to_whitelist) {
+ whitelist.addApp(app.getUid());
+ whitelistChanged = true;
+ } else if(id == R.id.remove_from_whitelist) {
+ whitelist.removeApp(app.getUid());
+ whitelistChanged = true;
+ } else
return super.onContextItemSelected(item);
- blocklist.saveAndReload();
+ if(whitelistChanged) {
+ whitelist.save();
+ if (CaptureService.isServiceActive())
+ CaptureService.requireInstance().reloadFirewallWhitelist();
+ } else
+ blocklist.saveAndReload();
// refresh the item
mAdapter.notifyItemChanged(app);
diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java
index 17398bb1..ced1d302 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java
@@ -24,6 +24,7 @@ import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -47,6 +48,7 @@ import androidx.appcompat.widget.SearchView;
import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
+import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.RecyclerView;
@@ -67,6 +69,7 @@ import com.emanuelef.remote_capture.adapters.ConnectionsAdapter;
import com.emanuelef.remote_capture.model.FilterDescriptor;
import com.emanuelef.remote_capture.model.MatchList;
import com.emanuelef.remote_capture.model.MatchList.RuleType;
+import com.emanuelef.remote_capture.model.Prefs;
import com.emanuelef.remote_capture.views.EmptyRecyclerView;
import com.emanuelef.remote_capture.interfaces.ConnectionsListener;
import com.emanuelef.remote_capture.activities.EditFilterActivity;
@@ -295,12 +298,15 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
MenuItem item;
Billing billing = Billing.newInstance(ctx);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
boolean firewallVisible = billing.isFirewallVisible();
+ boolean whitelistMode = Prefs.isFirewallWhitelistMode(prefs);
boolean showPurchaseFirewall = (!billing.isPurchased(Billing.FIREWALL_SKU) && billing.isAvailable(Billing.FIREWALL_SKU)) && !CaptureService.isCapturingAsRoot();
boolean blockVisible = false;
boolean unblockVisible = false;
Blocklist blocklist = PCAPdroid.getInstance().getBlocklist();
+ MatchList fwWhitelist = PCAPdroid.getInstance().getFirewallWhitelist();
if(app != null) {
boolean appBlocked = blocklist.matchesApp(app.getUid());
@@ -333,6 +339,12 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
item.setTitle(label);
item.setVisible(true);
}
+
+ if(firewallVisible && whitelistMode) {
+ boolean whitelisted = fwWhitelist.matchesApp(app.getUid());
+ menu.findItem(R.id.add_to_whitelist).setVisible(!whitelisted);
+ menu.findItem(R.id.remove_from_whitelist).setVisible(whitelisted);
+ }
}
if((conn.info != null) && (!conn.info.isEmpty())) {
@@ -442,11 +454,13 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
Context ctx = requireContext();
ConnectionDescriptor conn = mAdapter.getSelectedItem();
MatchList whitelist = PCAPdroid.getInstance().getMalwareWhitelist();
+ MatchList fwWhitelist = PCAPdroid.getInstance().getFirewallWhitelist();
Blocklist blocklist = PCAPdroid.getInstance().getBlocklist();
boolean firewallPurchased = Billing.newInstance(ctx).isPurchased(Billing.FIREWALL_SKU);
boolean mask_changed = false;
boolean whitelist_changed = false;
boolean blocklist_changed = false;
+ boolean firewall_wl_changed = false;
if(conn == null)
return super.onContextItemSelected(item);
@@ -531,6 +545,12 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
} else if(id == R.id.unblock_domain) {
blocklist.removeHost(Utils.getSecondLevelDomain(conn.info));
blocklist_changed = true;
+ } else if(id == R.id.add_to_whitelist) {
+ fwWhitelist.addApp(conn.uid);
+ firewall_wl_changed = true;
+ } else if(id == R.id.remove_from_whitelist) {
+ fwWhitelist.removeApp(conn.uid);
+ firewall_wl_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);
@@ -555,6 +575,10 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
} else if(whitelist_changed) {
whitelist.save();
CaptureService.reloadMalwareWhitelist();
+ } else if(firewall_wl_changed) {
+ fwWhitelist.save();
+ if(CaptureService.isServiceActive())
+ CaptureService.requireInstance().reloadFirewallWhitelist();
} else if(blocklist_changed)
blocklist.saveAndReload();
diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/EditListFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/EditListFragment.java
index 517b4c60..88b955eb 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/fragments/EditListFragment.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/EditListFragment.java
@@ -254,6 +254,9 @@ public class EditListFragment extends Fragment implements MatchList.ListChangeLi
else if(mListInfo.getType() == ListInfo.Type.BLOCKLIST) {
if(CaptureService.isServiceActive())
CaptureService.requireInstance().reloadBlocklist();
+ } else if(mListInfo.getType() == ListInfo.Type.FIREWALL_WHITELIST) {
+ if(CaptureService.isServiceActive())
+ CaptureService.requireInstance().reloadFirewallWhitelist();
}
}
diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/FirewallStatus.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/FirewallStatus.java
index ebbc977c..55f4e5a9 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/fragments/FirewallStatus.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/FirewallStatus.java
@@ -37,6 +37,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.MenuProvider;
@@ -51,16 +52,20 @@ import com.emanuelef.remote_capture.PCAPdroid;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils;
import com.emanuelef.remote_capture.activities.ConnectionsActivity;
+import com.emanuelef.remote_capture.activities.FirewallActivity;
import com.emanuelef.remote_capture.activities.MainActivity;
import com.emanuelef.remote_capture.model.Blocklist;
import com.emanuelef.remote_capture.model.ConnectionDescriptor;
import com.emanuelef.remote_capture.model.FilterDescriptor;
+import com.emanuelef.remote_capture.model.MatchList;
import com.emanuelef.remote_capture.model.Prefs;
public class FirewallStatus extends Fragment implements MenuProvider {
private static final String TAG = "FirewallStatus";
+ private static boolean whitelistWarningAck = false;
private Handler mHandler;
private SharedPreferences mPrefs;
+ private Menu mMenu;
private SwitchCompat mToggle;
private ImageView mStatusIcon;
private TextView mStatus;
@@ -69,6 +74,7 @@ public class FirewallStatus extends Fragment implements MenuProvider {
private TextView mNumRules;
private TextView mLastBlock;
private Blocklist mBlocklist;
+ private MatchList mWhitelist;
private int mOkColor, mWarnColor, mGrayColor;
@Override
@@ -84,6 +90,7 @@ public class FirewallStatus extends Fragment implements MenuProvider {
mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);
mBlocklist = PCAPdroid.getInstance().getBlocklist();
+ mWhitelist = PCAPdroid.getInstance().getFirewallWhitelist();
mStatus = view.findViewById(R.id.status);
mHandler = new Handler(Looper.getMainLooper());
mStatusIcon = view.findViewById(R.id.status_icon);
@@ -121,6 +128,7 @@ public class FirewallStatus extends Fragment implements MenuProvider {
@Override
public void onCreateMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.firewall_menu, menu);
+ mMenu = menu;
mToggle = (SwitchCompat) menu.findItem(R.id.toggle_btn).getActionView();
mToggle.setOnCheckedChangeListener((buttonView, isChecked) -> {
@@ -135,9 +143,9 @@ public class FirewallStatus extends Fragment implements MenuProvider {
updateStatus();
});
+ menu.findItem(R.id.whitelist_mode).setChecked(Prefs.isFirewallWhitelistMode(mPrefs));
menu.findItem(R.id.block_new_apps).setChecked(Prefs.blockNewApps(mPrefs));
-
- updateStatus();
+ reloadMode();
}
@Override
@@ -153,16 +161,52 @@ public class FirewallStatus extends Fragment implements MenuProvider {
item.setChecked(checked);
mPrefs.edit().putBoolean(Prefs.PREF_BLOCK_NEW_APPS, checked).apply();
return true;
+ } else if(id == R.id.whitelist_mode) {
+ boolean checked = !item.isChecked();
+ if(checked && !whitelistWarningAck) {
+ AlertDialog dialog = new AlertDialog.Builder(requireContext())
+ .setTitle(R.string.whitelist_mode)
+ .setMessage(R.string.firewall_whitelist_notice)
+ .setPositiveButton(R.string.ok, (d, whichButton) -> {
+ whitelistWarningAck = true;
+ item.setChecked(true);
+ mPrefs.edit().putBoolean(Prefs.PREF_FIREWALL_WHITELIST_MODE, true).apply();
+ reloadMode();
+ })
+ .setNegativeButton(R.string.cancel_action, (d, whichButton) -> {})
+ .show();
+
+ dialog.setCanceledOnTouchOutside(false);
+ } else {
+ item.setChecked(checked);
+ mPrefs.edit().putBoolean(Prefs.PREF_FIREWALL_WHITELIST_MODE, checked).apply();
+ reloadMode();
+ }
+ return true;
}
return false;
}
+ private void reloadMode() {
+ boolean whitelist_mode = Prefs.isFirewallWhitelistMode(mPrefs);
+
+ if(mMenu != null)
+ mMenu.findItem(R.id.block_new_apps).setVisible(!whitelist_mode);
+
+ if(CaptureService.isServiceActive())
+ CaptureService.requireInstance().reloadFirewallWhitelist();
+
+ ((FirewallActivity)requireActivity()).recheckTabs();
+ updateStatus();
+ }
+
private void updateStatus() {
Context ctx = requireContext();
ConnectionsRegister reg = CaptureService.getConnsRegister();
boolean is_running = CaptureService.isServiceActive();
boolean is_enabled = Prefs.isFirewallEnabled(ctx, mPrefs);
+ boolean whitelist_mode = Prefs.isFirewallWhitelistMode(mPrefs);
if(!is_running) {
mStatusIcon.setImageResource(R.drawable.ic_shield);
@@ -190,7 +234,7 @@ public class FirewallStatus extends Fragment implements MenuProvider {
mNumBlocked.setText(Utils.formatIntShort(((reg != null) ? reg.getNumBlockedConnections() : 0)));
mNumChecked.setText(Utils.formatIntShort(CaptureService.getNumCheckedFirewallConnections()));
mLastBlock.setText(Utils.formatEpochMin(ctx, ((reg != null) ? reg.getLastFirewallBlock() / 1000 : 0)));
- mNumRules.setText(Utils.formatIntShort(mBlocklist.getSize()));
+ mNumRules.setText(Utils.formatIntShort(mBlocklist.getSize() + (whitelist_mode ? mWhitelist.getSize() : 0)));
// Periodic update
mHandler.postDelayed(this::updateStatus, 1000);
diff --git a/app/src/main/java/com/emanuelef/remote_capture/model/ListInfo.java b/app/src/main/java/com/emanuelef/remote_capture/model/ListInfo.java
index cb6f7d60..fb09a999 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/model/ListInfo.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/model/ListInfo.java
@@ -33,6 +33,7 @@ public class ListInfo {
VISUALIZATION_MASK,
MALWARE_WHITELIST,
BLOCKLIST,
+ FIREWALL_WHITELIST,
}
public ListInfo(Type tp) {
@@ -51,6 +52,8 @@ public class ListInfo {
return PCAPdroid.getInstance().getMalwareWhitelist();
case BLOCKLIST:
return PCAPdroid.getInstance().getBlocklist();
+ case FIREWALL_WHITELIST:
+ return PCAPdroid.getInstance().getFirewallWhitelist();
}
assert false;
@@ -65,6 +68,8 @@ public class ListInfo {
return R.string.malware_whitelist_rules;
case BLOCKLIST:
return R.string.firewall_rules;
+ case FIREWALL_WHITELIST:
+ return R.string.whitelist;
}
assert false;
@@ -79,6 +84,8 @@ public class ListInfo {
return R.string.malware_whitelist_help;
case BLOCKLIST:
return 0;
+ case FIREWALL_WHITELIST:
+ return R.string.firewall_whitelist_help;
}
assert false;
diff --git a/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java b/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java
index 071bb543..10905b17 100644
--- a/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java
+++ b/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java
@@ -46,6 +46,9 @@ public class Prefs {
public static final String PAYLOAD_MODE_FULL = "full";
public static final String DEFAULT_PAYLOAD_MODE = PAYLOAD_MODE_MINIMAL;
+ // used to initialize the whitelist with some safe defaults
+ public static final int FIREWALL_WHITELIST_INIT_VER = 1;
+
public static final String PREF_COLLECTOR_IP_KEY = "collector_ip_address";
public static final String PREF_COLLECTOR_PORT_KEY = "collector_port";
public static final String PREF_SOCKS5_PROXY_IP_KEY = "socks5_proxy_ip_address";
@@ -66,6 +69,9 @@ public class Prefs {
public static final String PREF_MALWARE_WHITELIST = "malware_whitelist";
public static final String PREF_PCAPDROID_TRAILER = "pcapdroid_trailer";
public static final String PREF_BLOCKLIST = "bl";
+ public static final String PREF_FIREWALL_WHITELIST_MODE = "firewall_wl_mode";
+ public static final String PREF_FIREWALL_WHITELIST_INIT_VER = "firewall_wl_init";
+ public static final String PREF_FIREWALL_WHITELIST = "firewall_whitelist";
public static final String PREF_START_AT_BOOT = "start_at_boot";
public static final String PREF_SNAPLEN = "snaplen";
public static final String PREF_MAX_PKTS_PER_FLOW = "max_pkts_per_flow";
@@ -140,6 +146,10 @@ public class Prefs {
p.edit().putBoolean(PREF_LOCKDOWN_VPN_NOTICE_SHOWN, true).apply();
}
+ public static void setFirewallWhitelistInitialized(SharedPreferences p) {
+ p.edit().putInt(PREF_FIREWALL_WHITELIST_INIT_VER, FIREWALL_WHITELIST_INIT_VER).apply();
+ }
+
/* Prefs with defaults */
public static String getCollectorIp(SharedPreferences p) { return(p.getString(PREF_COLLECTOR_IP_KEY, "127.0.0.1")); }
public static int getCollectorPort(SharedPreferences p) { return(Integer.parseInt(p.getString(PREF_COLLECTOR_PORT_KEY, "1234"))); }
@@ -172,9 +182,10 @@ public class Prefs {
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 blockNewApps(SharedPreferences p) { return(p.getBoolean(PREF_BLOCK_NEW_APPS, false)); }
+ public static boolean isFirewallWhitelistMode(SharedPreferences p) { return(p.getBoolean(PREF_FIREWALL_WHITELIST_MODE, false)); }
+ public static boolean isFirewallWhitelistInitialized(SharedPreferences p) { return(p.getInt(PREF_FIREWALL_WHITELIST_INIT_VER, 0) == FIREWALL_WHITELIST_INIT_VER); }
public static String getMitmproxyOpts(SharedPreferences p) { return(p.getString(PREF_MITMPROXY_OPTS, "")); }
-
public static String asString(Context ctx) {
SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(ctx);
diff --git a/app/src/main/jni/core/jni_impl.c b/app/src/main/jni/core/jni_impl.c
index 8adb24e9..ae930f54 100644
--- a/app/src/main/jni/core/jni_impl.c
+++ b/app/src/main/jni/core/jni_impl.c
@@ -779,6 +779,53 @@ Java_com_emanuelef_remote_1capture_CaptureService_reloadBlocklist(JNIEnv *env, j
/* ******************************************************* */
+JNIEXPORT jboolean JNICALL
+Java_com_emanuelef_remote_1capture_CaptureService_reloadFirewallWhitelist(JNIEnv *env, jclass clazz,
+ jobject whitelist) {
+ pcapdroid_t *pd = global_pd;
+ if(!pd) {
+ log_e("NULL pd instance");
+ return false;
+ }
+
+ if(pd->root_capture) {
+ log_e("firewall in root mode not implemented");
+ return false;
+ }
+
+ if(pd->firewall.new_wl != NULL) {
+ log_e("previous whitelist not loaded yet");
+ return false;
+ }
+
+ if(whitelist == NULL) {
+ pd->firewall.wl_enabled = false;
+ log_d("firewall whitelist is disabled");
+ return true;
+ }
+
+ blacklist_t *wl = blacklist_init();
+ if(!wl) {
+ log_e("blacklist_init failed");
+ return false;
+ }
+
+ if(blacklist_load_list_descriptor(wl, env, whitelist) < 0) {
+ blacklist_destroy(wl);
+ return false;
+ }
+
+ blacklists_stats_t stats;
+ blacklist_get_stats(wl, &stats);
+ log_d("reloadFirewallWhitelist: %d apps, %d domains, %d IPs", stats.num_apps, stats.num_domains, stats.num_ips);
+
+ pd->firewall.new_wl = wl;
+ pd->firewall.wl_enabled = true;
+ return true;
+}
+
+/* ******************************************************* */
+
JNIEXPORT jboolean JNICALL
Java_com_emanuelef_remote_1capture_CaptureService_reloadMalwareWhitelist(JNIEnv *env, jclass clazz,
jobject whitelist) {
diff --git a/app/src/main/jni/core/pcapdroid.c b/app/src/main/jni/core/pcapdroid.c
index 228da8df..1666c148 100644
--- a/app/src/main/jni/core/pcapdroid.c
+++ b/app/src/main/jni/core/pcapdroid.c
@@ -374,8 +374,9 @@ pd_conn_t* pd_new_connection(pcapdroid_t *pd, const zdtun_5tuple_t *tuple, int u
conn->data->uid = data->uid;
- if(!conn->data->to_block && pd->firewall.enabled
- && blacklist_match_uid(pd->firewall.bl, conn->data->uid))
+ if(!conn->data->to_block && pd->firewall.enabled && (
+ blacklist_match_uid(pd->firewall.bl, conn->data->uid) ||
+ (pd->firewall.wl_enabled && pd->firewall.wl && !blacklist_match_uid(pd->firewall.wl, conn->data->uid))))
conn->data->netd_block_missed = true;
zdtun_5tuple2str(&conn->tuple, buf, sizeof(buf));
@@ -439,6 +440,12 @@ pd_conn_t* pd_new_connection(pcapdroid_t *pd, const zdtun_5tuple_t *tuple, int u
fw_num_checked_connections++;
}
+ if(pd->firewall.enabled && pd->firewall.wl_enabled && pd->firewall.wl && !data->to_block && data->uid != UID_NETD) {
+ // whitelist mode: block any app unless it's explicitly whitelisted.
+ // The blocklist still has priority to determine if a connection should be blocked.
+ data->to_block = !blacklist_match_uid(pd->firewall.wl, data->uid);
+ }
+
return(data);
}
@@ -887,6 +894,8 @@ static int check_blocked_conn_cb(pcapdroid_t *pd, const zdtun_5tuple_t *tuple, p
blacklist_match_ip(fw_bl, &dst_ip, tuple->ipver) ||
(data->info && data->info[0] && blacklist_match_domain(fw_bl, data->info));
}
+ if(pd->firewall.enabled && pd->firewall.wl_enabled && pd->firewall.wl && !data->to_block && data->uid != UID_NETD)
+ data->to_block = !blacklist_match_uid(pd->firewall.wl, data->uid);
if(old_block != data->to_block) {
data->update_type |= CONN_UPDATE_STATS;
@@ -1030,6 +1039,13 @@ void pd_housekeeping(pcapdroid_t *pd) {
pd->firewall.bl = pd->firewall.new_bl;
pd->firewall.new_bl = NULL;
iter_active_connections(pd, check_blocked_conn_cb);
+ } else if(pd->firewall.new_wl) {
+ // Load new whitelist
+ if(pd->firewall.wl)
+ blacklist_destroy(pd->firewall.wl);
+ pd->firewall.wl = pd->firewall.new_wl;
+ pd->firewall.new_wl = NULL;
+ iter_active_connections(pd, check_blocked_conn_cb);
}
}
@@ -1213,6 +1229,10 @@ int pd_run(pcapdroid_t *pd) {
blacklist_destroy(pd->firewall.bl);
if(pd->firewall.new_bl)
blacklist_destroy(pd->firewall.new_bl);
+ if(pd->firewall.wl)
+ blacklist_destroy(pd->firewall.wl);
+ if(pd->firewall.new_wl)
+ blacklist_destroy(pd->firewall.new_wl);
if(pd->malware_detection.enabled) {
if(pd->malware_detection.reload_in_progress) {
diff --git a/app/src/main/jni/core/pcapdroid.h b/app/src/main/jni/core/pcapdroid.h
index 789ab55c..e247893a 100644
--- a/app/src/main/jni/core/pcapdroid.h
+++ b/app/src/main/jni/core/pcapdroid.h
@@ -263,8 +263,11 @@ typedef struct pcapdroid {
struct {
bool enabled;
- blacklist_t *bl;
+ blacklist_t *bl; // blocklist
blacklist_t *new_bl;
+ bool wl_enabled;
+ blacklist_t *wl; // whitelist
+ blacklist_t *new_wl;
} firewall;
} pcapdroid_t;
diff --git a/app/src/main/res/layout/app_item.xml b/app/src/main/res/layout/app_item.xml
index 4588123d..98e71627 100644
--- a/app/src/main/res/layout/app_item.xml
+++ b/app/src/main/res/layout/app_item.xml
@@ -1,6 +1,7 @@
+
+
+
+
-
+
+
+
-
Provide additional options for mitmproxy
Error while starting the mitm addon. Check the log for details
+ Whitelist mode
+ In this mode all the connections will be blocked, unless manually whitelisted. You may miss push notifications without a proper whitelist
+ These rules specify which connections to allow. Blocklist rules have priority over whitelist rules
+ Add to whitelist
+ Remove from whitelist