Ability to manually update the blacklists

The update status is now also shown
This commit is contained in:
emanuele-f 2021-11-19 15:17:46 +01:00
parent 0edba60206
commit ebfcea7217
11 changed files with 172 additions and 41 deletions

View File

@ -107,6 +107,7 @@ public class CaptureService extends VpnService implements Runnable {
private ConnectivityManager.NetworkCallback mNetworkCallback;
private AppsResolver appsResolver;
private boolean mMalwareDetectionEnabled;
private boolean mBlacklistsUpdateRequested;
private Blacklists mBlacklists;
/* The maximum connections to log into the ConnectionsRegister. Older connections are dropped.
@ -544,13 +545,14 @@ public class CaptureService extends VpnService implements Runnable {
if(!mMalwareDetectionEnabled || (mBlacklistsUpdateThread != null))
return;
if(mBlacklists.needsUpdate()) {
if(mBlacklistsUpdateRequested || mBlacklists.needsUpdate()) {
mBlacklistsUpdateThread = new Thread(this::updateBlacklistsWork, "Blacklists Update");
mBlacklistsUpdateThread.start();
}
}
private void updateBlacklistsWork() {
mBlacklistsUpdateRequested = false;
mBlacklists.update();
reloadBlacklists();
mBlacklistsUpdateThread = null;
@ -617,6 +619,15 @@ public class CaptureService extends VpnService implements Runnable {
(INSTANCE.isRootCapture() == 1));
}
public static void requestBlacklistsUpdate() {
if(INSTANCE != null) {
INSTANCE.mBlacklistsUpdateRequested = true;
// Wake the update thread to run the blacklist thread
INSTANCE.mPendingUpdates.push(new Pair<>(new ConnectionDescriptor[0], new ConnectionUpdate[0]));
}
}
// Inside the mCaptureThread
@Override
public void run() {
@ -652,6 +663,8 @@ public class CaptureService extends VpnService implements Runnable {
ConnectionDescriptor[] new_conns = item.first;
ConnectionUpdate[] conns_updates = item.second;
checkBlacklistsUpdates();
// synchronize the conn_reg to ensure that newConnections and connectionsUpdates run atomically
// thus preventing the ConnectionsAdapter from interleaving other operations
synchronized (conn_reg) {
@ -744,8 +757,6 @@ public class CaptureService extends VpnService implements Runnable {
}
public void updateConnections(ConnectionDescriptor[] new_conns, ConnectionUpdate[] conns_updates) {
checkBlacklistsUpdates();
// Put the update into a queue to avoid performing much work on the capture thread.
// This will be processed by mConnUpdateThread.
mPendingUpdates.push(new Pair<>(new_conns, conns_updates));

View File

@ -30,8 +30,8 @@ import androidx.viewpager2.widget.ViewPager2;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.fragments.EditListFragment;
import com.emanuelef.remote_capture.fragments.MalwareBlacklists;
import com.emanuelef.remote_capture.fragments.MalwareStatus;
import com.emanuelef.remote_capture.fragments.BlacklistsFragment;
import com.emanuelef.remote_capture.fragments.MalwareStatusFragment;
import com.emanuelef.remote_capture.model.ListInfo;
import com.google.android.material.tabs.TabLayoutMediator;
@ -65,9 +65,9 @@ public class MalwareDetection extends BaseActivity {
switch (position) {
default: // Deliberate fall-through to status tab
case POS_STATUS:
return new MalwareStatus();
return new MalwareStatusFragment();
case POS_BLACKLISTS:
return new MalwareBlacklists();
return new BlacklistsFragment();
case POS_WHITELIST:
return EditListFragment.newInstance(ListInfo.Type.MALWARE_WHITELIST);
}

View File

@ -19,8 +19,18 @@
package com.emanuelef.remote_capture.fragments;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
@ -28,20 +38,27 @@ import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.PCAPdroid;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.adapters.BlacklistsAdapter;
import com.emanuelef.remote_capture.interfaces.BlacklistsStateListener;
import com.emanuelef.remote_capture.model.Blacklists;
public class MalwareBlacklists extends Fragment implements BlacklistsStateListener {
BlacklistsAdapter mAdapter;
Blacklists mBlacklists;
public class BlacklistsFragment extends Fragment implements BlacklistsStateListener {
private static final String TAG = "BlacklistsFragment";
private BlacklistsAdapter mAdapter;
private Blacklists mBlacklists;
private MenuItem mUpdateItem;
private BroadcastReceiver mReceiver;
private Handler mHandler;
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
setHasOptionsMenu(true);
return inflater.inflate(R.layout.malware_detection_blacklists, container, false);
}
@ -51,23 +68,70 @@ public class MalwareBlacklists extends Fragment implements BlacklistsStateListen
mAdapter = new BlacklistsAdapter(view.getContext(), PCAPdroid.getInstance().getBlacklists().iter());
ListView listView = view.findViewById(R.id.listview);
listView.setAdapter(mAdapter);
mHandler = new Handler(Looper.getMainLooper());
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String status = intent.getStringExtra(CaptureService.SERVICE_STATUS_KEY);
if(status != null)
refreshStatus();
}
};
}
@Override
public void onResume() {
super.onResume();
mBlacklists.addOnChangeListener(this);
LocalBroadcastManager.getInstance(requireContext())
.registerReceiver(mReceiver, new IntentFilter(CaptureService.ACTION_SERVICE_STATUS));
}
@Override
public void onPause() {
super.onPause();
mBlacklists.removeOnChangeListener(this);
LocalBroadcastManager.getInstance(requireContext())
.unregisterReceiver(mReceiver);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.blacklists_menu, menu);
mUpdateItem = menu.findItem(R.id.update);
refreshStatus();
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
if(id == R.id.update) {
CaptureService.requestBlacklistsUpdate();
return true;
}
return false;
}
private void refreshStatus() {
if(mAdapter != null)
mAdapter.notifyDataSetChanged();
if(mUpdateItem != null) {
mUpdateItem.setVisible(CaptureService.isServiceActive());
mUpdateItem.setEnabled(!mBlacklists.isUpdateInProgress());
}
}
@Override
public void onBlacklistsStateChanged() {
if(mAdapter != null)
mAdapter.notifyDataSetChanged();
Log.d(TAG, "onBlacklistsStateChanged");
mHandler.post(this::refreshStatus);
}
}

View File

@ -192,7 +192,7 @@ public class EditListFragment extends Fragment {
return true;
}
return super.onOptionsItemSelected(item);
return false;
}
private void recheckListSize() {

View File

@ -45,7 +45,7 @@ import com.emanuelef.remote_capture.activities.ConnectionsActivity;
import com.emanuelef.remote_capture.model.Blacklists;
import com.emanuelef.remote_capture.model.FilterDescriptor;
public class MalwareStatus extends Fragment {
public class MalwareStatusFragment extends Fragment {
private static final String TAG = "MalwareStatus";
private Blacklists mBlacklists;
private Handler mHandler;

View File

@ -32,6 +32,7 @@ public class BlacklistDescriptor {
public final String url;
long mLastUpdate = 0;
boolean mUpToDate = false;
boolean mUpdating = false;
public boolean loaded = false;
public int num_rules = 0;
@ -44,6 +45,7 @@ public class BlacklistDescriptor {
public enum Status {
NOT_LOADED,
OUTDATED,
UPDATING,
UP_TO_DATE
}
@ -54,11 +56,18 @@ public class BlacklistDescriptor {
this.url = url;
}
public void setUpdating() {
mUpdating = true;
mUpToDate = false;
}
public void setOutdated() {
mUpdating = false;
mUpToDate = false;
}
public void setUpdated(long now) {
mUpdating = false;
mLastUpdate = now;
mUpToDate = (mLastUpdate != 0);
}
@ -72,6 +81,8 @@ public class BlacklistDescriptor {
}
public Status getStatus() {
if(mUpdating)
return Status.UPDATING;
if(!loaded)
return Status.NOT_LOADED;
if(!mUpToDate)
@ -89,6 +100,9 @@ public class BlacklistDescriptor {
case OUTDATED:
id = R.string.status_outdated;
break;
case UPDATING:
id = R.string.status_updating;
break;
case UP_TO_DATE:
id = R.string.status_uptodate;
break;
@ -107,6 +121,9 @@ public class BlacklistDescriptor {
case OUTDATED:
id = R.color.warning;
break;
case UPDATING:
id = R.color.in_progress;
break;
case UP_TO_DATE:
id = R.color.ok;
break;

View File

@ -64,17 +64,19 @@ public class Blacklists {
private final ArrayList<BlacklistsStateListener> mListeners = new ArrayList<>();
private final SharedPreferences mPrefs;
private final Context mContext;
long last_update;
boolean first_update;
int num_domain_rules;
int num_ip_rules;
private boolean mFirstUpdate;
private boolean mUpdateInProgress;
private long mLastUpdate;
private int mNumDomainRules;
private int mNumIPRules;
public Blacklists(Context ctx) {
last_update = 0;
num_domain_rules = 0;
num_ip_rules = 0;
mLastUpdate = 0;
mNumDomainRules = 0;
mNumIPRules = 0;
mContext = ctx;
first_update = true;
mFirstUpdate = true;
mUpdateInProgress = false;
mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);
// Domains
@ -111,9 +113,9 @@ public class Blacklists {
if(!serialized.isEmpty()) {
JsonObject obj = JsonParser.parseString(serialized).getAsJsonObject();
last_update = obj.getAsJsonPrimitive("last_update").getAsLong();
num_domain_rules = obj.getAsJsonPrimitive("num_domain_rules").getAsInt();
num_ip_rules = obj.getAsJsonPrimitive("num_ip_rules").getAsInt();
mLastUpdate = obj.getAsJsonPrimitive("last_update").getAsLong();
mNumDomainRules = obj.getAsJsonPrimitive("num_domain_rules").getAsInt();
mNumIPRules = obj.getAsJsonPrimitive("num_ip_rules").getAsInt();
JsonObject blacklists_obj = obj.getAsJsonObject("blacklists");
if(blacklists_obj != null) { // support old format
@ -144,9 +146,9 @@ public class Blacklists {
}
JsonObject rv = new JsonObject();
rv.add("last_update", new JsonPrimitive(src.last_update));
rv.add("num_domain_rules", new JsonPrimitive(src.num_domain_rules));
rv.add("num_ip_rules", new JsonPrimitive(src.num_ip_rules));
rv.add("last_update", new JsonPrimitive(src.mLastUpdate));
rv.add("num_domain_rules", new JsonPrimitive(src.mNumDomainRules));
rv.add("num_ip_rules", new JsonPrimitive(src.mNumIPRules));
rv.add("blacklists", blacklists_obj);
return rv;
@ -179,7 +181,7 @@ public class Blacklists {
if(!f.exists()) {
// must update
last_update = 0;
mLastUpdate = 0;
}
}
@ -199,17 +201,19 @@ public class Blacklists {
public boolean needsUpdate() {
long now = System.currentTimeMillis();
return((now - last_update) >= BLACKLISTS_UPDATE_SECONDS * 1000)
|| (first_update && (getNumUpdatedBlacklists() < getNumBlacklists()));
return((now - mLastUpdate) >= BLACKLISTS_UPDATE_SECONDS * 1000)
|| (mFirstUpdate && (getNumUpdatedBlacklists() < getNumBlacklists()));
}
// NOTE: invoked in a separate thread (CaptureService.mBlacklistsUpdateThread)
public void update() {
// NOTE: invoked in a separate thread (CaptureService.mBlacklistsUpdateThread)
if(!needsUpdate())
return;
mUpdateInProgress = true;
for(BlacklistDescriptor bl: mLists)
bl.setUpdating();
notifyListeners();
Log.d(TAG, "Updating " + mLists.size() + " blacklists...");
first_update = false;
mFirstUpdate = false;
for(BlacklistDescriptor bl: mLists) {
Log.d(TAG, "\tupdating " + bl.fname + "...");
@ -220,7 +224,7 @@ public class Blacklists {
bl.setOutdated();
}
last_update = System.currentTimeMillis();
mLastUpdate = System.currentTimeMillis();
notifyListeners();
}
@ -239,6 +243,7 @@ public class Blacklists {
int num_loaded = 0;
int num_domains = 0;
int num_ips = 0;
HashSet<String> loaded = new HashSet<>();
for(NativeBlacklistStatus bl_status: loaded_blacklists) {
if(bl_status == null)
@ -249,6 +254,7 @@ public class Blacklists {
// Update the number of rules
bl.num_rules = bl_status.num_rules;
bl.loaded = true;
loaded.add(bl.fname);
if(bl.type == BlacklistDescriptor.Type.DOMAIN_BLACKLIST)
num_domains += bl_status.num_rules;
@ -260,9 +266,17 @@ public class Blacklists {
Log.w(TAG, "Loaded unknown blacklist " + bl_status.fname);
}
for(BlacklistDescriptor bl: mLists) {
if(!loaded.contains(bl.fname)) {
Log.w(TAG, "Blacklist not loaded: " + bl.fname);
bl.loaded = false;
}
}
Log.d(TAG, "Blacklists loaded: " + num_loaded + " lists, " + num_domains + " domains, " + num_ips + " IPs");
num_domain_rules = num_domains;
num_ip_rules = num_ips;
mNumDomainRules = num_domains;
mNumIPRules = num_ips;
mUpdateInProgress = false;
notifyListeners();
}
@ -271,15 +285,15 @@ public class Blacklists {
}
public int getNumLoadedDomainRules() {
return num_domain_rules;
return mNumDomainRules;
}
public int getNumLoadedIPRules() {
return num_ip_rules;
return mNumIPRules;
}
public long getLastUpdate() {
return last_update;
return mLastUpdate;
}
public int getNumBlacklists() {
@ -309,5 +323,9 @@ public class Blacklists {
public void removeOnChangeListener(BlacklistsStateListener listener) {
mListeners.remove(listener);
}
public boolean isUpdateInProgress() {
return mUpdateInProgress;
}
}

View File

@ -0,0 +1,6 @@
<vector android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/update"
android:title="@string/update_now"
android:orderInCategory="10"
android:icon="@drawable/ic_refresh"
app:showAsAction="ifRoom" />
</menu>

View File

@ -17,5 +17,6 @@
<color name="ok">#0CB350</color>
<color name="warning">#FF5722</color>
<color name="danger">#F80013</color>
<color name="in_progress">#29b6f6</color>
<color name="highContrast">#000000</color>
</resources>

View File

@ -231,5 +231,7 @@
<string name="domain_rules">Domain rules</string>
<string name="ip_rules">IP rules</string>
<string name="malware_status_update_failed">Some blacklists are outdated</string>
<string name="update_now">Update now</string>
<string name="status_updating">Updating…</string>
</resources>