JNIEXPORT void JNICALL Java_eu_faircode_netguard_ServiceSinkhole_jni_1init(JNIEnv *env, jobject instance) { loglevel = ANDROID_LOG_WARN; struct arguments args; args.env = env; args.instance = instance; init(&args); *socks5_addr = 0; socks5_port = 0; *socks5_username = 0; *socks5_password = 0; pcap_file = NULL; if (pthread_mutex_init(&lock, NULL)) log_android(ANDROID_LOG_ERROR, "pthread_mutex_init failed"); // Create signal pipe if (pipe(pipefds)) log_android(ANDROID_LOG_ERROR, "Create pipe error %d: %s", errno, strerror(errno)); else for (int i = 0; i < 2; i++) { int flags = fcntl(pipefds[i], F_GETFL, 0); if (flags < 0 || fcntl(pipefds[i], F_SETFL, flags | O_NONBLOCK) < 0) log_android(ANDROID_LOG_ERROR, "fcntl pipefds[%d] O_NONBLOCK error %d: %s", i, errno, strerror(errno)); } }
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); (*env)->DeleteGlobalRef(env, clsRR); } }
JNIEXPORT void JNICALL Java_eu_faircode_netguard_ServiceSinkhole_jni_1done(JNIEnv *env, jobject instance) { log_android(ANDROID_LOG_INFO, "Done"); clear(); if (pthread_mutex_destroy(&lock)) log_android(ANDROID_LOG_ERROR, "pthread_mutex_destroy failed"); for (int i = 0; i < 2; i++) if (close(pipefds[i])) log_android(ANDROID_LOG_ERROR, "Close pipe error %d: %s", errno, strerror(errno)); }
void log_packet(const struct arguments *args, jobject jpacket) { #ifdef PROFILE_JNI float mselapsed; struct timeval start, end; gettimeofday(&start, NULL); #endif jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance); const char *signature = "(Leu/faircode/netguard/Packet;)V"; if (midLogPacket == NULL) midLogPacket = jniGetMethodID(args->env, clsService, "logPacket", signature); (*args->env)->CallVoidMethod(args->env, args->instance, midLogPacket, jpacket); jniCheckException(args->env); (*args->env)->DeleteLocalRef(args->env, clsService); (*args->env)->DeleteLocalRef(args->env, jpacket); #ifdef PROFILE_JNI gettimeofday(&end, NULL); mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0; if (mselapsed > PROFILE_JNI) log_android(ANDROID_LOG_WARN, "log_packet %f", mselapsed); #endif }
jboolean is_domain_blocked(const struct arguments *args, const char *name) { #ifdef PROFILE_JNI float mselapsed; struct timeval start, end; gettimeofday(&start, NULL); #endif jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance); const char *signature = "(Ljava/lang/String;)Z"; if (midIsDomainBlocked == NULL) midIsDomainBlocked = jniGetMethodID(args->env, clsService, "isDomainBlocked", signature); jstring jname = (*args->env)->NewStringUTF(args->env, name); jboolean jallowed = (*args->env)->CallBooleanMethod( args->env, args->instance, midIsDomainBlocked, jname); jniCheckException(args->env); (*args->env)->DeleteLocalRef(args->env, jname); (*args->env)->DeleteLocalRef(args->env, clsService); #ifdef PROFILE_JNI gettimeofday(&end, NULL); mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0; if (mselapsed > PROFILE_JNI) log_android(ANDROID_LOG_WARN, "is_domain_blocked %f", mselapsed); #endif return jallowed; }
JNIEXPORT jintArray JNICALL Java_eu_faircode_netguard_ServiceSinkhole_jni_1get_1stats(JNIEnv *env, jobject instance) { if (pthread_mutex_lock(&lock)) log_android(ANDROID_LOG_ERROR, "pthread_mutex_lock failed"); jintArray jarray = (*env)->NewIntArray(env, 5); jint *jcount = (*env)->GetIntArrayElements(env, jarray, NULL); struct ng_session *s = ng_session; while (s != NULL) { if (s->protocol == IPPROTO_ICMP || s->protocol == IPPROTO_ICMPV6) { if (!s->icmp.stop) jcount[0]++; } else if (s->protocol == IPPROTO_UDP) { if (s->udp.state == UDP_ACTIVE) jcount[1]++; } else if (s->protocol == IPPROTO_TCP) { if (s->tcp.state != TCP_CLOSING && s->tcp.state != TCP_CLOSE) jcount[2]++; } s = s->next; } if (pthread_mutex_unlock(&lock)) log_android(ANDROID_LOG_ERROR, "pthread_mutex_unlock failed"); jcount[3] = 0; DIR *d = opendir("/proc/self/fd"); if (d) { struct dirent *dir; while ((dir = readdir(d)) != NULL) if (dir->d_type != DT_DIR) jcount[3]++; closedir(d); } struct rlimit rlim; memset(&rlim, 0, sizeof(struct rlimit)); getrlimit(RLIMIT_NOFILE, &rlim); jcount[4] = (jint) rlim.rlim_cur; (*env)->ReleaseIntArrayElements(env, jarray, jcount, NULL); return jarray; }
void account_usage(const struct arguments *args, jint version, jint protocol, const char *daddr, jint dport, jint uid, jlong sent, jlong received) { #ifdef PROFILE_JNI float mselapsed; struct timeval start, end; gettimeofday(&start, NULL); #endif jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance); const char *signature = "(Leu/faircode/netguard/Usage;)V"; if (midAccountUsage == NULL) midAccountUsage = jniGetMethodID(args->env, clsService, "accountUsage", signature); const char *usage = "eu/faircode/netguard/Usage"; if (midInitUsage == NULL) midInitUsage = jniGetMethodID(args->env, clsUsage, "<init>", "()V"); jobject jusage = jniNewObject(args->env, clsUsage, midInitUsage, usage); if (fidUsageTime == NULL) { const char *string = "Ljava/lang/String;"; fidUsageTime = jniGetFieldID(args->env, clsUsage, "Time", "J"); fidUsageVersion = jniGetFieldID(args->env, clsUsage, "Version", "I"); fidUsageProtocol = jniGetFieldID(args->env, clsUsage, "Protocol", "I"); fidUsageDAddr = jniGetFieldID(args->env, clsUsage, "DAddr", string); fidUsageDPort = jniGetFieldID(args->env, clsUsage, "DPort", "I"); fidUsageUid = jniGetFieldID(args->env, clsUsage, "Uid", "I"); fidUsageSent = jniGetFieldID(args->env, clsUsage, "Sent", "J"); fidUsageReceived = jniGetFieldID(args->env, clsUsage, "Received", "J"); } jlong jtime = time(NULL) * 1000LL; jstring jdaddr = (*args->env)->NewStringUTF(args->env, daddr); (*args->env)->SetLongField(args->env, jusage, fidUsageTime, jtime); (*args->env)->SetIntField(args->env, jusage, fidUsageVersion, version); (*args->env)->SetIntField(args->env, jusage, fidUsageProtocol, protocol); (*args->env)->SetObjectField(args->env, jusage, fidUsageDAddr, jdaddr); (*args->env)->SetIntField(args->env, jusage, fidUsageDPort, dport); (*args->env)->SetIntField(args->env, jusage, fidUsageUid, uid); (*args->env)->SetLongField(args->env, jusage, fidUsageSent, sent); (*args->env)->SetLongField(args->env, jusage, fidUsageReceived, received); (*args->env)->CallVoidMethod(args->env, args->instance, midAccountUsage, jusage); jniCheckException(args->env); (*args->env)->DeleteLocalRef(args->env, jdaddr); (*args->env)->DeleteLocalRef(args->env, jusage); (*args->env)->DeleteLocalRef(args->env, clsService); #ifdef PROFILE_JNI gettimeofday(&end, NULL); mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0; if (mselapsed > PROFILE_JNI) log_android(ANDROID_LOG_WARN, "log_packet %f", mselapsed); #endif }
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; }
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; }
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 not found", name, signature); jniCheckException(env); } return method; }
void dns_resolved(const struct arguments *args, const char *qname, const char *aname, const char *resource, int ttl) { #ifdef PROFILE_JNI float mselapsed; struct timeval start, end; gettimeofday(&start, NULL); #endif jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance); const char *signature = "(Leu/faircode/netguard/ResourceRecord;)V"; if (midDnsResolved == NULL) midDnsResolved = jniGetMethodID(args->env, clsService, "dnsResolved", signature); const char *rr = "eu/faircode/netguard/ResourceRecord"; if (midInitRR == NULL) midInitRR = jniGetMethodID(args->env, clsRR, "<init>", "()V"); jobject jrr = jniNewObject(args->env, clsRR, midInitRR, rr); if (fidQTime == NULL) { const char *string = "Ljava/lang/String;"; fidQTime = jniGetFieldID(args->env, clsRR, "Time", "J"); fidQName = jniGetFieldID(args->env, clsRR, "QName", string); fidAName = jniGetFieldID(args->env, clsRR, "AName", string); fidResource = jniGetFieldID(args->env, clsRR, "Resource", string); fidTTL = jniGetFieldID(args->env, clsRR, "TTL", "I"); } jlong jtime = time(NULL) * 1000LL; jstring jqname = (*args->env)->NewStringUTF(args->env, qname); jstring janame = (*args->env)->NewStringUTF(args->env, aname); jstring jresource = (*args->env)->NewStringUTF(args->env, resource); (*args->env)->SetLongField(args->env, jrr, fidQTime, jtime); (*args->env)->SetObjectField(args->env, jrr, fidQName, jqname); (*args->env)->SetObjectField(args->env, jrr, fidAName, janame); (*args->env)->SetObjectField(args->env, jrr, fidResource, jresource); (*args->env)->SetIntField(args->env, jrr, fidTTL, ttl); (*args->env)->CallVoidMethod(args->env, args->instance, midDnsResolved, jrr); jniCheckException(args->env); (*args->env)->DeleteLocalRef(args->env, jresource); (*args->env)->DeleteLocalRef(args->env, janame); (*args->env)->DeleteLocalRef(args->env, jqname); (*args->env)->DeleteLocalRef(args->env, jrr); (*args->env)->DeleteLocalRef(args->env, clsService); #ifdef PROFILE_JNI gettimeofday(&end, NULL); mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0; if (mselapsed > PROFILE_JNI) log_android(ANDROID_LOG_WARN, "log_packet %f", mselapsed); #endif }
struct allowed *is_address_allowed(const struct arguments *args, jobject jpacket) { #ifdef PROFILE_JNI float mselapsed; struct timeval start, end; gettimeofday(&start, NULL); #endif jclass clsService = (*args->env)->GetObjectClass(args->env, args->instance); const char *signature = "(Leu/faircode/netguard/Packet;)Leu/faircode/netguard/Allowed;"; if (midIsAddressAllowed == NULL) midIsAddressAllowed = jniGetMethodID(args->env, clsService, "isAddressAllowed", signature); jobject jallowed = (*args->env)->CallObjectMethod( args->env, args->instance, midIsAddressAllowed, jpacket); jniCheckException(args->env); if (jallowed != NULL) { if (fidRaddr == NULL) { const char *string = "Ljava/lang/String;"; fidRaddr = jniGetFieldID(args->env, clsAllowed, "raddr", string); fidRport = jniGetFieldID(args->env, clsAllowed, "rport", "I"); } jstring jraddr = (*args->env)->GetObjectField(args->env, jallowed, fidRaddr); if (jraddr == NULL) *allowed.raddr = 0; else { const char *raddr = (*args->env)->GetStringUTFChars(args->env, jraddr, NULL); strcpy(allowed.raddr, raddr); (*args->env)->ReleaseStringUTFChars(args->env, jraddr, raddr); } allowed.rport = (uint16_t) (*args->env)->GetIntField(args->env, jallowed, fidRport); (*args->env)->DeleteLocalRef(args->env, jraddr); } (*args->env)->DeleteLocalRef(args->env, jpacket); (*args->env)->DeleteLocalRef(args->env, clsService); (*args->env)->DeleteLocalRef(args->env, jallowed); #ifdef PROFILE_JNI gettimeofday(&end, NULL); mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0; if (mselapsed > PROFILE_JNI) log_android(ANDROID_LOG_WARN, "is_address_allowed %f", mselapsed); #endif return (jallowed == NULL ? NULL : &allowed); }
jint get_uid_retry(const int version, const int protocol, const void *saddr, const uint16_t sport) { char source[INET6_ADDRSTRLEN + 1]; inet_ntop(version == 4 ? AF_INET : AF_INET6, saddr, source, sizeof(source)); log_android(ANDROID_LOG_INFO, "get uid v%d p%d %s/%u", version, protocol, source, sport); jint uid = -1; int tries = 0; usleep(1000 * UID_DELAY); while (uid < 0 && tries++ < UID_MAXTRY) { // Check IPv6 table first if (version == 4) { int8_t saddr128[16]; memset(saddr128, 0, 10); saddr128[10] = (uint8_t) 0xFF; saddr128[11] = (uint8_t) 0xFF; memcpy(saddr128 + 12, saddr, 4); uid = get_uid(6, protocol, saddr128, sport, tries == UID_MAXTRY); } if (uid < 0) uid = get_uid(version, protocol, saddr, sport, tries == UID_MAXTRY); // Retry delay if (uid < 0 && tries < UID_MAXTRY) { log_android(ANDROID_LOG_WARN, "get uid v%d p%d %s/%u try %d", version, protocol, source, sport, tries); usleep(1000 * UID_DELAYTRY); } } if (uid < 0) log_android(ANDROID_LOG_ERROR, "uid v%d p%d %s/%u not found", version, protocol, source, sport); return uid; }
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)); const char *allowed = "eu/faircode/netguard/Allowed"; clsAllowed = jniGlobalRef(env, jniFindClass(env, allowed)); const char *rr = "eu/faircode/netguard/ResourceRecord"; clsRR = jniGlobalRef(env, jniFindClass(env, rr)); const char *usage = "eu/faircode/netguard/Usage"; clsUsage = jniGlobalRef(env, jniFindClass(env, usage)); // Raise file number limit to maximum struct rlimit rlim; if (getrlimit(RLIMIT_NOFILE, &rlim)) log_android(ANDROID_LOG_WARN, "getrlimit error %d: %s", errno, strerror(errno)); else { rlim_t soft = rlim.rlim_cur; rlim.rlim_cur = rlim.rlim_max; if (setrlimit(RLIMIT_NOFILE, &rlim)) log_android(ANDROID_LOG_WARN, "setrlimit error %d: %s", errno, strerror(errno)); else log_android(ANDROID_LOG_WARN, "raised file limit from %d to %d", soft, rlim.rlim_cur); } return JNI_VERSION_1_6; }
int protect_socket(const struct arguments *args, int socket) { jclass cls = (*args->env)->GetObjectClass(args->env, args->instance); if (midProtect == NULL) midProtect = jniGetMethodID(args->env, cls, "protect", "(I)Z"); jboolean isProtected = (*args->env)->CallBooleanMethod( args->env, args->instance, midProtect, socket); jniCheckException(args->env); if (!isProtected) { log_android(ANDROID_LOG_ERROR, "protect socket failed"); return -1; } (*args->env)->DeleteLocalRef(args->env, cls); return 0; }
JNIEXPORT jboolean JNICALL Java_eu_faircode_netguard_Util_is_1numeric_1address(JNIEnv *env, jclass type, jstring ip_) { jboolean numeric = 0; const char *ip = (*env)->GetStringUTFChars(env, ip_, 0); struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_NUMERICHOST; struct addrinfo *result; int err = getaddrinfo(ip, NULL, &hints, &result); if (err) log_android(ANDROID_LOG_DEBUG, "getaddrinfo(%s) error %d: %s", ip, err, gai_strerror(err)); else numeric = (jboolean) (result != NULL); (*env)->ReleaseStringUTFChars(env, ip_, ip); return numeric; }
JNIEXPORT void JNICALL Java_eu_faircode_netguard_ServiceSinkhole_jni_1socks5(JNIEnv *env, jobject instance, jstring addr_, jint port, jstring username_, jstring password_) { const char *addr = (*env)->GetStringUTFChars(env, addr_, 0); const char *username = (*env)->GetStringUTFChars(env, username_, 0); const char *password = (*env)->GetStringUTFChars(env, password_, 0); strcpy(socks5_addr, addr); socks5_port = port; strcpy(socks5_username, username); strcpy(socks5_password, password); log_android(ANDROID_LOG_WARN, "SOCKS5 %s:%d user=%s", socks5_addr, socks5_port, socks5_username); (*env)->ReleaseStringUTFChars(env, addr_, addr); (*env)->ReleaseStringUTFChars(env, username_, username); (*env)->ReleaseStringUTFChars(env, password_, password); }
JNIEXPORT void JNICALL Java_eu_faircode_netguard_ServiceSinkhole_jni_1stop( JNIEnv *env, jobject instance, jint tun, jboolean clr) { pthread_t t = thread_id; log_android(ANDROID_LOG_WARN, "Stop tun %d thread %x", tun, t); if (t && pthread_kill(t, 0) == 0) { log_android(ANDROID_LOG_WARN, "Write pipe thread %x", t); if (write(pipefds[1], "x", 1) < 0) log_android(ANDROID_LOG_WARN, "Write pipe error %d: %s", errno, strerror(errno)); else { log_android(ANDROID_LOG_WARN, "Join thread %x", t); int err = pthread_join(t, NULL); if (err != 0) log_android(ANDROID_LOG_WARN, "pthread_join error %d: %s", err, strerror(err)); } if (clr) clear(); log_android(ANDROID_LOG_WARN, "Stopped thread %x", t); } else log_android(ANDROID_LOG_WARN, "Not running thread %x", t); }
JNIEXPORT void JNICALL Java_eu_faircode_netguard_ServiceSinkhole_jni_1start( JNIEnv *env, jobject instance, jint tun, jboolean fwd53, jint rcode, jint loglevel_) { loglevel = loglevel_; max_tun_msg = 0; log_android(ANDROID_LOG_WARN, "Starting tun %d fwd53 %d level %d thread %x", tun, fwd53, loglevel, thread_id); // Set blocking int flags = fcntl(tun, F_GETFL, 0); if (flags < 0 || fcntl(tun, F_SETFL, flags & ~O_NONBLOCK) < 0) log_android(ANDROID_LOG_ERROR, "fcntl tun ~O_NONBLOCK error %d: %s", errno, strerror(errno)); if (thread_id && pthread_kill(thread_id, 0) == 0) log_android(ANDROID_LOG_ERROR, "Already running thread %x", thread_id); else { jint rs = (*env)->GetJavaVM(env, &jvm); if (rs != JNI_OK) log_android(ANDROID_LOG_ERROR, "GetJavaVM failed"); // Get arguments struct arguments *args = malloc(sizeof(struct arguments)); // args->env = will be set in thread args->instance = (*env)->NewGlobalRef(env, instance); args->tun = tun; args->fwd53 = fwd53; args->rcode = rcode; // Start native thread int err = pthread_create(&thread_id, NULL, handle_events, (void *) args); if (err == 0) log_android(ANDROID_LOG_WARN, "Started thread %x", thread_id); else log_android(ANDROID_LOG_ERROR, "pthread_create error %d: %s", err, strerror(err)); } }
void handle_ip(const struct arguments *args, const uint8_t *pkt, const size_t length, const int epoll_fd, int sessions, int maxsessions) { uint8_t protocol; void *saddr; void *daddr; char source[INET6_ADDRSTRLEN + 1]; char dest[INET6_ADDRSTRLEN + 1]; char flags[10]; int flen = 0; uint8_t *payload; // Get protocol, addresses & payload uint8_t version = (*pkt) >> 4; if (version == 4) { if (length < sizeof(struct iphdr)) { log_android(ANDROID_LOG_WARN, "IP4 packet too short length %d", length); return; } struct iphdr *ip4hdr = (struct iphdr *) pkt; protocol = ip4hdr->protocol; saddr = &ip4hdr->saddr; daddr = &ip4hdr->daddr; if (ip4hdr->frag_off & IP_MF) { log_android(ANDROID_LOG_ERROR, "IP fragment offset %u", (ip4hdr->frag_off & IP_OFFMASK) * 8); return; } uint8_t ipoptlen = (uint8_t) ((ip4hdr->ihl - 5) * 4); payload = (uint8_t *) (pkt + sizeof(struct iphdr) + ipoptlen); if (ntohs(ip4hdr->tot_len) != length) { log_android(ANDROID_LOG_ERROR, "Invalid length %u header length %u", length, ntohs(ip4hdr->tot_len)); return; } if (loglevel < ANDROID_LOG_WARN) { if (!calc_checksum(0, (uint8_t *) ip4hdr, sizeof(struct iphdr))) { log_android(ANDROID_LOG_ERROR, "Invalid IP checksum"); return; } } } else if (version == 6) { if (length < sizeof(struct ip6_hdr)) { log_android(ANDROID_LOG_WARN, "IP6 packet too short length %d", length); return; } struct ip6_hdr *ip6hdr = (struct ip6_hdr *) pkt; // Skip extension headers uint16_t off = 0; protocol = ip6hdr->ip6_nxt; if (!is_upper_layer(protocol)) { log_android(ANDROID_LOG_WARN, "IP6 extension %d", protocol); off = sizeof(struct ip6_hdr); struct ip6_ext *ext = (struct ip6_ext *) (pkt + off); while (is_lower_layer(ext->ip6e_nxt) && !is_upper_layer(protocol)) { protocol = ext->ip6e_nxt; log_android(ANDROID_LOG_WARN, "IP6 extension %d", protocol); off += (8 + ext->ip6e_len); ext = (struct ip6_ext *) (pkt + off); } if (!is_upper_layer(protocol)) { off = 0; protocol = ip6hdr->ip6_nxt; log_android(ANDROID_LOG_WARN, "IP6 final extension %d", protocol); } } saddr = &ip6hdr->ip6_src; daddr = &ip6hdr->ip6_dst; payload = (uint8_t *) (pkt + sizeof(struct ip6_hdr) + off); // TODO checksum } else { log_android(ANDROID_LOG_ERROR, "Unknown version %d", version); return; } inet_ntop(version == 4 ? AF_INET : AF_INET6, saddr, source, sizeof(source)); inet_ntop(version == 4 ? AF_INET : AF_INET6, daddr, dest, sizeof(dest)); // Get ports & flags int syn = 0; uint16_t sport = 0; uint16_t dport = 0; if (protocol == IPPROTO_ICMP || protocol == IPPROTO_ICMPV6) { if (length - (payload - pkt) < sizeof(struct icmp)) { log_android(ANDROID_LOG_WARN, "ICMP packet too short"); return; } struct icmp *icmp = (struct icmp *) payload; // http://lwn.net/Articles/443051/ sport = ntohs(icmp->icmp_id); dport = ntohs(icmp->icmp_id); } else if (protocol == IPPROTO_UDP) { if (length - (payload - pkt) < sizeof(struct udphdr)) { log_android(ANDROID_LOG_WARN, "UDP packet too short"); return; } struct udphdr *udp = (struct udphdr *) payload; sport = ntohs(udp->source); dport = ntohs(udp->dest); // TODO checksum (IPv6) } else if (protocol == IPPROTO_TCP) { if (length - (payload - pkt) < sizeof(struct tcphdr)) { log_android(ANDROID_LOG_WARN, "TCP packet too short"); return; } struct tcphdr *tcp = (struct tcphdr *) payload; sport = ntohs(tcp->source); dport = ntohs(tcp->dest); if (tcp->syn) { syn = 1; flags[flen++] = 'S'; } if (tcp->ack) flags[flen++] = 'A'; if (tcp->psh) flags[flen++] = 'P'; if (tcp->fin) flags[flen++] = 'F'; if (tcp->rst) flags[flen++] = 'R'; // TODO checksum } else if (protocol != IPPROTO_HOPOPTS && protocol != IPPROTO_IGMP && protocol != IPPROTO_ESP) report_error(args, 1, "Unknown protocol %d", protocol); flags[flen] = 0; // Limit number of sessions if (sessions >= maxsessions) { if ((protocol == IPPROTO_ICMP || protocol == IPPROTO_ICMPV6) || (protocol == IPPROTO_UDP && !has_udp_session(args, pkt, payload)) || (protocol == IPPROTO_TCP && syn)) { log_android(ANDROID_LOG_ERROR, "%d of max %d sessions, dropping version %d protocol %d", sessions, maxsessions, protocol, version); return; } } // Get uid jint uid = -1; if (protocol == IPPROTO_ICMP || protocol == IPPROTO_ICMPV6 || (protocol == IPPROTO_UDP && !has_udp_session(args, pkt, payload)) || (protocol == IPPROTO_TCP && syn)) uid = get_uid_retry(version, protocol, saddr, sport); log_android(ANDROID_LOG_DEBUG, "Packet v%d %s/%u > %s/%u proto %d flags %s uid %d", version, source, sport, dest, dport, protocol, flags, uid); // Check if allowed int allowed = 0; struct allowed *redirect = NULL; if (protocol == IPPROTO_UDP && has_udp_session(args, pkt, payload)) allowed = 1; // could be a lingering/blocked session else if (protocol == IPPROTO_TCP && !syn) allowed = 1; // assume existing session else { jobject objPacket = create_packet( args, version, protocol, flags, source, sport, dest, dport, "", uid, 0); redirect = is_address_allowed(args, objPacket); allowed = (redirect != NULL); if (redirect != NULL && (*redirect->raddr == 0 || redirect->rport == 0)) redirect = NULL; } // Handle allowed traffic if (allowed) { if (protocol == IPPROTO_ICMP || protocol == IPPROTO_ICMPV6) handle_icmp(args, pkt, length, payload, uid, epoll_fd); else if (protocol == IPPROTO_UDP) handle_udp(args, pkt, length, payload, uid, redirect, epoll_fd); else if (protocol == IPPROTO_TCP) handle_tcp(args, pkt, length, payload, uid, redirect, epoll_fd); } else { if (protocol == IPPROTO_UDP) block_udp(args, pkt, length, payload, uid); log_android(ANDROID_LOG_WARN, "Address v%d p%d %s/%u syn %d not allowed", version, protocol, dest, dport, syn); } }
int check_dhcp(const struct arguments *args, const struct udp_session *u, const uint8_t *data, const size_t datalen) { // This is untested // Android routing of DHCP is erroneous log_android(ANDROID_LOG_WARN, "DHCP check"); if (datalen < sizeof(struct dhcp_packet)) { log_android(ANDROID_LOG_WARN, "DHCP packet size %d", datalen); return -1; } const struct dhcp_packet *request = (struct dhcp_packet *) data; if (ntohl(request->option_format) != DHCP_OPTION_MAGIC_NUMBER) { log_android(ANDROID_LOG_WARN, "DHCP invalid magic %x", request->option_format); return -1; } if (request->htype != 1 || request->hlen != 6) { log_android(ANDROID_LOG_WARN, "DHCP unknown hardware htype %d hlen %d", request->htype, request->hlen); return -1; } log_android(ANDROID_LOG_WARN, "DHCP opcode", request->opcode); // Discover: source 0.0.0.0:68 destination 255.255.255.255:67 // Offer: source 10.1.10.1:67 destination 255.255.255.255:68 // Request: source 0.0.0.0:68 destination 255.255.255.255:67 // Ack: source: 10.1.10.1 destination: 255.255.255.255 if (request->opcode == 1) { // Discover/request struct dhcp_packet *response = calloc(500, 1); // Hack inet_pton(AF_INET, "10.1.10.1", &u->saddr); /* Discover: DHCP option 53: DHCP Discover DHCP option 50: 192.168.1.100 requested DHCP option 55: Parameter Request List: Request Subnet Mask (1), Router (3), Domain Name (15), Domain Name Server (6) Request DHCP option 53: DHCP Request DHCP option 50: 192.168.1.100 requested DHCP option 54: 192.168.1.1 DHCP server. */ memcpy(response, request, sizeof(struct dhcp_packet)); response->opcode = (uint8_t) (request->siaddr == 0 ? 2 /* Offer */ : /* Ack */ 4); response->secs = 0; response->flags = 0; memset(&response->ciaddr, 0, sizeof(response->ciaddr)); inet_pton(AF_INET, "10.1.10.2", &response->yiaddr); inet_pton(AF_INET, "10.1.10.1", &response->siaddr); memset(&response->giaddr, 0, sizeof(response->giaddr)); // https://tools.ietf.org/html/rfc2132 uint8_t *options = (uint8_t *) (response + sizeof(struct dhcp_packet)); int idx = 0; *(options + idx++) = 53; // Message type *(options + idx++) = 1; *(options + idx++) = (uint8_t) (request->siaddr == 0 ? 2 : 5); /* 1 DHCPDISCOVER 2 DHCPOFFER 3 DHCPREQUEST 4 DHCPDECLINE 5 DHCPACK 6 DHCPNAK 7 DHCPRELEASE 8 DHCPINFORM */ *(options + idx++) = 1; // subnet mask *(options + idx++) = 4; // IP4 length inet_pton(AF_INET, "255.255.255.0", options + idx); idx += 4; *(options + idx++) = 3; // gateway *(options + idx++) = 4; // IP4 length inet_pton(AF_INET, "10.1.10.1", options + idx); idx += 4; *(options + idx++) = 51; // lease time *(options + idx++) = 4; // quad *((uint32_t *) (options + idx)) = 3600; idx += 4; *(options + idx++) = 54; // DHCP *(options + idx++) = 4; // IP4 length inet_pton(AF_INET, "10.1.10.1", options + idx); idx += 4; *(options + idx++) = 6; // DNS *(options + idx++) = 4; // IP4 length inet_pton(AF_INET, "8.8.8.8", options + idx); idx += 4; *(options + idx++) = 255; // End /* DHCP option 53: DHCP Offer DHCP option 1: 255.255.255.0 subnet mask DHCP option 3: 192.168.1.1 router DHCP option 51: 86400s (1 day) IP address lease time DHCP option 54: 192.168.1.1 DHCP server DHCP option 6: DNS servers 9.7.10.15 */ write_udp(args, u, (uint8_t *) response, 500); free(response); } return 0; }
void check_allowed(const struct arguments *args) { char source[INET6_ADDRSTRLEN + 1]; char dest[INET6_ADDRSTRLEN + 1]; struct icmp_session *i = icmp_session; while (i != NULL) { if (!i->stop) { if (i->version == 4) { inet_ntop(AF_INET, &i->saddr.ip4, source, sizeof(source)); inet_ntop(AF_INET, &i->daddr.ip4, dest, sizeof(dest)); } else { inet_ntop(AF_INET6, &i->saddr.ip6, source, sizeof(source)); inet_ntop(AF_INET6, &i->daddr.ip6, dest, sizeof(dest)); } jobject objPacket = create_packet( args, i->version, IPPROTO_ICMP, "", source, 0, dest, 0, "", i->uid, 0); if (is_address_allowed(args, objPacket) == NULL) { i->stop = 1; log_android(ANDROID_LOG_WARN, "ICMP terminate %d uid %d", i->socket, i->uid); } } i = i->next; } struct udp_session *l = NULL; struct udp_session *u = udp_session; while (u != NULL) { if (u->state == UDP_ACTIVE) { if (u->version == 4) { inet_ntop(AF_INET, &u->saddr.ip4, source, sizeof(source)); inet_ntop(AF_INET, &u->daddr.ip4, dest, sizeof(dest)); } else { inet_ntop(AF_INET6, &u->saddr.ip6, source, sizeof(source)); inet_ntop(AF_INET6, &u->daddr.ip6, dest, sizeof(dest)); } jobject objPacket = create_packet( args, u->version, IPPROTO_UDP, "", source, ntohs(u->source), dest, ntohs(u->dest), "", u->uid, 0); if (is_address_allowed(args, objPacket) == NULL) { u->state = UDP_FINISHING; log_android(ANDROID_LOG_WARN, "UDP terminate session socket %d uid %d", u->socket, u->uid); } } else if (u->state == UDP_BLOCKED) { log_android(ANDROID_LOG_WARN, "UDP remove blocked session uid %d", u->uid); if (l == NULL) udp_session = u->next; else l->next = u->next; struct udp_session *c = u; u = u->next; free(c); continue; } l = u; u = u->next; } struct tcp_session *t = tcp_session; while (t != NULL) { if (t->state != TCP_CLOSING && t->state != TCP_CLOSE) { if (t->version == 4) { inet_ntop(AF_INET, &t->saddr.ip4, source, sizeof(source)); inet_ntop(AF_INET, &t->daddr.ip4, dest, sizeof(dest)); } else { inet_ntop(AF_INET6, &t->saddr.ip6, source, sizeof(source)); inet_ntop(AF_INET6, &t->daddr.ip6, dest, sizeof(dest)); } jobject objPacket = create_packet( args, t->version, IPPROTO_TCP, "", source, ntohs(t->source), dest, ntohs(t->dest), "", t->uid, 0); if (is_address_allowed(args, objPacket) == NULL) { write_rst(args, t); log_android(ANDROID_LOG_WARN, "TCP terminate socket %d uid %d", t->socket, t->uid); } } t = t->next; } }
void *handle_events(void *a) { int sdk; fd_set rfds; fd_set wfds; fd_set efds; struct timespec ts; sigset_t blockset; sigset_t emptyset; struct sigaction sa; struct arguments *args = (struct arguments *) a; log_android(ANDROID_LOG_WARN, "Start events tun=%d thread %x", args->tun, thread_id); // Attach to Java JNIEnv *env; jint rs = (*jvm)->AttachCurrentThread(jvm, &env, NULL); if (rs != JNI_OK) { log_android(ANDROID_LOG_ERROR, "AttachCurrentThread failed"); return NULL; } args->env = env; // Get SDK version sdk = sdk_int(env); int maxsessions = 1024; struct rlimit rlim; if (getrlimit(RLIMIT_NOFILE, &rlim)) log_android(ANDROID_LOG_WARN, "getrlimit error %d: %s", errno, strerror(errno)); else { maxsessions = rlim.rlim_cur * 80 / 100; log_android(ANDROID_LOG_WARN, "getrlimit soft %d hard %d max sessions %d", rlim.rlim_cur, rlim.rlim_max, maxsessions); } // Block SIGUSR1 sigemptyset(&blockset); sigaddset(&blockset, SIGUSR1); sigprocmask(SIG_BLOCK, &blockset, NULL); /// Handle SIGUSR1 sa.sa_sigaction = handle_signal; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGUSR1, &sa, NULL); // Terminate existing sessions not allowed anymore check_allowed(args); stopping = 0; signaled = 0; // Loop while (!stopping) { log_android(ANDROID_LOG_DEBUG, "Loop thread %x", thread_id); // Count sessions int isessions = get_icmp_sessions(); int usessions = get_udp_sessions(); int tsessions = get_tcp_sessions(); int sessions = isessions + usessions + tsessions; // Check sessions check_icmp_sessions(args, sessions, maxsessions); check_udp_sessions(args, sessions, maxsessions); check_tcp_sessions(args, sessions, maxsessions); // https://bugzilla.mozilla.org/show_bug.cgi?id=1093893 int idle = (tsessions + usessions + tsessions == 0 && sdk >= 16); log_android(ANDROID_LOG_DEBUG, "sessions ICMP %d UDP %d TCP %d max %d/%d idle %d sdk %d", isessions, usessions, tsessions, sessions, maxsessions, idle, sdk); // Next event time ts.tv_sec = (sdk < 16 ? 5 : get_select_timeout(sessions, maxsessions)); ts.tv_nsec = 0; sigemptyset(&emptyset); // Check if tun is writable FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds); FD_SET(args->tun, &wfds); if (pselect(args->tun + 1, &rfds, &wfds, &efds, &ts, &emptyset) == 0) { log_android(ANDROID_LOG_WARN, "tun not writable"); continue; } // Select int max = get_selects(args, &rfds, &wfds, &efds); int ready = pselect(max + 1, &rfds, &wfds, &efds, idle ? NULL : &ts, &emptyset); if (ready < 0) { if (errno == EINTR) { if (stopping && signaled) { ; log_android(ANDROID_LOG_WARN, "pselect signaled tun %d thread %x", args->tun, thread_id); report_exit(args, NULL); break; } else { // TODO check if SIGUSR1 is free log_android(ANDROID_LOG_DEBUG, "pselect interrupted tun %d thread %x", args->tun, thread_id); continue; } } else if (errno == EBADF) { struct stat sb; if (fstat(args->tun, &sb) < 0) { log_android(ANDROID_LOG_ERROR, "tun socket %d select error %d: %s", args->tun, errno, strerror(errno)); report_exit(args, "tun socket %d select error %d: %s", args->tun, errno, strerror(errno)); } else { log_android(ANDROID_LOG_WARN, "pselect EBADF"); break; } } else { log_android(ANDROID_LOG_ERROR, "pselect tun %d thread %x error %d: %s", args->tun, thread_id, errno, strerror(errno)); report_exit(args, "pselect tun %d thread %x error %d: %s", args->tun, thread_id, errno, strerror(errno)); break; } } if (ready == 0) log_android(ANDROID_LOG_DEBUG, "pselect timeout"); else { log_android(ANDROID_LOG_DEBUG, "pselect ready %d", ready); if (pthread_mutex_lock(&lock)) log_android(ANDROID_LOG_ERROR, "pthread_mutex_lock failed"); #ifdef PROFILE_EVENTS struct timeval start, end; float mselapsed; gettimeofday(&start, NULL); #endif // Check upstream int error = 0; if (check_tun(args, &rfds, &wfds, &efds, sessions, maxsessions) < 0) error = 1; else { #ifdef PROFILE_EVENTS gettimeofday(&end, NULL); mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0; if (mselapsed > PROFILE_EVENTS) log_android(ANDROID_LOG_WARN, "tun %f", mselapsed); gettimeofday(&start, NULL); #endif // Check ICMP downstream check_icmp_sockets(args, &rfds, &wfds, &efds); // Check UDP downstream check_udp_sockets(args, &rfds, &wfds, &efds); // Check TCP downstream check_tcp_sockets(args, &rfds, &wfds, &efds); } if (pthread_mutex_unlock(&lock)) log_android(ANDROID_LOG_ERROR, "pthread_mutex_unlock failed"); if (error) break; #ifdef PROFILE_EVENTS gettimeofday(&end, NULL); mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0; if (mselapsed > PROFILE_EVENTS) log_android(ANDROID_LOG_WARN, "sockets %f", mselapsed); #endif } } (*env)->DeleteGlobalRef(env, args->instance); // Detach from Java rs = (*jvm)->DetachCurrentThread(jvm); if (rs != JNI_OK) log_android(ANDROID_LOG_ERROR, "DetachCurrentThread failed"); // Cleanup free(args); log_android(ANDROID_LOG_WARN, "Stopped events tun=%d thread %x", args->tun, thread_id); thread_id = 0; return NULL; }
void handle_signal(int sig, siginfo_t *info, void *context) { log_android(ANDROID_LOG_DEBUG, "Signal %d", sig); signaled = 1; }
int get_selects(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *efds) { struct stat sb; // Initialize FD_ZERO(rfds); FD_ZERO(wfds); FD_ZERO(efds); // Always select tun FD_SET(args->tun, rfds); FD_SET(args->tun, efds); int max = args->tun; // Select ICMP sockets struct icmp_session *i = icmp_session; while (i != NULL) { if (!i->stop) { if (fstat(i->socket, &sb) < 0) { log_android(ANDROID_LOG_WARN, "ICMP socket %d select error %d: %s", i->socket, errno, strerror(errno)); i->stop = 1; } else { FD_SET(i->socket, efds); FD_SET(i->socket, rfds); if (i->socket > max) max = i->socket; } } i = i->next; } // Select UDP sockets struct udp_session *u = udp_session; while (u != NULL) { if (u->state == UDP_ACTIVE) { if (fstat(u->socket, &sb) < 0) { log_android(ANDROID_LOG_WARN, "UDP socket %d select error %d: %s", u->socket, errno, strerror(errno)); u->state = UDP_FINISHING; } else { FD_SET(u->socket, efds); FD_SET(u->socket, rfds); if (u->socket > max) max = u->socket; } } u = u->next; } // Select TCP sockets struct tcp_session *t = tcp_session; while (t != NULL) { // Select sockets if (t->socket >= 0) { if (fstat(t->socket, &sb) < 0) { log_android(ANDROID_LOG_WARN, "TCP socket %d select error %d: %s", t->socket, errno, strerror(errno)); write_rst(args, t); } else { if (t->state == TCP_LISTEN) { // Check for errors FD_SET(t->socket, efds); // Check for connected = writable FD_SET(t->socket, wfds); if (t->socket > max) max = t->socket; } else if (t->state == TCP_ESTABLISHED || t->state == TCP_CLOSE_WAIT) { // Check errors FD_SET(t->socket, efds); // Check for incoming data size_t send_window = get_send_window(t); if (send_window > 0) FD_SET(t->socket, rfds); // Check for outgoing data if (t->forward != NULL) FD_SET(t->socket, wfds); if (t->socket > max) max = t->socket; } } } t = t->next; } return max; }
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; }
int check_tun(const struct arguments *args, const struct epoll_event *ev, const int epoll_fd, int sessions, int maxsessions) { // Check tun error if (ev->events & EPOLLERR) { 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 (ev->events & EPOLLIN) { uint8_t *buffer = malloc(get_mtu()); ssize_t length = read(args->tun, buffer, get_mtu()); if (length < 0) { free(buffer); log_android(ANDROID_LOG_ERROR, "tun %d read error %d: %s", args->tun, errno, strerror(errno)); if (errno == EINTR || errno == EAGAIN) // Retry later return 0; else { report_exit(args, "tun %d read error %d: %s", args->tun, 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, epoll_fd, sessions, maxsessions); 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; }
jobject create_packet(const struct arguments *args, jint version, jint protocol, const char *flags, const char *source, jint sport, const char *dest, jint dport, const char *data, jint uid, jboolean allowed) { JNIEnv *env = args->env; #ifdef PROFILE_JNI float mselapsed; struct timeval start, end; gettimeofday(&start, NULL); #endif /* jbyte b[] = {1,2,3}; jbyteArray ret = env->NewByteArray(3); env->SetByteArrayRegion (ret, 0, 3, b); */ const char *packet = "eu/faircode/netguard/Packet"; if (midInitPacket == NULL) midInitPacket = jniGetMethodID(env, clsPacket, "<init>", "()V"); jobject jpacket = jniNewObject(env, clsPacket, midInitPacket, packet); if (fidTime == NULL) { const char *string = "Ljava/lang/String;"; fidTime = jniGetFieldID(env, clsPacket, "time", "J"); fidVersion = jniGetFieldID(env, clsPacket, "version", "I"); fidProtocol = jniGetFieldID(env, clsPacket, "protocol", "I"); fidFlags = jniGetFieldID(env, clsPacket, "flags", string); fidSaddr = jniGetFieldID(env, clsPacket, "saddr", string); fidSport = jniGetFieldID(env, clsPacket, "sport", "I"); fidDaddr = jniGetFieldID(env, clsPacket, "daddr", string); fidDport = jniGetFieldID(env, clsPacket, "dport", "I"); fidData = jniGetFieldID(env, clsPacket, "data", string); fidUid = jniGetFieldID(env, clsPacket, "uid", "I"); fidAllowed = jniGetFieldID(env, clsPacket, "allowed", "Z"); } 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); jstring jdata = (*env)->NewStringUTF(env, data); (*env)->SetLongField(env, jpacket, fidTime, t); (*env)->SetIntField(env, jpacket, fidVersion, version); (*env)->SetIntField(env, jpacket, fidProtocol, protocol); (*env)->SetObjectField(env, jpacket, fidFlags, jflags); (*env)->SetObjectField(env, jpacket, fidSaddr, jsource); (*env)->SetIntField(env, jpacket, fidSport, sport); (*env)->SetObjectField(env, jpacket, fidDaddr, jdest); (*env)->SetIntField(env, jpacket, fidDport, dport); (*env)->SetObjectField(env, jpacket, fidData, jdata); (*env)->SetIntField(env, jpacket, fidUid, uid); (*env)->SetBooleanField(env, jpacket, fidAllowed, allowed); (*env)->DeleteLocalRef(env, jdata); (*env)->DeleteLocalRef(env, jdest); (*env)->DeleteLocalRef(env, jsource); (*env)->DeleteLocalRef(env, jflags); // Caller needs to delete reference to packet #ifdef PROFILE_JNI gettimeofday(&end, NULL); mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0; if (mselapsed > PROFILE_JNI) log_android(ANDROID_LOG_WARN, "create_packet %f", mselapsed); #endif return jpacket; }
jint get_uid(const int version, const int protocol, const void *saddr, const uint16_t sport, int dump) { char line[250]; char hex[16 * 2 + 1]; int fields; uint8_t addr4[4]; uint8_t addr6[16]; int port; jint uid = -1; #ifdef PROFILE_UID float mselapsed; struct timeval start, end; gettimeofday(&start, NULL); #endif // NETLINK is not available on Android due to SELinux policies :-( // Get proc file name char *fn = NULL; if (protocol == IPPROTO_ICMP && version == 4) fn = "/proc/net/icmp"; else if (protocol == IPPROTO_ICMPV6 && version == 6) fn = "/proc/net/icmp6"; else if (protocol == IPPROTO_TCP) fn = (version == 4 ? "/proc/net/tcp" : "/proc/net/tcp6"); else if (protocol == IPPROTO_UDP) fn = (version == 4 ? "/proc/net/udp" : "/proc/net/udp6"); else return uid; if (dump) { char source[INET6_ADDRSTRLEN + 1]; inet_ntop(version == 4 ? AF_INET : AF_INET6, saddr, source, sizeof(source)); log_android(ANDROID_LOG_INFO, "Searching %s/%u in %s", source, sport, fn); } // Open proc file FILE *fd = fopen(fn, "r"); if (fd == NULL) { log_android(ANDROID_LOG_ERROR, "fopen %s error %d: %s", fn, errno, strerror(errno)); return uid; } // Scan proc file jint u; int i = 0; *line = 0; while (fgets(line, sizeof(line), fd) != NULL) { if (i++) { *hex = 0; port = -1; u = -1; if (version == 4) fields = sscanf( line, "%*d: %8s:%X %*X:%*X %*X %*lX:%*lX %*X:%*X %*X %d %*d %*ld", hex, &port, &u); else fields = sscanf( line, "%*d: %32s:%X %*X:%*X %*X %*lX:%*lX %*X:%*X %*X %d %*d %*ld", hex, &port, &u); if (fields == 3 && (version == 4 ? strlen(hex) == 8 : strlen(hex) == 32) && port >= 0 && u >= 0) { hex2bytes(hex, version == 4 ? addr4 : addr6); if (version == 4) ((uint32_t *) addr4)[0] = htonl(((uint32_t *) addr4)[0]); for (int w = 0; w < 4; w++) ((uint32_t *) addr6)[w] = htonl(((uint32_t *) addr6)[w]); if (dump) { char source[INET6_ADDRSTRLEN + 1]; inet_ntop(version == 4 ? AF_INET : AF_INET6, version == 4 ? addr4 : addr6, source, sizeof(source)); log_android(ANDROID_LOG_INFO, "%s/%u %d %s", source, port, u, line); } if (port == sport) { uid = u; if (memcmp(version == 4 ? addr4 : addr6, saddr, version == 4 ? 4 : 16) == 0) break; } } else log_android(ANDROID_LOG_ERROR, "Invalid field #%d: %s", fields, line); } } if (fclose(fd)) log_android(ANDROID_LOG_ERROR, "fclose %s error %d: %s", fn, errno, strerror(errno)); #ifdef PROFILE_UID gettimeofday(&end, NULL); mselapsed = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0; if (mselapsed > PROFILE_UID) log_android(ANDROID_LOG_WARN, "get uid ip %f", mselapsed); #endif return uid; }
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; }