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 5c6d13b7..e08b8e5d 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java +++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java @@ -28,6 +28,7 @@ import android.net.ConnectivityManager; import android.net.VpnService; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.ParcelFileDescriptor; import android.util.Log; import android.widget.Toast; @@ -46,6 +47,7 @@ public class CaptureService extends VpnService implements Runnable { private static final String TAG = "CaptureService"; private static final String VpnSessionName = "PCAPdroid VPN"; private ParcelFileDescriptor mParcelFileDescriptor = null; + private Handler mHandler; private Thread mThread; private String vpn_ipv4; private String vpn_dns; @@ -103,6 +105,8 @@ public class CaptureService extends VpnService implements Runnable { public int onStartCommand(Intent intent, int flags, int startId) { Context app_ctx = getApplicationContext(); + mHandler = new Handler(); + if (intent == null) { Log.d(CaptureService.TAG, "NULL intent onStartCommand"); return super.onStartCommand(null, flags, startId); @@ -289,10 +293,12 @@ public class CaptureService extends VpnService implements Runnable { public void run() { if(mParcelFileDescriptor != null) { int fd = mParcelFileDescriptor.getFd(); + int fd_setsize = getFdSetSize(); - if(fd > 0) + if((fd > 0) && (fd < fd_setsize)) { + Log.d(TAG, "VPN fd: " + fd + " - FD_SETSIZE: " + fd_setsize); runPacketLoop(fd, this, Build.VERSION.SDK_INT); - else + } else Log.e(TAG, "Invalid VPN fd: " + fd); } } @@ -401,7 +407,14 @@ public class CaptureService extends VpnService implements Runnable { mHttpServer.pushData(data); } + public void reportError(String msg) { + mHandler.post(() -> { + Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); + }); + } + public static native void runPacketLoop(int fd, CaptureService vpn, int sdk); public static native void stopPacketLoop(); public static native void askStatsDump(); + public static native int getFdSetSize(); } diff --git a/app/src/main/java/com/emanuelef/remote_capture/Utils.java b/app/src/main/java/com/emanuelef/remote_capture/Utils.java index a0417ed6..62678974 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/Utils.java +++ b/app/src/main/java/com/emanuelef/remote_capture/Utils.java @@ -175,6 +175,9 @@ public class Utils { if(connInfo != null) { int ipAddress = connInfo.getIpAddress(); + if(ipAddress == 0) + return(null); + if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) { ipAddress = Integer.reverseBytes(ipAddress); } @@ -206,7 +209,7 @@ public class Utils { // try to get the WiFi IP address first String wifi_ip = getLocalWifiIpAddress(context); - if(wifi_ip != null) { + if((wifi_ip != null) && (!wifi_ip.equals("0.0.0.0"))) { Log.d("getLocalIPAddress", "Using WiFi IP: " + wifi_ip); return wifi_ip; } @@ -223,9 +226,8 @@ public class Utils { && addr.isSiteLocalAddress() /* Exclude public IPs */ && !addr.equals(vpn_ip)) { String sAddr = addr.getHostAddress(); - boolean isIPv4 = sAddr.indexOf(':') < 0; - if (isIPv4) { + if ((addr instanceof Inet4Address) && !sAddr.equals("0.0.0.0")) { Log.d("getLocalIPAddress", "Using interface '" + intf.getName() + "' IP: " + sAddr); return sAddr; } diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java index fbf1ed8d..6fea65d7 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java @@ -184,6 +184,10 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa if (status.equals(CaptureService.SERVICE_STATUS_STARTED) && (mState == AppState.starting)) { appStateRunning(); } else if (status.equals(CaptureService.SERVICE_STATUS_STOPPED)) { + // The service may still be active (on premature native termination) + if (CaptureService.isServiceActive()) + CaptureService.stopService(); + appStateReady(); } } @@ -191,6 +195,14 @@ public class MainActivity extends AppCompatActivity implements LoaderManager.Loa }, new IntentFilter(CaptureService.ACTION_SERVICE_STATUS)); } + @Override + public void onResume() { + super.onResume(); + + if(mMenu != null) + initAppState(); + } + private void notifyAppState() { for(AppStateListener listener: mStateListeners) listener.appStateChanged(mState); diff --git a/app/src/main/jni/vpnproxy-jni/jni_helpers.c b/app/src/main/jni/vpnproxy-jni/jni_helpers.c index 9c5a5f87..5d8820eb 100644 --- a/app/src/main/jni/vpnproxy-jni/jni_helpers.c +++ b/app/src/main/jni/vpnproxy-jni/jni_helpers.c @@ -27,6 +27,33 @@ #include static int loglevel = 0; +static JNIEnv *cur_env = NULL; +static jclass vpnclass = 0; +static jclass vpn_inst = 0; +static jmethodID reportError = NULL; + +jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature); +int jniCheckException(JNIEnv *env); +void DeleteLocalRef(JNIEnv *env, jobject *jresource); + +/* ******************************************************* */ + +void init_log(int lvl, JNIEnv *env, jclass _vpnclass, jclass _vpn_inst) { + loglevel = lvl; + cur_env = env; + vpnclass = _vpnclass; + vpn_inst = _vpn_inst; + reportError = jniGetMethodID(cur_env, vpnclass, "reportError", "(Ljava/lang/String;)V"); +} + +/* ******************************************************* */ + +void finish_log() { + cur_env = NULL; + vpnclass = 0; + vpn_inst = 0; + reportError = 0; +} /* ******************************************************* */ @@ -37,8 +64,22 @@ void log_android(int prio, const char *fmt, ...) { va_start(argptr, fmt); vsnprintf(line, sizeof(line), fmt, argptr); - __android_log_print(prio, "VPNProxy", "%s", line); va_end(argptr); + + __android_log_print(prio, "VPNProxy", "%s", line); + + if((prio >= ANDROID_LOG_FATAL) && (cur_env != NULL) && (reportError != NULL)) { + // This is a fatal error, report it to the gui + jobject info_string = (*cur_env)->NewStringUTF(cur_env, line); + + if((jniCheckException(cur_env) != 0) || (info_string == NULL)) + return; + + (*cur_env)->CallVoidMethod(cur_env, vpn_inst, reportError, info_string); + jniCheckException(cur_env); + + DeleteLocalRef(cur_env, info_string); + } } } diff --git a/app/src/main/jni/vpnproxy-jni/vpnproxy.c b/app/src/main/jni/vpnproxy-jni/vpnproxy.c index 29f677a2..a54685b6 100644 --- a/app/src/main/jni/vpnproxy-jni/vpnproxy.c +++ b/app/src/main/jni/vpnproxy-jni/vpnproxy.c @@ -110,6 +110,25 @@ static bool send_header; /* ******************************************************* */ +static char* tuple2str(const zdtun_5tuple_t *tuple, char *buf, size_t bufsize) { + char srcip[64], dstip[64]; + struct in_addr addr; + + addr.s_addr = tuple->src_ip; + strncpy(srcip, inet_ntoa(addr), sizeof(srcip)); + addr.s_addr = tuple->dst_ip; + strncpy(dstip, inet_ntoa(addr), sizeof(dstip)); + + snprintf(buf, bufsize, "[proto %d]: %s:%u -> %s:%u", + tuple->ipproto, + srcip, ntohs(tuple->src_port), + dstip, ntohs(tuple->dst_port)); + + return buf; +} + +/* ******************************************************* */ + void free_ndpi(conn_data_t *data) { if(data->ndpi_flow) { ndpi_free_flow(data->ndpi_flow); @@ -484,9 +503,8 @@ static int resolve_uid(vpnproxy_data_t *proxy, const zdtun_5tuple_t *conn_info) if(uid >= 0) { #if 1 - char appbuf[256]; - char srcip[64], dstip[64]; - struct in_addr addr; + char buf[256]; + char appbuf[128]; if(uid == 0) strncpy(appbuf, "ROOT", sizeof(appbuf)); @@ -495,16 +513,8 @@ static int resolve_uid(vpnproxy_data_t *proxy, const zdtun_5tuple_t *conn_info) else getApplicationByUid(proxy, uid, appbuf, sizeof(appbuf)); - addr.s_addr = conn_info->src_ip; - strncpy(srcip, inet_ntoa(addr), sizeof(srcip)); - addr.s_addr = conn_info->dst_ip; - strncpy(dstip, inet_ntoa(addr), sizeof(dstip)); - - log_android(ANDROID_LOG_INFO, "[proto=%d]: %s:%u -> %s:%u [%d/%s]", - conn_info->ipproto, - srcip, ntohs(conn_info->src_port), - dstip, ntohs(conn_info->dst_port), - uid, appbuf); + log_android(ANDROID_LOG_INFO, "%s [%d/%s]", + tuple2str(conn_info, buf, sizeof(buf)), uid, appbuf); #endif } else uid = -1; @@ -676,16 +686,24 @@ static int net2tap(zdtun_t *tun, char *pkt_buf, int pkt_size, const zdtun_conn_t int rv = write(proxy->tapfd, pkt_buf, pkt_size); if(rv < 0) { - if(errno == EIO) { + if(errno == ENOBUFS) { + char buf[256]; + + // Do not abort, the connection will be terminated + log_android(ANDROID_LOG_ERROR, "Got ENOBUFS %s", tuple2str(zdtun_conn_get_5tuple(conn_info), buf, sizeof(buf))); + } else if(errno == EIO) { log_android(ANDROID_LOG_INFO, "Got I/O error (terminating?)"); running = false; - } else - log_android(ANDROID_LOG_ERROR, - "tap write (%d) failed [%d]: %s", pkt_size, errno, strerror(errno)); - } else if(rv != pkt_size) - log_android(ANDROID_LOG_WARN, + } else { + log_android(ANDROID_LOG_FATAL, + "tap write (%d) failed [%d]: %s", pkt_size, errno, strerror(errno)); + running = false; + } + } else if(rv != pkt_size) { + log_android(ANDROID_LOG_FATAL, "partial tap write (%d / %d)", rv, pkt_size); - else + rv = -1; + } else rv = 0; return rv; @@ -859,7 +877,7 @@ static int connect_dumper(vpnproxy_data_t *proxy) { dumper_socket = socket(AF_INET, proxy->pcap_dump.tcp_socket ? SOCK_STREAM : SOCK_DGRAM, 0); if (!dumper_socket) { - log_android(ANDROID_LOG_ERROR, + log_android(ANDROID_LOG_FATAL, "could not open UDP pcap dump socket [%d]: %s", errno, strerror(errno)); return(-1); @@ -874,7 +892,7 @@ static int connect_dumper(vpnproxy_data_t *proxy) { servaddr.sin_addr.s_addr = proxy->pcap_dump.collector_addr; if(connect(dumper_socket, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) { - log_android(ANDROID_LOG_ERROR, + log_android(ANDROID_LOG_FATAL, "connection to the PCAP receiver failed [%d]: %s", errno, strerror(errno)); return(-2); @@ -896,6 +914,8 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) { time_t last_connections_dump = (time(NULL) * 1000) - CONNECTION_DUMP_UPDATE_FREQUENCY_MS + 1000 /* update in a second */; jclass vpn_class = (*env)->GetObjectClass(env, vpn); + init_log(ANDROID_LOG_DEBUG, env, vpn_class, vpn); + zdtun_parse_pkt(mitmproxy_pkt_buffer, sizeof(mitmproxy_pkt_buffer)-1, &mitm_pkt); /* Classes */ @@ -984,8 +1004,8 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) { // Limit the segments size for two reasons: // 1. to be able to encapsulate the packets for the UDP export - // 2. to avoid ENOBUFS while writing to the tapfd (for big packets). - zdtun_set_max_window_size(tun, 32768); + // 2. to avoid ENOBUFS while writing to the tapfd (for big packets, e.g. speedtest). + zdtun_set_max_window_size(tun, 8192); log_android(ANDROID_LOG_DEBUG, "Starting packet loop [tapfd=%d]", tapfd); @@ -1001,7 +1021,7 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) { proxy.java_dump.buffer_idx = 0; if(!proxy.java_dump.buffer) { - log_android(ANDROID_LOG_ERROR, "malloc(java_dump.buffer) failed with code %d/%s", + log_android(ANDROID_LOG_FATAL, "malloc(java_dump.buffer) failed with code %d/%s", errno, strerror(errno)); running = false; } @@ -1063,7 +1083,7 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) { check_tls_mitm(tun, &proxy, &pkt, conn); if ((rc = zdtun_forward(tun, &pkt, conn)) != 0) { - log_android(ANDROID_LOG_ERROR, "zdtun_forward failed with code %d", rc); + log_android(ANDROID_LOG_ERROR, "zdtun_forward (proto=%d) failed with code %d", pkt.tuple.ipproto, rc); proxy.num_dropped_connections++; zdtun_destroy_conn(tun, conn); @@ -1120,6 +1140,8 @@ housekeeping: } notifyServiceStatus(&proxy, "stopped"); + + finish_log(); return(0); } @@ -1145,4 +1167,9 @@ Java_com_emanuelef_remote_1capture_CaptureService_askStatsDump(JNIEnv *env, jcla dump_vpn_stats_now = true; dump_capture_stats_now = true; } -} \ No newline at end of file +} + +JNIEXPORT jint JNICALL +Java_com_emanuelef_remote_1capture_CaptureService_getFdSetSize(JNIEnv *env, jclass clazz) { + return FD_SETSIZE; +} diff --git a/app/src/main/res/values-cn/strings.xml b/app/src/main/res/values-cn/strings.xml index 03c10515..e4327c1e 100644 --- a/app/src/main/res/values-cn/strings.xml +++ b/app/src/main/res/values-cn/strings.xml @@ -79,5 +79,6 @@ 发送数据包 接收数据包 DNS 查询 + DNS 服务器 diff --git a/submodules/zdtun b/submodules/zdtun index b5fa5c3f..22034444 160000 --- a/submodules/zdtun +++ b/submodules/zdtun @@ -1 +1 @@ -Subproject commit b5fa5c3ffaae5746aa9c9434ac7198c818ecede4 +Subproject commit 220344447ec72773e90245324999af976c5568cd