Use reentrant read/write lock to protect database / optimize performance

Refs #243
This commit is contained in:
M66B 2016-02-10 13:24:15 +01:00
parent e738cddd80
commit 29d3c74a9a
5 changed files with 175 additions and 186 deletions

View File

@ -79,7 +79,7 @@ am start -a eu.faircode.netguard.START_PORT_FORWARD \
--ei dport 53 \
--es raddr 8.8.4.4 \
--ei rport 53 \
--ei ruid 1 \
--ei ruid 9999 \
--user 0
*/
Log.i(TAG, "Start forwarding protocol " + protocol + " port " + dport + " to " + raddr + "/" + rport + " uid " + ruid);

View File

@ -527,11 +527,11 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
if (vpn4 == null || TextUtils.isEmpty(vpn4.trim()))
throw new IllegalArgumentException("vpn4");
InetAddress.getByName(vpn4);
SinkholeService.reload(null, "changed " + name, this);
} catch (Throwable ex) {
Log.w(TAG, ex.toString());
prefs.edit().remove("vpn4").apply();
}
SinkholeService.reload(null, "changed " + name, this);
getPreferenceScreen().findPreference(name).setTitle(getString(R.string.setting_vpn4, prefs.getString("vpn4", "10.1.10.1")));
} else if ("vpn6".equals(name)) {
@ -540,11 +540,11 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
if (vpn6 == null || TextUtils.isEmpty(vpn6.trim()))
throw new IllegalArgumentException("vpn6");
InetAddress.getByName(vpn6);
SinkholeService.reload(null, "changed " + name, this);
} catch (Throwable ex) {
Log.w(TAG, ex.toString());
prefs.edit().remove("vpn6").apply();
}
SinkholeService.reload(null, "changed " + name, this);
getPreferenceScreen().findPreference(name).setTitle(getString(R.string.setting_vpn6, prefs.getString("vpn6", "fd00:1:fd00:1:fd00:1:fd00:1")));
} else if ("dns".equals(name)) {
@ -553,11 +553,11 @@ public class ActivitySettings extends AppCompatActivity implements SharedPrefere
if (dns == null || TextUtils.isEmpty(dns.trim()))
throw new IllegalArgumentException("dns");
InetAddress.getByName(dns);
SinkholeService.reload(null, "changed " + name, this);
} catch (Throwable ex) {
Log.w(TAG, ex.toString());
prefs.edit().remove("dns").apply();
}
SinkholeService.reload(null, "changed " + name, this);
getPreferenceScreen().findPreference(name).setTitle(getString(R.string.setting_dns, prefs.getString("dns", Util.getDefaultDNS(this))));
} else if ("show_stats".equals(name))

View File

@ -35,6 +35,7 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "NetGuard.Database";
@ -53,6 +54,10 @@ public class DatabaseHelper extends SQLiteOpenHelper {
private final static int MSG_LOG = 1;
private final static int MSG_ACCESS = 2;
private ReentrantReadWriteLock mLock = new ReentrantReadWriteLock(true);
// TODO precompiled statements
static {
hthread = new HandlerThread("DatabaseHelper");
hthread.start();
@ -299,8 +304,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
// Log
public DatabaseHelper insertLog(Packet packet, String dname, int connection, boolean interactive) {
synchronized (context.getApplicationContext()) {
public void insertLog(Packet packet, String dname, int connection, boolean interactive) {
mLock.writeLock().lock();
try {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues cv = new ContentValues();
@ -345,59 +351,74 @@ public class DatabaseHelper extends SQLiteOpenHelper {
if (db.insert("log", null, cv) == -1)
Log.e(TAG, "Insert log failed");
} finally {
mLock.writeLock().unlock();
}
notifyLogChanged();
return this;
}
public DatabaseHelper clearLog() {
synchronized (context.getApplicationContext()) {
public void clearLog() {
mLock.writeLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
db.delete("log", null, new String[]{});
db.execSQL("VACUUM");
} finally {
mLock.writeLock().unlock();
}
notifyLogChanged();
return this;
}
public Cursor getLog(boolean udp, boolean tcp, boolean other, boolean allowed, boolean blocked) {
// There is no index on protocol/allowed for write performance
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, *";
query += " FROM log";
query += " WHERE (0 = 1";
if (udp)
query += " OR protocol = 17";
if (tcp)
query += " OR protocol = 6";
if (other)
query += " OR (protocol <> 6 AND protocol <> 17)";
query += ") AND (0 = 1";
if (allowed)
query += " OR allowed = 1";
if (blocked)
query += " OR allowed = 0";
query += ")";
query += " ORDER BY time DESC";
return db.rawQuery(query, new String[]{});
mLock.readLock().lock();
try {
// There is no index on protocol/allowed for write performance
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, *";
query += " FROM log";
query += " WHERE (0 = 1";
if (udp)
query += " OR protocol = 17";
if (tcp)
query += " OR protocol = 6";
if (other)
query += " OR (protocol <> 6 AND protocol <> 17)";
query += ") AND (0 = 1";
if (allowed)
query += " OR allowed = 1";
if (blocked)
query += " OR allowed = 0";
query += ")";
query += " ORDER BY time DESC";
return db.rawQuery(query, new String[]{});
} finally {
mLock.readLock().unlock();
}
}
public Cursor searchLog(String find) {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, *";
query += " FROM log";
query += " WHERE daddr LIKE ? OR dname LIKE ? OR dport = ? OR uid LIKE ?";
query += " ORDER BY time DESC";
return db.rawQuery(query, new String[]{"%" + find + "%", "%" + find + "%", find, "%" + find + "%"});
mLock.readLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, *";
query += " FROM log";
query += " WHERE daddr LIKE ? OR dname LIKE ? OR dport = ? OR uid LIKE ?";
query += " ORDER BY time DESC";
return db.rawQuery(query, new String[]{"%" + find + "%", "%" + find + "%", find, "%" + find + "%"});
} finally {
mLock.readLock().unlock();
}
}
// Access
public boolean updateAccess(Packet packet, String dname, int block) {
int rows;
synchronized (context.getApplicationContext()) {
mLock.writeLock().lock();
try {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues cv = new ContentValues();
@ -427,14 +448,17 @@ public class DatabaseHelper extends SQLiteOpenHelper {
Log.e(TAG, "Insert access failed");
} else if (rows != 1)
Log.e(TAG, "Update access failed rows=" + rows);
} finally {
mLock.writeLock().unlock();
}
notifyAccessChanged();
return (rows == 0);
}
public DatabaseHelper setAccess(long id, int block) {
synchronized (context.getApplicationContext()) {
public void setAccess(long id, int block) {
mLock.writeLock().lock();
try {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues cv = new ContentValues();
@ -443,66 +467,92 @@ public class DatabaseHelper extends SQLiteOpenHelper {
if (db.update("access", cv, "ID = ?", new String[]{Long.toString(id)}) != 1)
Log.e(TAG, "Set access failed");
} finally {
mLock.writeLock().unlock();
}
notifyAccessChanged();
return this;
}
public DatabaseHelper clearAccess() {
synchronized (context.getApplicationContext()) {
public void clearAccess() {
mLock.writeLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
db.delete("access", null, null);
} finally {
mLock.writeLock().unlock();
}
notifyAccessChanged();
return this;
}
public DatabaseHelper clearAccess(int uid) {
synchronized (context.getApplicationContext()) {
public void clearAccess(int uid) {
mLock.writeLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
db.delete("access", "uid = ? AND block < 0", new String[]{Integer.toString(uid)});
} finally {
mLock.writeLock().unlock();
}
notifyAccessChanged();
return this;
}
public Cursor getAccess(int uid) {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, *";
query += " FROM access WHERE uid = ?";
query += " ORDER BY time DESC";
query += " LIMIT 50";
return db.rawQuery(query, new String[]{Integer.toString(uid)});
mLock.readLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, *";
query += " FROM access WHERE uid = ?";
query += " ORDER BY time DESC";
query += " LIMIT 50";
return db.rawQuery(query, new String[]{Integer.toString(uid)});
} finally {
mLock.readLock().unlock();
}
}
public Cursor getAccess() {
SQLiteDatabase db = this.getReadableDatabase();
return db.query("access", null, "block >= 0", null, null, null, "uid");
mLock.readLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
return db.query("access", null, "block >= 0", null, null, null, "uid");
} finally {
mLock.readLock().unlock();
}
}
public Cursor getAccessUnset(int uid) {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT MAX(time) AS time, daddr, allowed";
query += " FROM access";
query += " WHERE uid = ?";
query += " AND block < 0";
query += " GROUP BY daddr, allowed";
query += " ORDER BY time DESC";
return db.rawQuery(query, new String[]{Integer.toString(uid)});
mLock.readLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT MAX(time) AS time, daddr, allowed";
query += " FROM access";
query += " WHERE uid = ?";
query += " AND block < 0";
query += " GROUP BY daddr, allowed";
query += " ORDER BY time DESC";
return db.rawQuery(query, new String[]{Integer.toString(uid)});
} finally {
mLock.readLock().unlock();
}
}
public long getRuleCount(int uid) {
SQLiteDatabase db = this.getReadableDatabase();
return db.compileStatement("SELECT COUNT(*) FROM access WHERE block >=0 AND uid =" + uid).simpleQueryForLong();
mLock.readLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
return db.compileStatement("SELECT COUNT(*) FROM access WHERE block >=0 AND uid =" + uid).simpleQueryForLong();
} finally {
mLock.readLock().unlock();
}
}
// DNS
public DatabaseHelper insertDns(ResourceRecord rr) {
synchronized (context.getApplicationContext()) {
public void insertDns(ResourceRecord rr) {
mLock.writeLock().lock();
try {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues cv = new ContentValues();
@ -521,51 +571,62 @@ public class DatabaseHelper extends SQLiteOpenHelper {
Log.e(TAG, "Insert dns failed");
} else if (rows != 1)
Log.e(TAG, "Update dns failed rows=" + rows);
} finally {
mLock.writeLock().unlock();
}
return this;
}
public DatabaseHelper cleanupDns(long time) {
public void cleanupDns(long time) {
// There is no index on time for write performance
synchronized (context.getApplicationContext()) {
mLock.writeLock().lock();
try {
SQLiteDatabase db = this.getWritableDatabase();
int rows = db.delete("dns", "time < ?", new String[]{Long.toString(time)});
Log.i(TAG, "Cleanup DNS" +
" before=" + SimpleDateFormat.getDateTimeInstance().format(new Date(time)) +
" rows=" + rows);
} finally {
mLock.writeLock().unlock();
}
return this;
}
public String getQName(String ip) {
SQLiteDatabase db = this.getReadableDatabase();
mLock.readLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
return db.compileStatement(
"SELECT qname FROM dns WHERE resource = '" + ip.replace("'", "''") + "'")
.simpleQueryForString();
} catch (SQLiteDoneException ignored) {
// Not found
return null;
} finally {
mLock.readLock().unlock();
}
}
public Cursor getDns() {
SQLiteDatabase db = this.getReadableDatabase();
mLock.readLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT a.uid, a.version, a.protocol, a.daddr, d.resource, a.dport, a.block";
query += " FROM access AS a";
query += " JOIN dns AS d";
query += " ON d.qname = a.daddr";
query += " WHERE a.block >= 0";
String query = "SELECT a.uid, a.version, a.protocol, a.daddr, d.resource, a.dport, a.block";
query += " FROM access AS a";
query += " JOIN dns AS d";
query += " ON d.qname = a.daddr";
query += " WHERE a.block >= 0";
return db.rawQuery(query, new String[]{});
return db.rawQuery(query, new String[]{});
} finally {
mLock.readLock().unlock();
}
}
// Forward
public DatabaseHelper addForward(int protocol, int dport, String raddr, int rport, int ruid) {
synchronized (context.getApplicationContext()) {
public void addForward(int protocol, int dport, String raddr, int rport, int ruid) {
mLock.writeLock().lock();
try {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues cv = new ContentValues();
@ -577,25 +638,33 @@ public class DatabaseHelper extends SQLiteOpenHelper {
if (db.insert("forward", null, cv) < 0)
Log.e(TAG, "Insert forward failed");
} finally {
mLock.writeLock().unlock();
}
return this;
}
public DatabaseHelper deleteForward(int protocol, int dport) {
synchronized (context.getApplicationContext()) {
public void deleteForward(int protocol, int dport) {
mLock.writeLock().lock();
try {
SQLiteDatabase db = this.getWritableDatabase();
db.delete("forward", "protocol = ? AND dport = ?",
new String[]{Integer.toString(protocol), Integer.toString(dport)});
} finally {
mLock.writeLock().unlock();
}
return this;
}
public Cursor getForwarding() {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, *";
query += " FROM forward";
query += " ORDER BY dport";
return db.rawQuery(query, new String[]{});
mLock.readLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, *";
query += " FROM forward";
query += " ORDER BY dport";
return db.rawQuery(query, new String[]{});
} finally {
mLock.readLock().unlock();
}
}
public void addLogChangedListener(LogChangedListener listener) {

View File

@ -177,7 +177,7 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
private List<Float> gtx = new ArrayList<>();
private List<Float> grx = new ArrayList<>();
private HashMap<ApplicationInfo, Long> app = new HashMap<>();
private HashMap<Integer, Long> mapUidBytes = new HashMap<>();
public ServiceHandler(Looper looper) {
super(looper);
@ -458,7 +458,7 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
gt.clear();
gtx.clear();
grx.clear();
app.clear();
mapUidBytes.clear();
stats = true;
updateStats();
}
@ -518,39 +518,40 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
// Calculate application speeds
if (show_top) {
if (app.size() == 0) {
if (mapUidBytes.size() == 0) {
for (ApplicationInfo ainfo : getPackageManager().getInstalledApplications(0))
if (ainfo.uid != Process.myUid())
app.put(ainfo, TrafficStats.getUidTxBytes(ainfo.uid) + TrafficStats.getUidRxBytes(ainfo.uid));
mapUidBytes.put(ainfo.uid, TrafficStats.getUidTxBytes(ainfo.uid) + TrafficStats.getUidRxBytes(ainfo.uid));
} else if (t > 0) {
TreeMap<Float, ApplicationInfo> mapSpeed = new TreeMap<>(new Comparator<Float>() {
TreeMap<Float, Integer> mapSpeedUid = new TreeMap<>(new Comparator<Float>() {
@Override
public int compare(Float value, Float other) {
return -value.compareTo(other);
}
});
float dt = (ct - t) / 1000f;
for (ApplicationInfo aInfo : app.keySet()) {
long bytes = TrafficStats.getUidTxBytes(aInfo.uid) + TrafficStats.getUidRxBytes(aInfo.uid);
float speed = (bytes - app.get(aInfo)) / dt;
for (int uid : mapUidBytes.keySet()) {
long bytes = TrafficStats.getUidTxBytes(uid) + TrafficStats.getUidRxBytes(uid);
float speed = (bytes - mapUidBytes.get(uid)) / dt;
if (speed > 0) {
mapSpeed.put(speed, aInfo);
app.put(aInfo, bytes);
mapSpeedUid.put(speed, uid);
mapUidBytes.put(uid, bytes);
}
}
StringBuilder sb = new StringBuilder();
int i = 0;
for (float s : mapSpeed.keySet()) {
for (float speed : mapSpeedUid.keySet()) {
if (i++ >= 3)
break;
if (s < 1000 * 1000)
sb.append(getString(R.string.msg_kbsec, s / 1000));
if (speed < 1000 * 1000)
sb.append(getString(R.string.msg_kbsec, speed / 1000));
else
sb.append(getString(R.string.msg_mbsec, s / 1000 / 1000));
sb.append(getString(R.string.msg_mbsec, speed / 1000 / 1000));
sb.append(' ');
sb.append(getPackageManager().getApplicationLabel(mapSpeed.get(s)).toString());
List<String> apps = Util.getApplicationNames(mapSpeedUid.get(speed), SinkholeService.this);
sb.append(apps.size() > 0 ? apps.get(0) : "?");
sb.append("\r\n");
}
if (sb.length() > 0)
@ -698,7 +699,7 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
}
if (packet.uid < 0 && packet.dport != 53)
Log.w(TAG, "Unknown application packet=" + packet);
Log.w(TAG, "Unknown application packet " + packet);
}
private void resolved(ResourceRecord rr) {

View File

@ -1,81 +0,0 @@
/*
This file is part of NetGuard.
NetGuard is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NetGuard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
Copyright 2015-2016 by Marcel Bokhorst (M66B)
*/
#include "netguard.h"
int max_tun_msg = 0;
extern FILE *pcap_file;
int check_tun(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *efds) {
// Check tun error
if (FD_ISSET(args->tun, efds)) {
log_android(ANDROID_LOG_ERROR, "tun %d exception", args->tun);
if (fcntl(args->tun, F_GETFL) < 0) {
log_android(ANDROID_LOG_ERROR, "fcntl tun %d F_GETFL error %d: %s",
args->tun, errno, strerror(errno));
report_exit(args, "fcntl tun %d F_GETFL error %d: %s",
args->tun, errno, strerror(errno));
} else
report_exit(args, "tun %d exception", args->tun);
return -1;
}
// Check tun read
if (FD_ISSET(args->tun, rfds)) {
uint8_t *buffer = malloc(TUN_MAXMSG);
ssize_t length = read(args->tun, buffer, TUN_MAXMSG);
if (length < 0) {
free(buffer);
log_android(ANDROID_LOG_ERROR, "tun read error %d: %s", errno, strerror(errno));
if (errno == EINTR || errno == EAGAIN)
// Retry later
return 0;
else {
report_exit(args, "tun read error %d: %s", errno, strerror(errno));
return -1;
}
}
else if (length > 0) {
// Write pcap record
if (pcap_file != NULL)
write_pcap_rec(buffer, (size_t) length);
if (length > max_tun_msg) {
max_tun_msg = length;
log_android(ANDROID_LOG_WARN, "Maximum tun msg length %d", max_tun_msg);
}
// Handle IP from tun
handle_ip(args, buffer, (size_t) length);
free(buffer);
}
else {
// tun eof
free(buffer);
log_android(ANDROID_LOG_ERROR, "tun %d empty read", args->tun);
report_exit(args, "tun %d empty read", args->tun);
return -1;
}
}
return 0;
}