diff --git a/app/app.iml b/app/app.iml
index c8002dfb..6e13ec56 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -98,7 +98,6 @@
-
diff --git a/app/src/main/jni/netguard/netguard.c b/app/src/main/jni/netguard/netguard.c
index 223e9dc4..b4b3cfe5 100644
--- a/app/src/main/jni/netguard/netguard.c
+++ b/app/src/main/jni/netguard/netguard.c
@@ -34,15 +34,13 @@
#include "netguard.h"
-#define PROFILE 1
+// #define PROFILE 1
// TODO TCP fragmentation
// TODO TCP push
// TODO TCPv6
// TODO UDPv4
// TODO UDPv6
-// TODO DHCP
-// TODO log allowed traffic
// TODO fix warnings
// TODO non blocking send/write, handle EAGAIN/EWOULDBLOCK
@@ -105,7 +103,7 @@ Java_eu_faircode_netguard_SinkholeService_jni_1start(
args->filter = filter;
for (int i = 0; i < args->count; i++)
- log_android(ANDROID_LOG_INFO, "Allowed uid %d", args->uid[i]);
+ log_android(ANDROID_LOG_DEBUG, "Allowed uid %d", args->uid[i]);
// Start native thread
int err = pthread_create(&thread_id, NULL, handle_events, (void *) args);
@@ -131,15 +129,9 @@ Java_eu_faircode_netguard_SinkholeService_jni_1stop(JNIEnv *env, jobject instanc
if (err != 0)
log_android(ANDROID_LOG_WARN, "pthread_join error %d: %s", err, strerror(err));
}
- if (clear) {
- struct session *s = session;
- while (s != NULL) {
- struct session *p = s;
- s = s->next;
- free(p);
- }
- session = NULL;
- }
+ if (clear)
+ clear_sessions();
+
log_android(ANDROID_LOG_INFO, "Stopped thread %lu", thread_id);
} else
log_android(ANDROID_LOG_WARN, "Not running");
@@ -149,13 +141,7 @@ JNIEXPORT void JNICALL
Java_eu_faircode_netguard_SinkholeService_jni_1done(JNIEnv *env, jobject instance) {
log_android(ANDROID_LOG_INFO, "Done");
- struct session *s = session;
- while (s != NULL) {
- struct session *p = s;
- s = s->next;
- free(p);
- }
- session = NULL;
+ clear_sessions();
if (pcap_fn != NULL)
free(pcap_fn);
@@ -183,8 +169,20 @@ Java_eu_faircode_netguard_SinkholeService_jni_1pcap(JNIEnv *env, jclass type, js
(*env)->ReleaseStringUTFChars(env, name_, name);
}
}
+
// Private functions
+void clear_sessions() {
+ struct session *s = session;
+ while (s != NULL) {
+ close(s->socket);
+ struct session *p = s;
+ s = s->next;
+ free(p);
+ }
+ session = NULL;
+}
+
void handle_signal(int sig, siginfo_t *info, void *context) {
log_android(ANDROID_LOG_DEBUG, "Signal %d", sig);
signaled = 1;
@@ -224,6 +222,20 @@ void handle_events(void *a) {
signaled = 0;
+ // Open UDP socket
+ int usock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (usock < 0)
+ log_android(ANDROID_LOG_ERROR, "UDP socket error %d: %s", errno, strerror(errno));
+
+ protect_socket(args, usock);
+
+ struct sockaddr_in uaddr;
+ uaddr.sin_family = AF_INET;
+ uaddr.sin_addr.s_addr = INADDR_ANY;
+ uaddr.sin_port = 0;
+ if (bind(usock, (struct sockaddr *) &uaddr, sizeof(struct sockaddr)) < 0)
+ log_android(ANDROID_LOG_ERROR, "UDP bind error %d: %s", errno, strerror(errno));
+
// Loop
while (1) {
log_android(ANDROID_LOG_DEBUG, "Loop thread %lu", thread_id);
@@ -232,7 +244,7 @@ void handle_events(void *a) {
ts.tv_sec = SELECT_TIMEOUT;
ts.tv_nsec = 0;
sigemptyset(&emptyset);
- int max = get_selects(args, &rfds, &wfds, &efds);
+ int max = get_selects(args, usock, &rfds, &wfds, &efds);
int ready = pselect(max + 1, &rfds, &wfds, &efds, session == NULL ? NULL : &ts, &emptyset);
if (ready < 0) {
if (errno == EINTR) {
@@ -270,7 +282,7 @@ void handle_events(void *a) {
#endif
// Check upstream
- if (check_tun(args, &rfds, &wfds, &efds) < 0)
+ if (check_tun(args, usock, &rfds, &wfds, &efds) < 0)
break;
#ifdef PROFILE
@@ -283,6 +295,9 @@ void handle_events(void *a) {
gettimeofday(&start, NULL);
#endif
+ // Check UDP
+ check_udp(args, usock, &rfds, &wfds, &efds);
+
// Check downstream
check_sockets(args, &rfds, &wfds, &efds);
@@ -292,10 +307,12 @@ void handle_events(void *a) {
(end.tv_usec - start.tv_usec) / 1000.0;
if (mselapsed > 1)
log_android(ANDROID_LOG_INFO, "sockets %f", mselapsed);
- }
#endif
+ }
}
+ close(usock);
+
free(args->uid);
free(args);
@@ -308,20 +325,27 @@ void handle_events(void *a) {
// TODO report exit to Java
}
-int get_selects(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *efds) {
+int get_selects(const struct arguments *args, int usock, fd_set *rfds, fd_set *wfds,
+ fd_set *efds) {
time_t now = time(NULL);
- // Select
+ // Initialize
FD_ZERO(rfds);
FD_ZERO(wfds);
FD_ZERO(efds);
- // Always read tun
+ // Always select tun
FD_SET(args->tun, rfds);
FD_SET(args->tun, efds);
-
int max = args->tun;
+ // Always select UDP
+ FD_SET(usock, rfds);
+ FD_SET(usock, efds);
+ if (usock > max)
+ max = usock;
+
+ // Select sockets
struct session *last = NULL;
struct session *cur = session;
while (cur != NULL) {
@@ -391,7 +415,8 @@ int get_selects(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set
return max;
}
-int check_tun(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *efds) {
+int check_tun(const struct arguments *args, int usock, fd_set *rfds, fd_set *wfds,
+ fd_set *efds) {
// Check tun error
if (FD_ISSET(args->tun, efds)) {
log_android(ANDROID_LOG_ERROR, "tun exception");
@@ -412,7 +437,7 @@ int check_tun(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *
write_pcap_rec(buffer, length);
// Handle IP from tun
- handle_ip(args, buffer, length);
+ handle_ip(args, usock, buffer, length);
}
else {
// tun eof
@@ -424,6 +449,36 @@ int check_tun(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *
return 0;
}
+void check_udp(const struct arguments *args, int usock, fd_set *rfds, fd_set *wfds,
+ fd_set *efds) {
+ if (FD_ISSET(usock, efds)) {
+ // Socket error
+ int serr = 0;
+ socklen_t optlen = sizeof(int);
+ int err = getsockopt(usock, SOL_SOCKET, SO_ERROR, &serr, &optlen);
+ if (err < 0)
+ log_android(ANDROID_LOG_ERROR, "UDP getsockopt error %d: %s", errno,
+ strerror(errno));
+ else if (serr)
+ log_android(ANDROID_LOG_ERROR, "UDP SO_ERROR %d: %s", serr, strerror(serr));
+ }
+ else if (FD_ISSET(usock, rfds)) {
+ // Socket receive
+ uint8_t buffer[MAX_PKTSIZE];
+ struct sockaddr_in client;
+ int clen = sizeof(client);
+ ssize_t bytes = recvfrom(
+ usock, buffer, sizeof(buffer), 0,
+ (struct sockaddr *) &client, (socklen_t *) &clen);
+ int sport = ntohs(client.sin_port);
+
+ char source[40];
+ inet_ntop(client.sin_family, &client.sin_addr, source, sizeof(source));
+
+ log_android(ANDROID_LOG_INFO, "UDP from %s/%u data %d", source, sport, bytes);
+ }
+}
+
void check_sockets(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *efds) {
struct session *cur = session;
while (cur != NULL) {
@@ -519,7 +574,8 @@ void check_sockets(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_
}
}
- if (cur->state != oldstate || cur->local_seq != oldlocal || cur->remote_seq != oldremote) {
+ if (cur->state != oldstate || cur->local_seq != oldlocal ||
+ cur->remote_seq != oldremote) {
char dest[20];
inet_ntop(AF_INET, &(cur->daddr), dest, sizeof(dest));
log_android(ANDROID_LOG_INFO,
@@ -533,7 +589,8 @@ void check_sockets(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_
}
}
-void handle_ip(const struct arguments *args, const uint8_t *buffer, const uint16_t length) {
+void handle_ip(const struct arguments *args, int usock, const uint8_t *buffer,
+ const uint16_t length) {
uint8_t protocol;
void *saddr;
void *daddr;
@@ -686,7 +743,9 @@ void handle_ip(const struct arguments *args, const uint8_t *buffer, const uint16
// Handle allowed traffic
if (allowed) {
- if (protocol == IPPROTO_TCP)
+ if (protocol == IPPROTO_UDP)
+ allowed = handle_udp(args, usock, buffer, length, uid);
+ else if (protocol == IPPROTO_TCP)
allowed = handle_tcp(args, buffer, length, uid);
else
allowed = 0;
@@ -699,7 +758,45 @@ void handle_ip(const struct arguments *args, const uint8_t *buffer, const uint16
}
}
-jboolean handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t length, int uid) {
+jboolean handle_udp(const struct arguments *args, int usock,
+ const uint8_t *buffer, uint16_t length, int uid) {
+ // Check version
+ uint8_t version = (*buffer) >> 4;
+ if (version != 4)
+ return 0;
+
+ // Get headers
+ struct iphdr *iphdr = buffer;
+ uint8_t ipoptlen = (iphdr->ihl - 5) * 4;
+ struct udphdr *udphdr = buffer + sizeof(struct iphdr) + ipoptlen;
+
+ // Get data
+ uint16_t dataoff = sizeof(struct iphdr) + ipoptlen + sizeof(struct udphdr);
+ uint16_t datalen = length - dataoff;
+
+ // TODO make session
+
+ char dest[40];
+ inet_ntop(version == 4 ? AF_INET : AF_INET6, udphdr->dest, dest, sizeof(dest));
+
+ log_android(ANDROID_LOG_INFO, "Forwarding UDP to %s/%u data %d",
+ dest, ntohs(udphdr->dest), datalen);
+
+ struct sockaddr_in client;
+ client.sin_family = (version == 4 ? AF_INET : AF_INET6);
+ client.sin_addr.s_addr = iphdr->daddr;
+ client.sin_port = udphdr->dest;
+
+ if (sendto(usock, buffer + dataoff, datalen, 0, &client, sizeof(client)) != datalen) {
+ log_android(ANDROID_LOG_ERROR, "UDP sendto error %s:%s", errno, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+jboolean handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t length,
+ int uid) {
#ifdef PROFILE
float mselapsed;
struct timeval start, end;
@@ -888,7 +985,8 @@ jboolean handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_
ntohl(tcphdr->seq) == cur->remote_seq &&
ntohl(tcphdr->ack_seq) < cur->local_seq) {
int confirm = cur->local_seq - ntohl(tcphdr->ack_seq);
- log_android(ANDROID_LOG_INFO, "Simultaneous close %s/%u lport %u confirm %d",
+ log_android(ANDROID_LOG_INFO,
+ "Simultaneous close %s/%u lport %u confirm %d",
dest, ntohs(cur->dest), cur->lport, confirm);
write_ack(cur, confirm, args->tun);
}
@@ -940,7 +1038,8 @@ jboolean handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_
else if (cur->state == TCP_CLOSING)
cur->state = TCP_TIME_WAIT;
else
- log_android(ANDROID_LOG_ERROR, "Invalid ACK session %s/%u lport %u state %s",
+ log_android(ANDROID_LOG_ERROR,
+ "Invalid ACK session %s/%u lport %u state %s",
dest, ntohs(cur->dest), cur->lport, strstate(cur->state));
}
else {
@@ -969,7 +1068,8 @@ jboolean handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_
log_android(ANDROID_LOG_ERROR, "Unknown packet session %s/%u lport %u",
dest, ntohs(cur->dest), cur->lport);
- if (cur->state != oldstate || cur->local_seq != oldlocal || cur->remote_seq != oldremote)
+ if (cur->state != oldstate || cur->local_seq != oldlocal ||
+ cur->remote_seq != oldremote)
log_android(ANDROID_LOG_INFO,
"Session %s/%u lport %u new state %s local %u remote %u",
dest, ntohs(cur->dest), cur->lport, strstate(cur->state),
@@ -1006,26 +1106,8 @@ int open_tcp(const struct session *cur, const struct arguments *args) {
}
// Protect
- JNIEnv *env = args->env;
- jobject instance = args->instance;
- jclass cls = (*env)->GetObjectClass(env, instance);
- jmethodID mid = (*env)->GetMethodID(env, cls, "protect", "(I)Z");
- if (mid == 0) {
- log_android(ANDROID_LOG_ERROR, "protect not found");
+ if (protect_socket(args, sock) < 0)
return -1;
- }
- else {
- jboolean isProtected = (*env)->CallBooleanMethod(env, instance, mid, sock);
- if (!isProtected)
- log_android(ANDROID_LOG_ERROR, "protect failed");
-
- jthrowable ex = (*env)->ExceptionOccurred(env);
- if (ex) {
- (*env)->ExceptionDescribe(env);
- (*env)->ExceptionClear(env);
- (*env)->DeleteLocalRef(env, ex);
- }
- }
// Set non blocking
uint8_t flags = fcntl(sock, F_GETFL, 0);
@@ -1299,7 +1381,8 @@ jint get_uid(const int protocol, const int version,
}
if (port == sport) {
- if (memcmp(version == 4 ? addr4 : addr6, saddr, version == 4 ? 4 : 16) == 0) {
+ if (memcmp(version == 4 ? addr4 : addr6, saddr, version == 4 ? 4 : 16) ==
+ 0) {
uid = u;
break;
}
@@ -1323,6 +1406,35 @@ jint get_uid(const int protocol, const int version,
return uid;
}
+int protect_socket(struct arguments *args, int socket) {
+ JNIEnv *env = args->env;
+ jobject instance = args->instance;
+
+ jclass cls = (*env)->GetObjectClass(env, instance);
+ jmethodID mid = (*env)->GetMethodID(env, cls, "protect", "(I)Z");
+ if (mid == 0) {
+ log_android(ANDROID_LOG_ERROR, "protect method not found");
+ return -1;
+ }
+
+ jboolean isProtected = (*env)->CallBooleanMethod(env, instance, mid, socket);
+ if (!isProtected) {
+ log_android(ANDROID_LOG_ERROR, "protect failed");
+ return -1;
+ }
+
+ jthrowable ex = (*env)->ExceptionOccurred(env);
+ if (ex) {
+ log_android(ANDROID_LOG_ERROR, "protect exception");
+ (*env)->ExceptionDescribe(env);
+ (*env)->ExceptionClear(env);
+ (*env)->DeleteLocalRef(env, ex);
+ return -1;
+ }
+
+ return 0;
+}
+
uint16_t calc_checksum(uint8_t *buffer, uint16_t length) {
register uint32_t sum = 0;
register uint16_t *buf = buffer;
diff --git a/app/src/main/jni/netguard/netguard.h b/app/src/main/jni/netguard/netguard.h
index 44fbdaaf..bac8d789 100644
--- a/app/src/main/jni/netguard/netguard.h
+++ b/app/src/main/jni/netguard/netguard.h
@@ -67,17 +67,25 @@ typedef struct pcaprec_hdr_s {
#define LINKTYPE_RAW 101
+void clear_sessions();
+
void handle_signal(int sig, siginfo_t *info, void *context);
void handle_events(void *a);
-int get_selects(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *efds);
+int get_selects(const struct arguments *args, int usock, fd_set *rfds, fd_set *wfds, fd_set *efds);
-int check_tun(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *efds);
+int check_tun(const struct arguments *args, int usock, fd_set *rfds, fd_set *wfds, fd_set *efds);
+
+void check_udp(const struct arguments *args, int usock, fd_set *rfds, fd_set *wfds, fd_set *efds);
void check_sockets(const struct arguments *args, fd_set *rfds, fd_set *wfds, fd_set *efds);
-void handle_ip(const struct arguments *args, const uint8_t *buffer, const uint16_t length);
+void handle_ip(const struct arguments *args, int usock,
+ const uint8_t *buffer, const uint16_t length);
+
+jboolean handle_udp(const struct arguments *args, int usock,
+ const uint8_t *buffer, uint16_t length, int uid);
jboolean handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t length, int uid);