Show blocked/allowed IPs per application

This commit is contained in:
M66B 2016-01-27 11:59:16 +01:00
parent 4560925d6d
commit 2a6feef49f
26 changed files with 292 additions and 33 deletions

View File

@ -0,0 +1,77 @@
package eu.faircode.netguard;
import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CursorAdapter;
import android.widget.TextView;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
public class AccessAdapter extends CursorAdapter {
private static String TAG = "NetGuard.Access";
private int colDaddr;
private int colDPort;
private int colTime;
private int colAllowed;
public AccessAdapter(Context context, Cursor cursor) {
super(context, cursor, 0);
colDaddr = cursor.getColumnIndex("daddr");
colDPort = cursor.getColumnIndex("dport");
colTime = cursor.getColumnIndex("time");
colAllowed = cursor.getColumnIndex("allowed");
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.access, parent, false);
}
@Override
public void bindView(final View view, final Context context, final Cursor cursor) {
// Get values
final String dest = cursor.getString(colDaddr);
final int dport = (cursor.isNull(colDPort) ? -1 : cursor.getInt(colDPort));
long time = cursor.getLong(colTime);
int allowed = cursor.getInt(colAllowed);
// Get views
TextView tvTime = (TextView) view.findViewById(R.id.tvTime);
CheckBox cbIp = (CheckBox) view.findViewById(R.id.cbIp);
final TextView tvDest = (TextView) view.findViewById(R.id.tvDest);
// Set values
tvTime.setText(new SimpleDateFormat("HH:mm:ss").format(time));
cbIp.setChecked(allowed != 0);
new AsyncTask<String, Object, String>() {
@Override
protected void onPreExecute() {
tvDest.setText(dest + ":" + dport);
}
@Override
protected String doInBackground(String... args) {
try {
// This requires internet permission
return InetAddress.getByName(args[0]).getHostName();
} catch (UnknownHostException ignored) {
return args[0];
}
}
@Override
protected void onPostExecute(String host) {
tvDest.setText(host + ":" + dport);
}
}.execute(dest);
}
}

View File

@ -62,6 +62,7 @@ public class ActivityMain extends AppCompatActivity implements SharedPreferences
private boolean running = false;
private SwipeRefreshLayout swipeRefresh;
private DatabaseHelper dh;
private RuleAdapter adapter = null;
private MenuItem menuSearch = null;
private AlertDialog dialogFirst = null;
@ -186,7 +187,8 @@ public class ActivityMain extends AppCompatActivity implements SharedPreferences
RecyclerView rvApplication = (RecyclerView) findViewById(R.id.rvApplication);
rvApplication.setHasFixedSize(true);
rvApplication.setLayoutManager(new LinearLayoutManager(this));
adapter = new RuleAdapter(this);
dh = new DatabaseHelper(this);
adapter = new RuleAdapter(dh, this);
rvApplication.setAdapter(adapter);
// Swipe to refresh
@ -306,6 +308,8 @@ public class ActivityMain extends AppCompatActivity implements SharedPreferences
running = false;
dh.close();
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
LocalBroadcastManager.getInstance(this).unregisterReceiver(onRulesetChanged);

View File

@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -15,8 +16,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "NetGuard.Database";
private static final String DB_NAME = "Netguard";
private static final int DB_VERSION = 8;
private static final int DB_VERSION = 9;
private static boolean once = true;
private static List<LogChangedListener> logChangedListeners = new ArrayList<LogChangedListener>();
private Context mContext;
@ -24,15 +26,39 @@ public class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
mContext = context;
if (!once) {
once = true;
File dbfile = context.getDatabasePath(DB_NAME);
if (dbfile.exists()) {
Log.w(TAG, "Deleting " + dbfile);
dbfile.delete();
}
File dbjournal = context.getDatabasePath(DB_NAME + "-journal");
if (dbjournal.exists()) {
Log.w(TAG, "Deleting " + dbjournal);
dbjournal.delete();
}
}
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.i(TAG, "Creating database " + DB_NAME + ":" + DB_VERSION);
Log.i(TAG, "Creating database " + DB_NAME + " version " + DB_VERSION);
createTableLog(db);
createTableAccess(db);
}
@Override
public void onConfigure(SQLiteDatabase db) {
//db.enableWriteAheadLogging();
super.onConfigure(db);
}
private void createTableLog(SQLiteDatabase db) {
Log.i(TAG, "Creating log table");
db.execSQL("CREATE TABLE log (" +
" ID INTEGER PRIMARY KEY AUTOINCREMENT" +
", time INTEGER NOT NULL" +
@ -55,9 +81,22 @@ public class DatabaseHelper extends SQLiteOpenHelper {
db.execSQL("CREATE INDEX idx_log_uid ON log(uid)");
}
private void createTableAccess(SQLiteDatabase db) {
Log.i(TAG, "Creating access table");
db.execSQL("CREATE TABLE access (" +
" ID INTEGER PRIMARY KEY AUTOINCREMENT" +
", uid INTEGER NOT NULL" +
", daddr TEXT NOT NULL" +
", dport INTEGER NULL" +
", time INTEGER NOT NULL" +
", allowed INTEGER NOT NULL" +
");");
db.execSQL("CREATE UNIQUE INDEX idx_access ON access(uid, daddr, dport)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion);
Log.i(TAG, DB_NAME + " upgrading from version " + oldVersion + " to " + newVersion);
db.beginTransaction();
try {
@ -98,10 +137,18 @@ public class DatabaseHelper extends SQLiteOpenHelper {
db.execSQL("CREATE INDEX idx_log_uid ON log(uid)");
oldVersion = 8;
}
if (oldVersion < 9) {
createTableAccess(db);
oldVersion = 9;
}
db.setVersion(DB_VERSION);
if (oldVersion == DB_VERSION) {
db.setVersion(oldVersion);
db.setTransactionSuccessful();
Log.e(TAG, DB_NAME + " upgraded to " + DB_VERSION);
} else
throw new IllegalArgumentException(DB_NAME + " upgraded to " + oldVersion + " but required " + DB_VERSION);
db.setTransactionSuccessful();
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
} finally {
@ -109,7 +156,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
}
}
// Location
// Log
public DatabaseHelper insertLog(Packet packet, int connection, boolean interactive) {
synchronized (mContext.getApplicationContext()) {
@ -196,6 +243,38 @@ public class DatabaseHelper extends SQLiteOpenHelper {
return db.rawQuery(query, new String[]{"%" + find + "%", "%" + find + "%", "%" + find + "%"});
}
// Access
public DatabaseHelper updateAccess(Packet packet) {
synchronized (mContext.getApplicationContext()) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("time", packet.time);
cv.put("allowed", packet.allowed ? 1 : 0);
int rows = db.update("access", cv, "uid = ? AND daddr = ? AND dport = ?", new String[]{
Integer.toString(packet.uid), packet.daddr, Integer.toString(packet.dport)});
if (rows == 0) {
cv.put("uid", packet.uid);
cv.put("daddr", packet.daddr);
cv.put("dport", packet.dport);
if (db.insert("access", null, cv) == -1)
Log.e(TAG, "Insert access failed");
}
}
return this;
}
public Cursor getAccess(int uid) {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, * FROM access WHERE uid = ?";
query += " ORDER BY time DESC";
return db.rawQuery(query, new String[]{Integer.toString(uid)});
}
public static void addLogChangedListener(LogChangedListener listener) {
logChangedListeners.add(listener);
}

View File

@ -0,0 +1,24 @@
package eu.faircode.netguard;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;
public class ExpandedListView extends ListView {
public ExpandedListView(Context context) {
super(context);
}
public ExpandedListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExpandedListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 4, MeasureSpec.AT_MOST));
}
}

View File

@ -33,9 +33,9 @@ public class LogAdapter extends CursorAdapter {
private int colVersion;
private int colProtocol;
private int colFlags;
private int colSource;
private int colSAddr;
private int colSPort;
private int colDest;
private int colDaddr;
private int colDPort;
private int colUid;
private int colData;
@ -53,9 +53,9 @@ public class LogAdapter extends CursorAdapter {
colVersion = cursor.getColumnIndex("version");
colProtocol = cursor.getColumnIndex("protocol");
colFlags = cursor.getColumnIndex("flags");
colSource = cursor.getColumnIndex("saddr");
colSAddr = cursor.getColumnIndex("saddr");
colSPort = cursor.getColumnIndex("sport");
colDest = cursor.getColumnIndex("daddr");
colDaddr = cursor.getColumnIndex("daddr");
colDPort = cursor.getColumnIndex("dport");
colUid = cursor.getColumnIndex("uid");
colData = cursor.getColumnIndex("data");
@ -84,9 +84,9 @@ public class LogAdapter extends CursorAdapter {
int version = (cursor.isNull(colVersion) ? -1 : cursor.getInt(colVersion));
int protocol = (cursor.isNull(colProtocol) ? -1 : cursor.getInt(colProtocol));
String flags = cursor.getString(colFlags);
String source = cursor.getString(colSource);
String saddr = cursor.getString(colSAddr);
int sport = (cursor.isNull(colSPort) ? -1 : cursor.getInt(colSPort));
final String dest = cursor.getString(colDest);
String daddr = cursor.getString(colDaddr);
int dport = (cursor.isNull(colDPort) ? -1 : cursor.getInt(colDPort));
int uid = (cursor.isNull(colUid) ? -1 : cursor.getInt(colUid));
String data = cursor.getString(colData);
@ -98,9 +98,9 @@ public class LogAdapter extends CursorAdapter {
TextView tvTime = (TextView) view.findViewById(R.id.tvTime);
TextView tvProtocol = (TextView) view.findViewById(R.id.tvProtocol);
TextView tvFlags = (TextView) view.findViewById(R.id.tvFlags);
final TextView tvSource = (TextView) view.findViewById(R.id.tvSource);
final TextView tvSAddr = (TextView) view.findViewById(R.id.tvSAddr);
TextView tvSPort = (TextView) view.findViewById(R.id.tvSPort);
final TextView tvDest = (TextView) view.findViewById(R.id.tvDest);
final TextView tvDaddr = (TextView) view.findViewById(R.id.tvDAddr);
TextView tvDPort = (TextView) view.findViewById(R.id.tvDPort);
ImageView ivIcon = (ImageView) view.findViewById(R.id.ivIcon);
TextView tvUid = (TextView) view.findViewById(R.id.tvUid);
@ -172,14 +172,14 @@ public class LogAdapter extends CursorAdapter {
tvUid.setText(Integer.toString(uid));
// TODO resolve source when inbound
tvSource.setText(getKnownAddress(source));
tvSAddr.setText(getKnownAddress(saddr));
if (resolve && !isKnownAddress(dest))
if (resolve && !isKnownAddress(daddr))
synchronized (mapIPHost) {
if (mapIPHost.containsKey(dest))
tvDest.setText(mapIPHost.get(dest));
if (mapIPHost.containsKey(daddr))
tvDaddr.setText(mapIPHost.get(daddr));
else {
tvDest.setText(dest);
tvDaddr.setText(daddr);
new AsyncTask<String, Object, String>() {
@Override
protected String doInBackground(String... args) {
@ -187,7 +187,7 @@ public class LogAdapter extends CursorAdapter {
// This requires internet permission
return InetAddress.getByName(args[0]).getHostName();
} catch (UnknownHostException ignored) {
return dest;
return args[0];
}
}
@ -197,13 +197,13 @@ public class LogAdapter extends CursorAdapter {
if (!mapIPHost.containsKey(host))
mapIPHost.put(host, host);
}
tvDest.setText(host);
tvDaddr.setText(host);
}
}.execute(dest);
}.execute(daddr);
}
}
else
tvDest.setText(getKnownAddress(dest));
tvDaddr.setText(getKnownAddress(daddr));
if (TextUtils.isEmpty(data)) {
tvData.setText("");

View File

@ -28,11 +28,16 @@ public class Packet {
public int sport;
public String daddr;
public int dport;
public boolean outbound;
public String data;
public int uid;
public boolean allowed;
public Packet() {
}
@Override
public String toString() {
return "uid=" + uid + " daddr=" + daddr + ":" + dport;
}
}

View File

@ -48,6 +48,7 @@ import android.widget.Filterable;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
@ -59,6 +60,7 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> im
private static final String TAG = "NetGuard.Adapter";
private Activity context;
private DatabaseHelper dh;
private boolean wifi;
private boolean telephony;
private boolean debuggable;
@ -105,6 +107,7 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> im
public ImageButton btnSettings;
public Button btnLaunch;
public ListView lvAccess;
public TextView tvStatistics;
public ViewHolder(View itemView) {
@ -143,6 +146,7 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> im
btnSettings = (ImageButton) itemView.findViewById(R.id.btnSettings);
btnLaunch = (Button) itemView.findViewById(R.id.btnLaunch);
lvAccess = (ListView) itemView.findViewById(R.id.lvAccess);
tvStatistics = (TextView) itemView.findViewById(R.id.tvStatistics);
final View wifiParent = (View) cbWifi.getParent();
@ -173,8 +177,9 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> im
}
}
public RuleAdapter(Activity context) {
public RuleAdapter(DatabaseHelper dh, Activity context) {
this.context = context;
this.dh = dh;
this.wifi = Util.hasWifi(context);
this.telephony = Util.hasTelephony(context);
this.debuggable = Util.isDebuggable(context);
@ -442,6 +447,12 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> im
}
});
if (rule.expanded) {
AccessAdapter adapter = new AccessAdapter(context, dh.getAccess(rule.info.applicationInfo.uid));
holder.lvAccess.setAdapter(adapter);
} else
holder.lvAccess.setAdapter(null);
// Traffic statistics
holder.tvStatistics.setText(context.getString(R.string.msg_mbday, rule.upspeed, rule.downspeed));
}

View File

@ -629,10 +629,11 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
}
private void log(Packet packet) {
new DatabaseHelper(SinkholeService.this).insertLog(
packet,
(last_connected ? last_metered ? 2 : 1 : 0),
last_interactive).close();
DatabaseHelper dh = new DatabaseHelper(SinkholeService.this);
dh.insertLog(packet, (last_connected ? last_metered ? 2 : 1 : 0), last_interactive);
if (packet.uid > 0 && packet.outbound)
dh.updateAccess(packet);
dh.close();
}
private void set(Intent intent) {

View File

@ -1108,10 +1108,13 @@ void handle_ip(const struct arguments *args, const uint8_t *pkt, const size_t le
log = 1;
}
}
else
log = 1;
// Log traffic
if (args->log && (!args->filter || log))
log_packet(args, version, protocol, flags, source, sport, dest, dport, extra, uid, allowed);
log_packet(args, version, protocol, flags, source, sport, dest, dport, 1, extra, uid,
allowed);
}
jboolean handle_udp(const struct arguments *args,
@ -2101,7 +2104,7 @@ ssize_t write_udp(const struct arguments *args, const struct udp_session *cur,
if (res >= 0) {
if (args->log && ntohs(cur->dest) != 53)
log_packet(args, cur->version, IPPROTO_UDP, "",
dest, ntohs(udp->dest), source, ntohs(udp->source), "", cur->uid, 1);
dest, ntohs(udp->dest), source, ntohs(udp->source), 0, "", cur->uid, 1);
// Write pcap record
if (pcap_file != NULL)
@ -2519,6 +2522,7 @@ void log_packet(
jint sport,
const char *dest,
jint dport,
jboolean outbound,
const char *data,
jint uid,
jboolean allowed) {
@ -2555,6 +2559,8 @@ void log_packet(
(*env)->SetIntField(env, objPacket, jniGetFieldID(env, clsPacket, "sport", "I"), sport);
(*env)->SetObjectField(env, objPacket, jniGetFieldID(env, clsPacket, "daddr", string), jdest);
(*env)->SetIntField(env, objPacket, jniGetFieldID(env, clsPacket, "dport", "I"), dport);
(*env)->SetBooleanField(env, objPacket, jniGetFieldID(env, clsPacket, "outbound", "Z"),
outbound);
(*env)->SetObjectField(env, objPacket, jniGetFieldID(env, clsPacket, "data", string), jdata);
(*env)->SetIntField(env, objPacket, jniGetFieldID(env, clsPacket, "uid", "I"), uid);
(*env)->SetBooleanField(env, objPacket, jniGetFieldID(env, clsPacket, "allowed", "Z"), allowed);

View File

@ -309,6 +309,7 @@ void log_packet(const struct arguments *args,
jint sport,
const char *dest,
jint dport,
jboolean outbound,
const char *data,
jint uid,
jboolean allowed);

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ip_allowed" android:state_checked="true" />
<item android:drawable="@drawable/ip_blocked" android:state_checked="false" />
</selector>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_check_white_24dp"
android:tint="?attr/colorOn" />

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_close_white_24dp"
android:tint="?attr/colorOff" />

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".ActivityMain">
<TextView
android:id="@+id/tvTime"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
<CheckBox
android:id="@+id/cbIp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:button="@drawable/ip"
android:scaleX="0.8"
android:scaleY="0.8" />
<TextView
android:id="@+id/tvDest"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
</LinearLayout>

View File

@ -93,7 +93,7 @@
android:textSize="12sp" />
<TextView
android:id="@+id/tvSource"
android:id="@+id/tvSAddr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
@ -115,7 +115,7 @@
android:textSize="12sp" />
<TextView
android:id="@+id/tvDest"
android:id="@+id/tvDAddr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"

View File

@ -279,6 +279,12 @@
android:text="@string/title_launch" />
</LinearLayout>
<eu.faircode.netguard.ExpandedListView
android:id="@+id/lvAccess"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp" />
<TextView
android:id="@+id/tvStatistics"
android:layout_width="wrap_content"