mirror of https://github.com/M66B/NetGuard.git
471 lines
16 KiB
C
471 lines
16 KiB
C
/*
|
|
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-2017 by Marcel Bokhorst (M66B)
|
|
*/
|
|
|
|
#include "netguard.h"
|
|
|
|
int max_tun_msg = 0;
|
|
extern int loglevel;
|
|
extern FILE *pcap_file;
|
|
|
|
uint16_t get_mtu() {
|
|
return 10000;
|
|
}
|
|
|
|
uint16_t get_default_mss(int version) {
|
|
if (version == 4)
|
|
return (uint16_t) (get_mtu() - sizeof(struct iphdr) - sizeof(struct tcphdr));
|
|
else
|
|
return (uint16_t) (get_mtu() - sizeof(struct ip6_hdr) - sizeof(struct tcphdr));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// https://en.wikipedia.org/wiki/IPv6_packet#Extension_headers
|
|
// http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
|
|
int is_lower_layer(int protocol) {
|
|
// No next header = 59
|
|
return (protocol == 0 || // Hop-by-Hop Options
|
|
protocol == 60 || // Destination Options (before routing header)
|
|
protocol == 43 || // Routing
|
|
protocol == 44 || // Fragment
|
|
protocol == 51 || // Authentication Header (AH)
|
|
protocol == 50 || // Encapsulating Security Payload (ESP)
|
|
protocol == 60 || // Destination Options (before upper-layer header)
|
|
protocol == 135); // Mobility
|
|
}
|
|
|
|
int is_upper_layer(int protocol) {
|
|
return (protocol == IPPROTO_TCP ||
|
|
protocol == IPPROTO_UDP ||
|
|
protocol == IPPROTO_ICMP ||
|
|
protocol == IPPROTO_ICMPV6);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 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;
|
|
}
|