diff --git a/app/app.iml b/app/app.iml index 6e13ec56..c8002dfb 100644 --- a/app/app.iml +++ b/app/app.iml @@ -98,6 +98,7 @@ + diff --git a/app/src/main/java/eu/faircode/netguard/ActivityLog.java b/app/src/main/java/eu/faircode/netguard/ActivityLog.java index d952cf5d..fa970072 100644 --- a/app/src/main/java/eu/faircode/netguard/ActivityLog.java +++ b/app/src/main/java/eu/faircode/netguard/ActivityLog.java @@ -292,6 +292,9 @@ public class ActivityLog extends AppCompatActivity { OutputStream out = null; FileInputStream in = null; try { + // Stop capture + SinkholeService.setPcap(null); + Log.i(TAG, "Export PCAP URI=" + data.getData()); out = getContentResolver().openOutputStream(data.getData()); @@ -313,6 +316,13 @@ public class ActivityLog extends AppCompatActivity { Util.sendCrashReport(ex, ActivityLog.this); return ex; } finally { + // Resume capture + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ActivityLog.this); + if (prefs.getBoolean("pcap", false)) { + File pcap_file = new File(getCacheDir(), "netguard.pcap"); + SinkholeService.setPcap(pcap_file); + } + if (out != null) try { out.close(); diff --git a/app/src/main/jni/netguard/netguard.c b/app/src/main/jni/netguard/netguard.c index bbdb2035..7734ec45 100644 --- a/app/src/main/jni/netguard/netguard.c +++ b/app/src/main/jni/netguard/netguard.c @@ -55,6 +55,7 @@ int signaled = 0; struct udp_session *udp_session = NULL; struct tcp_session *tcp_session = NULL; + int loglevel = 0; FILE *pcap_file = NULL; @@ -608,7 +609,9 @@ void check_udp_sockets(const struct arguments *args, fd_set *rfds, fd_set *wfds, inet_ntop(AF_INET, &(cur->daddr), dest, sizeof(dest)); log_android(ANDROID_LOG_INFO, "UDP recv bytes %d for %s/%u @tun", bytes, dest, ntohs(cur->dest)); - write_udp(args, cur, buffer, bytes, args->tun); + if (write_udp(args, cur, buffer, bytes, args->tun) < 0) + log_android(ANDROID_LOG_ERROR, "write UDP error %d: %s", + errno, strerror((errno))); } } } @@ -928,60 +931,15 @@ jboolean handle_udp(const struct arguments *args, const uint8_t *buffer, uint16_ cur = cur->next; } + char source[40]; + char dest[40]; + inet_ntop(version == 4 ? AF_INET : AF_INET6, &(iphdr->saddr), source, sizeof(source)); + inet_ntop(version == 4 ? AF_INET : AF_INET6, &(iphdr->daddr), dest, sizeof(dest)); + // Create new session if needed if (cur == NULL) { - log_android(ANDROID_LOG_INFO, "UDP new session"); - - // Check for DNS - if (ntohs(udphdr->dest) == 53 && datalen > sizeof(struct dns_header)) { - char *h = hex(buffer + dataoff, datalen); - log_android(ANDROID_LOG_DEBUG, "DNS off %d len %d hex %s", dataoff, datalen, h); - free(h); - - struct dns_header *dns = (struct dns_header *) (buffer + dataoff); - uint16_t flags = ntohs(dns->flags); - - // Check if standard query - if ((flags & DNS_QR) == 0 && (flags & DNS_OPCODE) == 0 && dns->qdcount != 0) { - char name[64]; - uint8_t noff = 0; - - // http://tools.ietf.org/html/rfc1035 - uint8_t len; - uint16_t ptr; - uint16_t qdoff = dataoff + sizeof(struct dns_header); - do { - len = *(buffer + qdoff); - if (len < 63) { - ptr = qdoff; - qdoff += (1 + len); - } else { - // TODO check top 2 bits - ptr = dataoff + (len & 63); - len = *(buffer + ptr); - qdoff += 1; - } - log_android(ANDROID_LOG_DEBUG, "DNS len %d ptr %d qdoff %d", len, ptr, qdoff); - if (len && ptr + 1 + len <= dataoff + datalen) { - memcpy(name + noff, buffer + ptr + 1, len); - *(name + noff + len) = '.'; - noff += (len + 1); - } - } while (len && ptr + 1 + len <= dataoff + datalen); - - if (noff > 0 && qdoff + 4 <= dataoff + datalen) { - *(name + noff - 1) = 0; - uint16_t qtype = ntohs(*((uint16_t *) (buffer + qdoff))); - uint16_t qclass = ntohs(*((uint16_t *) (buffer + qdoff + 2))); - log_android(ANDROID_LOG_WARN, "DNS %s qtype %d qclass %d", name, qtype, qclass); - for (int i = 0; i < args->hcount; i++) - if (!strcmp(name, args->hosts[i])) { - log_android(ANDROID_LOG_WARN, "DNS %s blocked", name); - return 0; - } - } - } - } + log_android(ANDROID_LOG_WARN, "UDP new session from %s/%u to %s/%u", + source, ntohs(udphdr->source), dest, ntohs(udphdr->dest)); // Register session struct udp_session *u = malloc(sizeof(struct udp_session)); @@ -1023,10 +981,9 @@ jboolean handle_udp(const struct arguments *args, const uint8_t *buffer, uint16_ } } - char source[40]; - char dest[40]; - inet_ntop(version == 4 ? AF_INET : AF_INET6, &(iphdr->saddr), source, sizeof(source)); - inet_ntop(version == 4 ? AF_INET : AF_INET6, &(iphdr->daddr), dest, sizeof(dest)); + // Check for DNS + if (check_dns(args, cur, buffer, length)) + return 0; log_android(ANDROID_LOG_INFO, "UDP forward from tun %s/%u to %s/%u data %d", source, ntohs(udphdr->source), dest, ntohs(udphdr->dest), datalen); @@ -1048,6 +1005,90 @@ jboolean handle_udp(const struct arguments *args, const uint8_t *buffer, uint16_ return 1; } +int check_dns(const struct arguments *args, const struct udp_session *u, + const uint8_t *buffer, const uint16_t length) { + // 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; + + if (ntohs(udphdr->dest) == 53 && datalen > sizeof(struct dns_header)) { + const struct dns_header *dns = (struct dns_header *) (buffer + dataoff); + uint16_t flags = ntohs(dns->flags); + + // Check if standard query + if ((flags & DNS_QR) == 0 && (flags & DNS_OPCODE) == 0 && dns->qdcount != 0) { + char name[64]; + uint8_t noff = 0; + + // http://tools.ietf.org/html/rfc1035 + uint8_t len; + uint16_t ptr; + uint16_t qdoff = dataoff + sizeof(struct dns_header); + do { + len = *(buffer + qdoff); + if (len <= 0x3F) { + ptr = qdoff; + qdoff += (1 + len); + } else { + // TODO check top 2 bits + ptr = dataoff + (len & 0x3F) * 256 + *(buffer + qdoff + 1); + len = *(buffer + ptr); + qdoff += 2; + } + if (len && ptr + 1 + len <= dataoff + datalen) { + memcpy(name + noff, buffer + ptr + 1, len); + *(name + noff + len) = '.'; + noff += (len + 1); + } + } while (len && ptr + 1 + len <= dataoff + datalen); + + if (noff > 0 && qdoff + 4 <= dataoff + datalen) { + *(name + noff - 1) = 0; + uint16_t qtype = ntohs(*((uint16_t *) (buffer + qdoff))); + uint16_t qclass = ntohs(*((uint16_t *) (buffer + qdoff + 2))); + qdoff += 4; + + if (qtype == DNS_QTYPE_A && qclass == DNS_QCLASS_IN) + for (int i = 0; i < args->hcount; i++) + if (!strcmp(name, args->hosts[i])) { + log_android(ANDROID_LOG_WARN, "DNS %s blocked", name); + + struct dns_response reply; + reply.qname_ptr = htons(sizeof(struct dns_header) | 0xC000); + reply.qtype = htons(qtype); + reply.qclass = htons(qclass); + reply.ttl = htonl(DNS_TTL); // seconds + reply.rdlength = htons(sizeof(reply.rdata)); + inet_aton("127.0.0.1", &(reply.rdata)); + + uint16_t qsize = qdoff - dataoff; + uint16_t rsize = qsize + sizeof(struct dns_response); + uint8_t *response = malloc(rsize); + memcpy(response, buffer + dataoff, qsize); + memcpy(response + qsize, &reply, sizeof(struct dns_response)); + + struct dns_header *rh = response; + rh->flags = htons(DNS_QR); + rh->ancount = htons(1); + + if (write_udp(args, u, response, rsize, args->tun) < 0) + log_android(ANDROID_LOG_ERROR, "write UDP error %d: %s", + errno, strerror((errno))); + + return 1; + } + } + } + } + + return 0; +} + jboolean handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t length, int uid) { #ifdef PROFILE float mselapsed; @@ -1555,8 +1596,9 @@ 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, ip->protocol, "", - source, ntohs(udp->source), dest, ntohs(udp->dest), cur->uid, 1); + if (args->log) + log_packet(args, cur->version, ip->protocol, "", + source, ntohs(udp->source), dest, ntohs(udp->dest), cur->uid, 1); // Write pcap record if (pcap_file != NULL) @@ -2063,7 +2105,7 @@ void read_hosts(const char *name, struct arguments *args) { log_android(ANDROID_LOG_ERROR, "fclose(%s) error %d: %s", name, errno, strerror(errno)); for (int i = 0; i < args->hcount; i++) - log_android(ANDROID_LOG_WARN, "host '%s'", args->hosts[i]); + log_android(ANDROID_LOG_DEBUG, "host '%s'", args->hosts[i]); } const char *strstate(const int state) { @@ -2108,5 +2150,7 @@ char *hex(const u_int8_t *data, const u_int16_t len) { hexout[i * 3 + 1] = hex_str[(data[i]) & 0x0F]; hexout[i * 3 + 2] = ' '; } + hexout[len * 3] = 0; + return hexout; } diff --git a/app/src/main/jni/netguard/netguard.h b/app/src/main/jni/netguard/netguard.h index be578fdc..8b07bf8d 100644 --- a/app/src/main/jni/netguard/netguard.h +++ b/app/src/main/jni/netguard/netguard.h @@ -23,7 +23,7 @@ #define UID_MAXTRY 3 #define MAX_PCAP_FILE (1024 * 1024) // bytes -#define MAX_PCAP_RECORD 80 // bytes +#define MAX_PCAP_RECORD 128 // bytes struct arguments { JNIEnv *env; @@ -72,11 +72,11 @@ struct tcp_session { // https://wiki.wireshark.org/Development/LibpcapFileFormat -typedef unsigned short guint16_t; -typedef unsigned int guint32_t; -typedef signed int gint32_t; +typedef uint16_t guint16_t; +typedef uint32_t guint32_t; +typedef int32_t gint32_t; -typedef struct pcap_hdr_s { +typedef struct __attribute__((__packed__)) pcap_hdr_s { guint32_t magic_number; guint16_t version_major; guint16_t version_minor; @@ -86,8 +86,7 @@ typedef struct pcap_hdr_s { guint32_t network; } pcap_hdr_t; - -typedef struct pcaprec_hdr_s { +typedef struct __attribute__((__packed__)) pcaprec_hdr_s { guint32_t ts_sec; guint32_t ts_usec; guint32_t incl_len; @@ -96,7 +95,7 @@ typedef struct pcaprec_hdr_s { #define LINKTYPE_RAW 101 -typedef struct dns_header { +typedef struct __attribute__((__packed__)) dns_header { __be16 msgid; __be16 flags; __be16 qdcount; @@ -105,6 +104,15 @@ typedef struct dns_header { __be16 arcount; } dns_header_t; +typedef struct __attribute__((__packed__)) dns_response { + __be16 qname_ptr; + __be16 qtype; + __be16 qclass; + __be32 ttl; + __be16 rdlength; + __be32 rdata; +} dns_response_t; + #define DNS_QR 1 #define DNS_OPCODE 30 #define DNS_TC 8 @@ -113,6 +121,8 @@ typedef struct dns_header { #define DNS_QTYPE_AAAA 28 // IPv6 #define DNS_QCLASS_IN 1 +#define DNS_TTL 3600 // seconds + void clear_sessions(); void handle_signal(int sig, siginfo_t *info, void *context); @@ -137,6 +147,9 @@ jboolean handle_udp(const struct arguments *args, const uint8_t *buffer, uint16_ jboolean handle_tcp(const struct arguments *args, const uint8_t *buffer, uint16_t length, int uid); +int check_dns(const struct arguments *args, const struct udp_session *u, + const uint8_t *buffer, const uint16_t length); + int open_socket(const struct tcp_session *cur, const struct arguments *args); uint16_t get_local_port(const int sock);