Native added source to traffic logging, added switch to enable host name resolving

This commit is contained in:
M66B 2016-01-22 10:37:57 +01:00
parent 8cad00d2b1
commit 33845fd733
11 changed files with 364 additions and 157 deletions

View File

@ -24,9 +24,10 @@
-keepnames class eu.faircode.netguard.** { *; }
#JNI callback
-keep class eu.faircode.netguard.Packet { *; }
-keep class eu.faircode.netguard.SinkholeService {
void selectExit(boolean);
void logPacket(long, int, java.lang.String, int, int, java.lang.String, int, boolean);
void logPacket(eu.faircode.netguard.Packet);
}
#Support library

View File

@ -56,6 +56,7 @@ public class ActivityLog extends AppCompatActivity {
private LogAdapter adapter;
private DatabaseHelper dh;
private boolean live;
private boolean resolve;
private static final int REQUEST_PCAP = 1;
@ -80,10 +81,13 @@ public class ActivityLog extends AppCompatActivity {
getSupportActionBar().setTitle(R.string.menu_log);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
resolve = prefs.getBoolean("resolve", false);
lvLog = (ListView) findViewById(R.id.lvLog);
dh = new DatabaseHelper(this);
adapter = new LogAdapter(this, dh.getLog());
adapter = new LogAdapter(this, dh.getLog(), resolve);
lvLog.setAdapter(adapter);
lvLog.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@ -180,11 +184,13 @@ public class ActivityLog extends AppCompatActivity {
public boolean onPrepareOptionsMenu(Menu menu) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean log = prefs.getBoolean("log", false);
boolean resolve = prefs.getBoolean("resolve", false);
boolean filter = prefs.getBoolean("filter", false);
boolean pcap = prefs.getBoolean("pcap", false);
boolean export = (getPackageManager().resolveActivity(getIntentPCAPDocument(), 0) != null);
menu.findItem(R.id.menu_log_enabled).setChecked(log);
menu.findItem(R.id.menu_log_resolve).setChecked(resolve);
menu.findItem(R.id.menu_pcap_enabled).setChecked(pcap);
menu.findItem(R.id.menu_pcap_enabled).setEnabled(log || filter);
menu.findItem(R.id.menu_pcap_export).setEnabled(pcap && export);
@ -213,6 +219,14 @@ public class ActivityLog extends AppCompatActivity {
DatabaseHelper.removeLocationChangedListener(listener);
return true;
case R.id.menu_log_resolve:
item.setChecked(!item.isChecked());
resolve = item.isChecked();
prefs.edit().putBoolean("resolve", resolve).apply();
adapter = new LogAdapter(this, dh.getLog(), resolve);
lvLog.setAdapter(adapter);
return true;
case R.id.menu_log_clear:
dh.clear();
if (!live) {

View File

@ -15,7 +15,7 @@ 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 = 6;
private static final int DB_VERSION = 7;
private static List<LogChangedListener> logChangedListeners = new ArrayList<LogChangedListener>();
@ -37,16 +37,20 @@ public class DatabaseHelper extends SQLiteOpenHelper {
" ID INTEGER PRIMARY KEY AUTOINCREMENT" +
", time INTEGER NOT NULL" +
", version INTEGER NULL" +
", ip TEXT" +
", protocol INTEGER NULL" +
", port INTEGER NULL" +
", flags TEXT" +
", saddr TEXT" +
", sport INTEGER NULL" +
", daddr TEXT" +
", dport INTEGER NULL" +
", uid INTEGER NULL" +
", allowed INTEGER NULL" +
", connection INTEGER NULL" +
", interactive INTEGER NULL" +
", allowed INTEGER NULL" +
");");
db.execSQL("CREATE INDEX idx_log_time ON log(time)");
db.execSQL("CREATE INDEX idx_log_source ON log(saddr, sport)");
db.execSQL("CREATE INDEX idx_log_dest ON log(daddr, dport)");
}
@Override
@ -78,6 +82,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE log ADD COLUMN allowed INTEGER NULL");
oldVersion = 6;
}
if (oldVersion < 7) {
db.execSQL("DROP TABLE log");
createTableLog(db);
oldVersion = 7;
}
db.setVersion(DB_VERSION);
@ -91,45 +100,42 @@ public class DatabaseHelper extends SQLiteOpenHelper {
// Location
public DatabaseHelper insertLog(
long time,
int version,
String ip,
int protocol,
int port,
String flags,
int uid,
int connection,
boolean interactive,
boolean allowed) {
public DatabaseHelper insertLog(Packet packet, int connection, boolean interactive) {
synchronized (mContext.getApplicationContext()) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("time", time);
cv.put("version", version);
cv.put("ip", ip);
cv.put("time", packet.time);
cv.put("version", packet.version);
if (protocol < 0)
if (packet.protocol < 0)
cv.putNull("protocol");
else
cv.put("protocol", protocol);
cv.put("protocol", packet.protocol);
if (port < 0)
cv.putNull("port");
cv.put("flags", packet.flags);
cv.put("saddr", packet.saddr);
if (packet.sport < 0)
cv.putNull("sport");
else
cv.put("port", port);
cv.put("sport", packet.sport);
cv.put("flags", flags);
cv.put("daddr", packet.daddr);
if (packet.dport < 0)
cv.putNull("dport");
else
cv.put("dport", packet.dport);
if (uid < 0)
if (packet.uid < 0)
cv.putNull("uid");
else
cv.put("uid", uid);
cv.put("uid", packet.uid);
cv.put("allowed", packet.allowed ? 1 : 0);
cv.put("connection", connection);
cv.put("interactive", interactive ? 1 : 0);
cv.put("allowed", allowed ? 1 : 0);
if (db.insert("log", null, cv) == -1)
Log.e(TAG, "Insert log failed");

View File

@ -22,30 +22,36 @@ import java.util.HashMap;
import java.util.Map;
public class LogAdapter extends CursorAdapter {
private boolean resolve;
private int colTime;
private int colVersion;
private int colIP;
private int colProtocol;
private int colPort;
private int colFlags;
private int colSource;
private int colSPort;
private int colDest;
private int colDPort;
private int colUid;
private int colAllowed;
private int colConnection;
private int colInteractive;
private int colAllowed;
private Map<String, String> mapIPHost = new HashMap<String, String>();
public LogAdapter(Context context, Cursor cursor) {
public LogAdapter(Context context, Cursor cursor, boolean resolve) {
super(context, cursor, 0);
this.resolve = resolve;
colTime = cursor.getColumnIndex("time");
colVersion = cursor.getColumnIndex("version");
colIP = cursor.getColumnIndex("ip");
colProtocol = cursor.getColumnIndex("protocol");
colPort = cursor.getColumnIndex("port");
colFlags = cursor.getColumnIndex("flags");
colSource = cursor.getColumnIndex("saddr");
colSPort = cursor.getColumnIndex("sport");
colDest = cursor.getColumnIndex("daddr");
colDPort = cursor.getColumnIndex("dport");
colUid = cursor.getColumnIndex("uid");
colAllowed = cursor.getColumnIndex("allowed");
colConnection = cursor.getColumnIndex("connection");
colInteractive = cursor.getColumnIndex("interactive");
colAllowed = cursor.getColumnIndex("allowed");
}
@Override
@ -58,27 +64,29 @@ public class LogAdapter extends CursorAdapter {
// Get values
long time = cursor.getLong(colTime);
int version = (cursor.isNull(colVersion) ? -1 : cursor.getInt(colVersion));
String ip = cursor.getString(colIP);
int protocol = (cursor.isNull(colProtocol) ? -1 : cursor.getInt(colProtocol));
int port = (cursor.isNull(colPort) ? -1 : cursor.getInt(colPort));
String flags = cursor.getString(colFlags);
String source = cursor.getString(colSource);
int sport = (cursor.isNull(colSPort) ? -1 : cursor.getInt(colSPort));
final String dest = cursor.getString(colDest);
int dport = (cursor.isNull(colDPort) ? -1 : cursor.getInt(colDPort));
int uid = (cursor.isNull(colUid) ? -1 : cursor.getInt(colUid));
int allowed = (cursor.isNull(colAllowed) ? -1 : cursor.getInt(colAllowed));
int connection = (cursor.isNull(colConnection) ? -1 : cursor.getInt(colConnection));
int interactive = (cursor.isNull(colInteractive) ? -1 : cursor.getInt(colInteractive));
int allowed = (cursor.isNull(colAllowed) ? -1 : cursor.getInt(colAllowed));
final String whois = (ip.length() > 1 && ip.charAt(0) == '/' ? ip.substring(1) : ip);
// Get views
TextView tvTime = (TextView) view.findViewById(R.id.tvTime);
ImageView ivConnection = (ImageView) view.findViewById(R.id.ivConnection);
ImageView ivInteractive = (ImageView) view.findViewById(R.id.ivInteractive);
TextView tvProtocol = (TextView) view.findViewById(R.id.tvProtocol);
TextView tvPort = (TextView) view.findViewById(R.id.tvPort);
TextView tvFlags = (TextView) view.findViewById(R.id.tvFlags);
final TextView tvSource = (TextView) view.findViewById(R.id.tvSource);
TextView tvSPort = (TextView) view.findViewById(R.id.tvSPort);
final TextView tvDest = (TextView) view.findViewById(R.id.tvDest);
TextView tvDPort = (TextView) view.findViewById(R.id.tvDPort);
ImageView ivIcon = (ImageView) view.findViewById(R.id.ivIcon);
TextView tvUid = (TextView) view.findViewById(R.id.tvUid);
final TextView tvIP = (TextView) view.findViewById(R.id.tvIP);
ImageView ivConnection = (ImageView) view.findViewById(R.id.ivConnection);
ImageView ivInteractive = (ImageView) view.findViewById(R.id.ivInteractive);
// Set values
tvTime.setText(new SimpleDateFormat("HH:mm:ss").format(time));
@ -106,9 +114,11 @@ public class LogAdapter extends CursorAdapter {
else
tvProtocol.setText(protocol < 0 ? "" : Integer.toString(protocol));
tvPort.setText(port < 0 ? "" : Integer.toString(port));
tvFlags.setText(flags);
tvSPort.setText(sport < 0 ? "" : Integer.toString(sport));
tvDPort.setText(dport < 0 ? "" : Integer.toString(dport));
// Application icon
ApplicationInfo info = null;
PackageManager pm = context.getPackageManager();
@ -143,32 +153,38 @@ public class LogAdapter extends CursorAdapter {
// tvFlags.setText("+APFR");
// tvUid.setText("18888");
synchronized (mapIPHost) {
if (mapIPHost.containsKey(whois))
tvIP.setText(mapIPHost.get(whois));
else {
tvIP.setText(whois);
new AsyncTask<String, Object, String>() {
@Override
protected String doInBackground(String... args) {
try {
// This requires internet permission
return InetAddress.getByName(args[0]).getHostName();
} catch (UnknownHostException ignored) {
return whois;
}
}
// TODO resolve source when inbound
@Override
protected void onPostExecute(String host) {
synchronized (mapIPHost) {
if (!mapIPHost.containsKey(host))
mapIPHost.put(host, host);
tvSource.setText(source);
if (resolve)
synchronized (mapIPHost) {
if (mapIPHost.containsKey(dest))
tvDest.setText(mapIPHost.get(dest));
else {
tvDest.setText(dest);
new AsyncTask<String, Object, String>() {
@Override
protected String doInBackground(String... args) {
try {
// This requires internet permission
return InetAddress.getByName(args[0]).getHostName();
} catch (UnknownHostException ignored) {
return dest;
}
}
tvIP.setText(host);
}
}.execute(whois);
@Override
protected void onPostExecute(String host) {
synchronized (mapIPHost) {
if (!mapIPHost.containsKey(host))
mapIPHost.put(host, host);
}
tvDest.setText(host);
}
}.execute(dest);
}
}
}
else
tvDest.setText(dest);
}
}

View File

@ -0,0 +1,37 @@
package eu.faircode.netguard;
/*
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)
*/
public class Packet {
public long time;
public int version;
public int protocol;
public String flags;
public String saddr;
public int sport;
public String daddr;
public int dport;
public int uid;
public boolean allowed;
public Packet() {
}
}

View File

@ -63,6 +63,7 @@ import android.widget.RemoteViews;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
@ -783,26 +784,11 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
}
// Called from native code
private void logPacket(
long time,
int version,
String daddr,
int protocol,
int dport,
String flags,
int uid,
boolean allowed) {
private void logPacket(Packet packet) {
new DatabaseHelper(SinkholeService.this).insertLog(
time,
version,
daddr,
protocol,
dport,
flags,
uid,
packet,
(last_connected ? last_metered ? 2 : 1 : 0),
last_interactive,
allowed).close();
last_interactive).close();
}
private BroadcastReceiver interactiveStateReceiver = new BroadcastReceiver() {

View File

@ -46,7 +46,6 @@
// TODO non blocking send/write/close, handle EAGAIN/EWOULDBLOCK
// It is assumed that no packets will get lost and that packets arrive in order
// http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/index.html
// Global variables
@ -60,6 +59,36 @@ char *pcap_fn = NULL;
// JNI
jclass clsPacket;
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
log_android(ANDROID_LOG_INFO, "JNI load");
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
log_android(ANDROID_LOG_INFO, "JNI load GetEnv failed");
return -1;
}
const char *packet = "eu/faircode/netguard/Packet";
clsPacket = jniGlobalRef(env, jniFindClass(env, packet));
// TODO find methods
return JNI_VERSION_1_6;
}
void JNI_OnUnload(JavaVM *vm, void *reserved) {
log_android(ANDROID_LOG_INFO, "JNI unload");
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK)
log_android(ANDROID_LOG_INFO, "JNI load GetEnv failed");
else {
(*env)->DeleteGlobalRef(env, clsPacket);
}
}
JNIEXPORT void JNICALL
Java_eu_faircode_netguard_SinkholeService_jni_1init(JNIEnv *env) {
udp_session = NULL;
@ -852,7 +881,7 @@ void handle_ip(const struct arguments *args, const uint8_t *buffer, const uint16
// Log traffic
if (args->log) {
if (!args->filter || syn || log || protocol != IPPROTO_TCP)
log_packet(args, version, dest, protocol, dport, flags, uid, allowed);
log_packet(args, version, protocol, flags, source, sport, dest, dport, uid, allowed);
}
}
@ -1422,13 +1451,15 @@ int write_udp(const struct arguments *args, const struct udp_session *cur,
udp->check = 0;
udp->len = htons(sizeof(struct udphdr) + datalen);
char to[20];
inet_ntop(AF_INET, &(ip->daddr), to, sizeof(to));
char source[20];
char dest[20];
inet_ntop(AF_INET, &(ip->saddr), source, sizeof(source));
inet_ntop(AF_INET, &(ip->daddr), dest, sizeof(dest));
// Send packet
log_android(ANDROID_LOG_DEBUG,
"Sending UDP to tun %s/%u data %u",
to, ntohs(udp->dest), datalen);
"Sending UDP to tun from %s/%u to %s/%u data %u",
source, ntohs(udp->source), dest, ntohs(udp->dest), datalen);
int res = write(tun, buffer, len);
@ -1440,7 +1471,8 @@ int write_udp(const struct arguments *args, const struct udp_session *cur,
log_android(ANDROID_LOG_INFO, "tun UDP write %f", mselapsed);
#endif
log_packet(args, cur->version, to, ip->protocol, ntohs(udp->dest), "", cur->uid, 1);
log_packet(args, cur->version, ip->protocol, "",
source, ntohs(udp->source), dest, ntohs(udp->dest), cur->uid, 1);
// Write pcap record
if (pcap_fn != NULL)
@ -1707,6 +1739,59 @@ uint16_t calc_checksum(uint8_t *buffer, uint16_t length) {
return (uint16_t) (~sum);
}
// http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html
// http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/index.html
jobject jniGlobalRef(JNIEnv *env, jobject cls) {
jobject gcls = (*env)->NewGlobalRef(env, cls);
if (gcls == NULL)
log_android(ANDROID_LOG_ERROR, "Global ref failed (out of memory?)");
return gcls;
}
jclass jniFindClass(JNIEnv *env, const char *name) {
jclass cls = (*env)->FindClass(env, name);
if (cls == NULL)
log_android(ANDROID_LOG_ERROR, "Class %s not found", name);
else
jniCheckException(env);
return cls;
}
jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature) {
jmethodID method = (*env)->GetMethodID(env, cls, name, signature);
if (method == NULL)
log_android(ANDROID_LOG_ERROR, "Method %s%s", name, signature);
return method;
}
jfieldID jniGetFieldID(JNIEnv *env, jclass cls, const char *name, const char *type) {
jfieldID field = (*env)->GetFieldID(env, cls, name, type);
if (field == NULL)
log_android(ANDROID_LOG_ERROR, "Field %s type %s not found", name, type);
return field;
}
jobject jniNewObject(JNIEnv *env, jclass cls, jmethodID constructor, const char *name) {
jobject object = (*env)->NewObject(env, cls, constructor);
if (object == NULL)
log_android(ANDROID_LOG_ERROR, "Create object %s failed", name);
else
jniCheckException(env);
return object;
}
int jniCheckException(JNIEnv *env) {
jthrowable ex = (*env)->ExceptionOccurred(env);
if (ex) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
(*env)->DeleteLocalRef(env, ex);
return 1;
}
return 0;
}
void log_android(int prio, const char *fmt, ...) {
if (prio >= loglevel) {
char line[1024];
@ -1721,10 +1806,12 @@ void log_android(int prio, const char *fmt, ...) {
void log_packet(
const struct arguments *args,
jint version,
const char *dest,
jint protocol,
jint dport,
const char *flags,
const char *source,
jint sport,
const char *dest,
jint dport,
jint uid,
jboolean allowed) {
#ifdef PROFILE
@ -1735,37 +1822,43 @@ void log_packet(
JNIEnv *env = args->env;
jobject instance = args->instance;
jclass cls = (*env)->GetObjectClass(env, instance);
char *signature = "(JILjava/lang/String;IILjava/lang/String;IZ)V";
jmethodID mid = (*env)->GetMethodID(env, cls, "logPacket", signature);
if (mid == 0)
log_android(ANDROID_LOG_ERROR, "Method logPacket%s not found", signature);
else {
struct timeval tv;
gettimeofday(&tv, NULL);
jlong t = tv.tv_sec * 1000LL + tv.tv_usec / 1000;
jstring jdest = (*env)->NewStringUTF(env, dest);
jstring jflags = (*env)->NewStringUTF(env, flags);
(*env)->CallVoidMethod(env, instance, mid,
t,
version,
jdest,
protocol,
dport,
jflags,
uid,
allowed);
(*env)->DeleteLocalRef(env, jdest);
(*env)->DeleteLocalRef(env, jflags);
jthrowable ex = (*env)->ExceptionOccurred(env);
if (ex) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
(*env)->DeleteLocalRef(env, ex);
}
}
(*env)->DeleteLocalRef(env, cls);
jclass clsService = (*env)->GetObjectClass(env, instance);
const char *signature = "(Leu/faircode/netguard/Packet;)V";
jmethodID logPacket = jniGetMethodID(env, clsService, "logPacket", signature);
const char *packet = "eu/faircode/netguard/Packet";
jmethodID initPacket = jniGetMethodID(env, clsPacket, "<init>", "()V");
jobject objPacket = jniNewObject(env, clsPacket, initPacket, packet);
struct timeval tv;
gettimeofday(&tv, NULL);
jlong t = tv.tv_sec * 1000LL + tv.tv_usec / 1000;
jstring jflags = (*env)->NewStringUTF(env, flags);
jstring jsource = (*env)->NewStringUTF(env, source);
jstring jdest = (*env)->NewStringUTF(env, dest);
const char *string = "Ljava/lang/String;";
(*env)->SetLongField(env, objPacket, jniGetFieldID(env, clsPacket, "time", "J"), t);
(*env)->SetIntField(env, objPacket, jniGetFieldID(env, clsPacket, "version", "I"), version);
(*env)->SetIntField(env, objPacket, jniGetFieldID(env, clsPacket, "protocol", "I"), protocol);
(*env)->SetObjectField(env, objPacket, jniGetFieldID(env, clsPacket, "flags", string), jflags);
(*env)->SetObjectField(env, objPacket, jniGetFieldID(env, clsPacket, "saddr", string), jsource);
(*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)->SetIntField(env, objPacket, jniGetFieldID(env, clsPacket, "uid", "I"), uid);
(*env)->SetBooleanField(env, objPacket, jniGetFieldID(env, clsPacket, "allowed", "Z"), allowed);
(*env)->CallVoidMethod(env, instance, logPacket, objPacket);
jniCheckException(env);
(*env)->DeleteLocalRef(env, jdest);
(*env)->DeleteLocalRef(env, jsource);
(*env)->DeleteLocalRef(env, jflags);
(*env)->DeleteLocalRef(env, objPacket);
(*env)->DeleteLocalRef(env, clsService);
#ifdef PROFILE
gettimeofday(&end, NULL);

View File

@ -144,14 +144,28 @@ jint get_uid(const int protocol, const int version,
uint16_t calc_checksum(uint8_t *buffer, uint16_t length);
jobject jniGlobalRef(JNIEnv *env, jobject cls);
jclass jniFindClass(JNIEnv *env, const char *name);
jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature);
jfieldID jniGetFieldID(JNIEnv *env, jclass cls, const char *name, const char *type);
jobject jniNewObject(JNIEnv *env, jclass cls, jmethodID constructor, const char *name);
int jniCheckException(JNIEnv *env);
void log_android(int prio, const char *fmt, ...);
void log_packet(const struct arguments *args,
jint version,
const char *dest,
jint protocol,
jint dport,
const char *flags,
const char *source,
jint sport,
const char *dest,
jint dport,
jint uid,
jboolean allowed);

View File

@ -26,38 +26,59 @@
android:layout_height="16dip"
android:layout_gravity="center_vertical" />
<TextView
android:id="@+id/tvProtocol"
android:layout_width="16dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="end"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
<TextView
android:id="@+id/tvPort"
android:layout_width="36dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="end"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
<TextView
android:id="@+id/tvFlags"
android:layout_width="40dp"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
android:orientation="vertical">
<TextView
android:id="@+id/tvProtocol"
android:layout_width="16dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
<TextView
android:id="@+id/tvFlags"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/tvSPort"
android:layout_width="36dp"
android:layout_height="wrap_content"
android:gravity="end"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
<TextView
android:id="@+id/tvDPort"
android:layout_width="36dp"
android:layout_height="wrap_content"
android:gravity="end"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
</LinearLayout>
<ImageView
android:id="@+id/ivIcon"
android:layout_width="16dip"
android:layout_height="16dip"
android:layout_gravity="center_vertical" />
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp" />
<TextView
android:id="@+id/tvUid"
@ -68,13 +89,26 @@
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
<TextView
android:id="@+id/tvIP"
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:layout_weight="1"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
android:orientation="vertical">
<TextView
android:id="@+id/tvSource"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
<TextView
android:id="@+id/tvDest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

View File

@ -9,6 +9,11 @@
android:checkable="true"
android:checked="true"
android:title="@string/menu_live" />
<item
android:id="@+id/menu_log_resolve"
android:checkable="true"
android:checked="false"
android:title="@string/menu_resolve" />
<item
android:id="@+id/menu_log_clear"
android:title="@string/menu_clear" />

View File

@ -26,6 +26,7 @@ These issues are caused by bugs in Android, or in the software provided by the m
<string name="menu_enabled">Enabled</string>
<string name="menu_live">Live updates</string>
<string name="menu_resolve">Resolve host names</string>
<string name="menu_pcap_enabled">PCAP enabled</string>
<string name="menu_pcap_export">PCAP export</string>
<string name="menu_clear">Clear</string>